Example #1
0
        /// <summary>
        /// Filter a list of occurrences, removing ones that are older than the given startTime
        /// and cutting anyone with the startTime in the middle, plus set as unavailable any occurrence
        /// that happens inside the advanceTime (relative to the current machine time).
        /// </summary>
        /// <param name="occurences"></param>
        /// <param name="startTime"></param>
        /// <param name="advanceTime"></param>
        /// <returns></returns>
        private static IEnumerable <CalendarDll.CalendarUtils.AvailabilitySlot> OccurrencesWithAdvanceTimeSlot(
            IEnumerable <CalendarDll.CalendarUtils.AvailabilitySlot> occurences, DateTimeOffset startTime, double advanceTime)
        {
            var notBeforeTime = DateTimeOffset.Now.AddHours(advanceTime);

            if (startTime < notBeforeTime)
            {
                var past = new CalendarDll.CalendarUtils.AvailabilitySlot
                {
                    StartTime          = startTime,
                    EndTime            = notBeforeTime,
                    AvailabilityTypeID = (int)AvailabilityType.Unavailable
                };
                yield return(past);
            }
            else
            {
                // Since the queried time is newer than the 'limit time in advance',
                // then our new limit is the queried startTime (anything before that
                // is not wanted now)
                notBeforeTime = startTime;
            }
            // Occurrences need to be filtered to do not include ones that happens older than the advance time / startTime,
            // not just for performance of the timeline computation, but for consistency since it can breaks the logic there (since
            // it expects events sorted ascending by startTime, and occurrence with older start than advanceTime slot would break it).
            // Too, if an occurrence starts before advance but ends after, must create a new slot (cut from intersection -advanceTimeStart- to endtime).
            foreach (var s in occurences)
            {
                if (s.EndTime <= notBeforeTime)
                {
                    // Excluded, is old
                    continue;
                }
                else if (s.StartTime < notBeforeTime)
                {
                    // Intersection (since endTime is not older than notBeforeTime, by first 'if' check)
                    yield return(new CalendarDll.CalendarUtils.AvailabilitySlot
                    {
                        StartTime = notBeforeTime,
                        EndTime = s.EndTime,
                        AvailabilityTypeID = s.AvailabilityTypeID
                    });
                }
                else
                {
                    // Just newer, give it 'as is'
                    yield return(s);
                }
            }
        }
Example #2
0
        /// <summary>
        /// It optimizes a given timeline, as a list of non overlapping and ordered slots, by merging consecutive
        /// slots of the same availabilityType in one, getting the smaller timeline list possible to represent
        /// the same availability.
        /// </summary>
        /// <param name="slots">Non overlapping and ordered slots, as coming from GetTimeline; otherwise, results are unexpected. A null slot in the list will throw random exception.</param>
        /// <returns></returns>
        static private IEnumerable <CalendarDll.CalendarUtils.AvailabilitySlot> OptimizeTimeline(IEnumerable <CalendarDll.CalendarUtils.AvailabilitySlot> slots)
        {
            var enumerate = slots.GetEnumerator();

            // Quick return on empty list
            if (!enumerate.MoveNext())
            {
                yield break;
            }

            // Read first: is the initial 'previous one'
            var prevSlot = enumerate.Current;

            while (enumerate.MoveNext())
            {
                var currentSlot = enumerate.Current;

                if (currentSlot.AvailabilityTypeID == prevSlot.AvailabilityTypeID)
                {
                    // Merge lastSlot and current slot
                    prevSlot = new CalendarDll.CalendarUtils.AvailabilitySlot
                    {
                        AvailabilityTypeID = prevSlot.AvailabilityTypeID,
                        StartTime          = prevSlot.StartTime,
                        EndTime            = currentSlot.EndTime
                    };
                }
                else
                {
                    // Merging not possible, return last one and
                    // replace reference with current one
                    yield return(prevSlot);

                    prevSlot = currentSlot;
                }
            }

            // Return the pending last slot
            yield return(prevSlot);
        }
Example #3
0
        /// <summary>
        /// Gets a timeline of non overlapping slots, without holes (filled in with 'unavailable')
        /// for the given set of slots, sorted ascending.
        /// But is NOT optimized, meaning can contains consecutive slots of same availability type; to
        /// get an optimized, compressed, output, apply OptimizeTimeline to the result.
        /// </summary>
        /// <param name="AvailabilitySlots"></param>
        /// <param name="passNumber">Used only internally, to avoid that recursive calls creates an stack overflow.</param>
        /// <returns></returns>
        static private IEnumerable <CalendarDll.CalendarUtils.AvailabilitySlot> GetTimeline(IEnumerable <CalendarDll.CalendarUtils.AvailabilitySlot> AvailabilitySlots, int passNumber)
        {
            var enumerate = AvailabilitySlots.GetEnumerator();

            // Quick return
            if (!enumerate.MoveNext())
            {
                yield break;
            }
            // First one, as previous on the iteration.
            var prevsBuffer = new List <CalendarDll.CalendarUtils.AvailabilitySlot>();

            prevsBuffer.Add(enumerate.Current);
            var newsBuffer = new List <CalendarDll.CalendarUtils.AvailabilitySlot>();
            // On each iteration, number of queued items in the buffer that can be released
            var dequeueCount = 0;
            // On each iteration, number of queued items in the buffer After the dequeueCount
            // that must be discarded/removed frmo the buffer without return them, because
            // gets obsolete after perform the analysis (that means that new, fragmented,
            // ranges were created for that one because of collisions)
            var discardCount = 0;
            // On each iteration, current element must or must not be added 'as is' to the queue
            var addCurrentToBuffer = true;

            // Implemented several passes when an analyzed lists of 'prevsBuffer' contains more than 1
            // element, because how elements may overlap has edge cases that only can be solved
            // by reordering the list and apply the logic on it.
            //
            // NOTE: It's something optmized because rather than re-analyze the whole resultsets when
            // a complete pass is done [like GetTimeline(GetTimeline(...))], only the specific sections of overlaped ranges
            // performs a second (or more) passes, and still is effective.
            //
            // TODO: Review if the logic updates makes possible to remove the multiple passes feature OR a most effective way to check when
            // multiple passes are needed (right now just [prevsBuffer.length > 2], maybe other quick logic can discard
            // cases that match that condiction but don't need a second pass).
            // IMPORTANT: Right now, on unit tests for different edge cases extra passes are not needed.
            var needsAnotherPass = false;

            while (enumerate.MoveNext())
            {
                var current = enumerate.Current;
                needsAnotherPass = prevsBuffer.Count > 2;

                foreach (var prev in prevsBuffer)
                {
                    if (current.StartTime >= prev.EndTime)
                    {
                        // Previous can be dequeue (will not collide with following ranges)
                        dequeueCount++;
                    }
                    else /* current.StartTime < prev.EndTime */
                    {
                        addCurrentToBuffer = false;
                        discardCount++;
                        // Intersection of events:
                        // |.....prev.....|
                        //         |.....current.....|
                        // |.prev..|.new..|..current.|
                        //
                        // TODO: Optimize creation of ranges: only 2 are needed, because the
                        // new range will share its availability with one of the others, allowing
                        // to mix both in one.
                        //
                        // IMPORTANT: current may finish before prev (current inside prev) like
                        // |.............prev..............|
                        //        |......current....|
                        // |.prev.|.prev-or-current.|.prev.|
                        //
                        // TODO: If prev has higher or same priority, there is only one range, the prev
                        // TODO: If current has higher priority, three ranges are needed, the current keeps 'as is'
                        //
                        // IMPORTANT: because of multiple ranges overlapping and ones first being longer than following ones,
                        // the split behavior may end creating 'prev' ranges that happens AFTER 'current', because
                        // of that the first cut may end being a prev or current section and 'minDateTime and maxdatetime' are required.
                        // The graph can be something like
                        //        |.......prev........|
                        // |....current....|
                        // |p-or-c|.p-or-c.|.prev.....|
                        // ANOTHER EDGE CASE Because multiple overlapping and passes
                        //                  |.....prev.....|
                        // |...current..|
                        // |...current..|new|.....prev.....|

                        // - Return a reduced version of the prev
                        // (if there is place for one!)
                        var minStart = MinDateTime(prev.StartTime, current.StartTime);
                        var minEnd   = MinDateTime(MinDateTime(prev.EndTime, current.EndTime), MaxDateTime(prev.StartTime, current.StartTime));
                        //if (minEnd < minStart) throw new Exception("TEST minEnd < minStart::" + passNumber + ":" + minEnd.ToString("r") + ":" + minStart.ToString("r"));
                        if (prev.StartTime != current.StartTime)
                        {
                            newsBuffer.Add(new CalendarDll.CalendarUtils.AvailabilitySlot
                            {
                                StartTime          = minStart,
                                EndTime            = minEnd,
                                AvailabilityTypeID = minStart == current.StartTime ? current.AvailabilityTypeID : prev.AvailabilityTypeID
                            });
                        }
                        // - New range on the intersection, with the stronger availability
                        var maxStart = MaxDateTime(MinDateTime(prev.EndTime, current.EndTime), MaxDateTime(prev.StartTime, current.StartTime));
                        //if (maxStart < minEnd) throw new Exception("TEST maxStart < minEnd::" + maxStart.ToString("r") + ":" + minEnd.ToString("r"));
                        newsBuffer.Add(new CalendarDll.CalendarUtils.AvailabilitySlot
                        {
                            StartTime          = minEnd,
                            EndTime            = maxStart,
                            AvailabilityTypeID = GetPriorityAvailabilitySlot(prev, current).AvailabilityTypeID
                        });
                        // - Reduced version of the current
                        // (if there is place for one!)
                        if (prev.EndTime != current.EndTime)
                        {
                            var maxEnd = MaxDateTime(prev.EndTime, current.EndTime);
                            //if (maxEnd < maxStart) throw new Exception("TEST maxEnd < maxStart::" + maxEnd.ToString("r") + ":" + maxStart.ToString("r"));
                            newsBuffer.Add(new CalendarDll.CalendarUtils.AvailabilitySlot
                            {
                                StartTime          = maxStart,
                                EndTime            = maxEnd,
                                AvailabilityTypeID = maxEnd == current.EndTime ? current.AvailabilityTypeID : prev.AvailabilityTypeID
                            });
                        }
                    }
                }

                if (addCurrentToBuffer)
                {
                    // Current must be in the list
                    prevsBuffer.Add(current);
                }
                addCurrentToBuffer = true;

                // Add the new ones to queue
                foreach (var ne in newsBuffer)
                {
                    prevsBuffer.Add(ne);
                }
                newsBuffer.Clear();

                // Check two latest ranges in order to fill in a hole (if any)
                if (prevsBuffer.Count > 1)
                {
                    var preHole  = prevsBuffer[prevsBuffer.Count - 2];
                    var postHole = prevsBuffer[prevsBuffer.Count - 1];
                    if (postHole.StartTime > preHole.EndTime)
                    {
                        // There is a gap:
                        // fill it with unavailable slot
                        var hole = new CalendarDll.CalendarUtils.AvailabilitySlot
                        {
                            AvailabilityTypeID = (int)LcCalendar.AvailabilityType.Unavailable,
                            StartTime          = preHole.EndTime,
                            EndTime            = postHole.StartTime
                        };
                        // Must be inserted in the place of postHole (to keep them sorted), and re-add that after
                        prevsBuffer[prevsBuffer.Count - 1] = hole;
                        prevsBuffer.Add(postHole);
                        dequeueCount++;
                    }
                }

                // Dequee: return and remove from buffer that elements that are ready
                for (var i = 0; i < dequeueCount; i++)
                {
                    // Since we are modifing the buffer on each iteration,
                    // the element to return and remove is ever the first (0)
                    yield return(prevsBuffer[0]);

                    prevsBuffer.RemoveAt(0);
                }
                dequeueCount = 0;
                // Discard: remove from buffer, but NOT return, elements that get obsolete
                for (var i = 0; i < discardCount; i++)
                {
                    // Since we are modifing the buffer on each iteration,
                    // the element to remove is ever the first (0)
                    prevsBuffer.RemoveAt(0);
                }
                discardCount = 0;

                // Multi passes are needed to ensure correct results.
                if (needsAnotherPass)
                {
                    // Stack overflow, excessive passes, control:
                    if (passNumber + 1 > MAX_GETTIMELINE_PASSES)
                    {
                        throw new Exception("Impossible to compute availability.");
                    }
                    prevsBuffer = GetTimeline(prevsBuffer.OrderBy(x => x.StartTime), passNumber + 1).ToList();
                }
            }

            // Return last pending:
            foreach (var range in prevsBuffer)
            {
                yield return(range);
            }
        }
Example #4
0
        static CalendarDll.CalendarUtils.AvailabilitySlot GetPriorityAvailabilitySlot(CalendarDll.CalendarUtils.AvailabilitySlot date1, CalendarDll.CalendarUtils.AvailabilitySlot date2)
        {
            var pri1 = AvailabilityPriorities[date1.AvailabilityTypeID];
            var pri2 = AvailabilityPriorities[date2.AvailabilityTypeID];

            return(pri1 >= pri2 ? date1 : date2);
        }