From aaeae287415a8a92d191329cf84e27a622fe47fe Mon Sep 17 00:00:00 2001 From: Markus Minichmayr Date: Thu, 1 May 2025 22:09:43 +0200 Subject: [PATCH 1/5] Fix `RecurringEvaluator`, which was not properly considering `periodEnd` --- Ical.Net/Evaluation/RecurringEvaluator.cs | 25 ++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/Ical.Net/Evaluation/RecurringEvaluator.cs b/Ical.Net/Evaluation/RecurringEvaluator.cs index d1aa9549a..980cfa30e 100644 --- a/Ical.Net/Evaluation/RecurringEvaluator.cs +++ b/Ical.Net/Evaluation/RecurringEvaluator.cs @@ -49,11 +49,16 @@ protected IEnumerable EvaluateRRule(CalDateTime referenceDate, CalDateTi /// Evaluates the RDate component. protected IEnumerable EvaluateRDate(CalDateTime referenceDate, CalDateTime? periodStart, CalDateTime? periodEnd) { - var recurrences = - new SortedSet(Recurrable.RecurrenceDates - .GetAllPeriodsByKind(PeriodKind.Period, PeriodKind.DateOnly, PeriodKind.DateTime)); + var recurrences = Recurrable.RecurrenceDates + .GetAllPeriodsByKind(PeriodKind.Period, PeriodKind.DateOnly, PeriodKind.DateTime) + .AsEnumerable(); - return recurrences; + if (periodStart != null) + recurrences = recurrences.Where(p => p.StartTime.GreaterThanOrEqual(periodStart)); + if (periodEnd != null) + recurrences = recurrences.Where(p => p.StartTime.LessThan(periodEnd)); + + return new SortedSet(recurrences); } /// @@ -89,9 +94,15 @@ protected IEnumerable EvaluateExRule(CalDateTime referenceDate, CalDateT /// The end date of the range to evaluate. protected IEnumerable EvaluateExDate(CalDateTime referenceDate, CalDateTime? periodStart, CalDateTime? periodEnd) { - var exDates = new SortedSet(Recurrable - .ExceptionDates.GetAllPeriodsByKind(PeriodKind.DateOnly, PeriodKind.DateTime)); - return exDates; + var exDates = Recurrable.ExceptionDates.GetAllPeriodsByKind(PeriodKind.DateOnly, PeriodKind.DateTime) + .AsEnumerable(); + + if (periodStart != null) + exDates = exDates.Where(p => p.StartTime.GreaterThanOrEqual(periodStart)); + if (periodEnd != null) + exDates = exDates.Where(p => p.StartTime.LessThan(periodEnd)); + + return new SortedSet(exDates); } public override IEnumerable Evaluate(CalDateTime referenceDate, CalDateTime? periodStart, CalDateTime? periodEnd, EvaluationOptions? options) From f60533608b1cf9492ca9d798a84da81af840cc81 Mon Sep 17 00:00:00 2001 From: Markus Minichmayr Date: Wed, 30 Apr 2025 17:42:23 +0200 Subject: [PATCH 2/5] Evaluation: Remove `periodEnd` param from `.GetOccurrences()` and `.Evaluate()` --- Ical.Net/Calendar.cs | 26 ++++---------- Ical.Net/CalendarCollection.cs | 8 ++--- Ical.Net/CalendarComponents/Alarm.cs | 11 +++--- Ical.Net/CalendarComponents/FreeBusy.cs | 4 ++- .../CalendarComponents/RecurringComponent.cs | 6 ++-- Ical.Net/Evaluation/Evaluator.cs | 2 +- Ical.Net/Evaluation/EventEvaluator.cs | 10 ++---- Ical.Net/Evaluation/IEvaluator.cs | 14 ++++---- .../Evaluation/RecurrencePatternEvaluator.cs | 28 +++++---------- Ical.Net/Evaluation/RecurrenceUtil.cs | 34 ++++++++----------- Ical.Net/Evaluation/RecurringEvaluator.cs | 29 ++++++---------- Ical.Net/Evaluation/TimeZoneInfoEvaluator.cs | 7 ---- Ical.Net/Evaluation/TodoEvaluator.cs | 8 ++--- Ical.Net/IGetOccurrences.cs | 10 +++--- Ical.Net/VTimeZoneInfo.cs | 4 +-- 15 files changed, 75 insertions(+), 126 deletions(-) diff --git a/Ical.Net/Calendar.cs b/Ical.Net/Calendar.cs index 5644fdf2a..7f9be70bd 100644 --- a/Ical.Net/Calendar.cs +++ b/Ical.Net/Calendar.cs @@ -217,26 +217,12 @@ public VTimeZone AddTimeZone(VTimeZone tz) return tz; } - /// - /// Returns a list of occurrences of each recurring component - /// that occur between and . - /// - /// The beginning date/time of the range. - /// The end date/time of the range. - /// - /// A list of occurrences that fall between the date/time arguments provided. - public virtual IEnumerable GetOccurrences(CalDateTime? startTime = null, CalDateTime? endTime = null, EvaluationOptions? options = null) - => GetOccurrences(startTime, endTime, options); + /// + public virtual IEnumerable GetOccurrences(CalDateTime? startTime = null, EvaluationOptions? options = null) + => GetOccurrences(startTime, options); - /// - /// Returns all occurrences of components of type T that start within the date range provided. - /// All components occurring between and - /// will be returned. - /// - /// The starting date range - /// The ending date range - /// - public virtual IEnumerable GetOccurrences(CalDateTime? startTime = null, CalDateTime? endTime = null, EvaluationOptions? options = null) where T : IRecurringComponent + /// + public virtual IEnumerable GetOccurrences(CalDateTime? startTime = null, EvaluationOptions? options = null) where T : IRecurringComponent { // These are the UID/RECURRENCE-ID combinations that replace other occurrences. var recurrenceIdsAndUids = this.Children.OfType() @@ -247,7 +233,7 @@ public virtual IEnumerable GetOccurrences(CalDateTime? startTime var occurrences = RecurringItems .OfType() - .Select(recurrable => recurrable.GetOccurrences(startTime, endTime, options)) + .Select(recurrable => recurrable.GetOccurrences(startTime, options)) // Enumerate the list of occurrences (not the occurrences themselves) now to ensure // the initialization code is run, including validation and error handling. diff --git a/Ical.Net/CalendarCollection.cs b/Ical.Net/CalendarCollection.cs index ed7b12850..5d14c1811 100644 --- a/Ical.Net/CalendarCollection.cs +++ b/Ical.Net/CalendarCollection.cs @@ -57,11 +57,11 @@ private IEnumerable GetOccurrences(Func GetOccurrences(CalDateTime? startTime = null, CalDateTime? endTime = null, EvaluationOptions? options = null) - => GetOccurrences(iCal => iCal.GetOccurrences(startTime, endTime, options)); + public IEnumerable GetOccurrences(CalDateTime? startTime = null, EvaluationOptions? options = null) + => GetOccurrences(iCal => iCal.GetOccurrences(startTime, options)); - public IEnumerable GetOccurrences(CalDateTime? startTime = null, CalDateTime? endTime = null, EvaluationOptions? options = null) where T : IRecurringComponent - => GetOccurrences(iCal => iCal.GetOccurrences(startTime, endTime, options)); + public IEnumerable GetOccurrences(CalDateTime? startTime = null, EvaluationOptions? options = null) where T : IRecurringComponent + => GetOccurrences(iCal => iCal.GetOccurrences(startTime, options)); private FreeBusy CombineFreeBusy(FreeBusy? main, FreeBusy current) { diff --git a/Ical.Net/CalendarComponents/Alarm.cs b/Ical.Net/CalendarComponents/Alarm.cs index 1fd4f8e69..56bfc47cd 100644 --- a/Ical.Net/CalendarComponents/Alarm.cs +++ b/Ical.Net/CalendarComponents/Alarm.cs @@ -72,9 +72,9 @@ public Alarm() /// /// Gets a list of alarm occurrences for the given recurring component, - /// that occur between and . + /// that occur at or after . /// - public virtual IList GetOccurrences(IRecurringComponent rc, CalDateTime? fromDate, CalDateTime? toDate, EvaluationOptions? options) + public virtual IList GetOccurrences(IRecurringComponent rc, CalDateTime? fromDate, EvaluationOptions? options) { if (Trigger == null) { @@ -95,7 +95,7 @@ public virtual IList GetOccurrences(IRecurringComponent rc, Cal } Duration? duration = null; - foreach (var o in rc.GetOccurrences(fromDate, toDate, options)) + foreach (var o in rc.GetOccurrences(fromDate, options)) { var dt = o.Period.StartTime; if (string.Equals(Trigger.Related, TriggerRelation.End, TriggerRelation.Comparison)) @@ -145,10 +145,9 @@ public virtual IList GetOccurrences(IRecurringComponent rc, Cal /// is null, all triggered alarms will be returned. /// /// The earliest date/time to poll triggered alarms for. - /// /// /// A list of objects, each containing a triggered alarm. - public virtual IList Poll(CalDateTime? start, CalDateTime? end, EvaluationOptions? options = null) + public virtual IList Poll(CalDateTime? start, EvaluationOptions? options = null) { var results = new List(); @@ -158,7 +157,7 @@ public virtual IList Poll(CalDateTime? start, CalDateTime? end, return results; } - results.AddRange(GetOccurrences(rc, start, end, options)); + results.AddRange(GetOccurrences(rc, start, options)); return results; } diff --git a/Ical.Net/CalendarComponents/FreeBusy.cs b/Ical.Net/CalendarComponents/FreeBusy.cs index 6e02bd055..316c87de5 100644 --- a/Ical.Net/CalendarComponents/FreeBusy.cs +++ b/Ical.Net/CalendarComponents/FreeBusy.cs @@ -22,7 +22,9 @@ public class FreeBusy : UniqueComponent, IMergeable return null; } - var occurrences = occ.GetOccurrences(freeBusyRequest.Start, freeBusyRequest.End, options); + var occurrences = occ.GetOccurrences(freeBusyRequest.Start, options) + .TakeWhile(p => (freeBusyRequest.End == null) || (p.Period.StartTime < freeBusyRequest.End)); + var contacts = new List(); var isFilteredByAttendees = false; diff --git a/Ical.Net/CalendarComponents/RecurringComponent.cs b/Ical.Net/CalendarComponents/RecurringComponent.cs index 24db107e2..a531d27d5 100644 --- a/Ical.Net/CalendarComponents/RecurringComponent.cs +++ b/Ical.Net/CalendarComponents/RecurringComponent.cs @@ -190,13 +190,13 @@ protected override void OnDeserializing(StreamingContext context) Initialize(); } - public virtual IEnumerable GetOccurrences(CalDateTime? startTime = null, CalDateTime? endTime = null, EvaluationOptions? options = null) - => RecurrenceUtil.GetOccurrences(this, startTime, endTime, options); + public virtual IEnumerable GetOccurrences(CalDateTime? startTime = null, EvaluationOptions? options = null) + => RecurrenceUtil.GetOccurrences(this, startTime, options); public virtual IList PollAlarms() => PollAlarms(null, null); public virtual IList PollAlarms(CalDateTime? startTime, CalDateTime? endTime) - => Alarms.SelectMany(a => a.Poll(startTime, endTime)).ToList(); + => Alarms.SelectMany(a => a.Poll(startTime).TakeWhile(p => (endTime == null) || (p.Period?.StartTime < endTime))).ToList(); protected bool Equals(RecurringComponent other) { diff --git a/Ical.Net/Evaluation/Evaluator.cs b/Ical.Net/Evaluation/Evaluator.cs index c967bb96b..083047a2b 100644 --- a/Ical.Net/Evaluation/Evaluator.cs +++ b/Ical.Net/Evaluation/Evaluator.cs @@ -48,5 +48,5 @@ protected void IncrementDate(ref CalDateTime dt, RecurrencePattern pattern, int } } - public abstract IEnumerable Evaluate(CalDateTime referenceDate, CalDateTime? periodStart, CalDateTime? periodEnd, EvaluationOptions? options); + public abstract IEnumerable Evaluate(CalDateTime referenceDate, CalDateTime? periodStart, EvaluationOptions? options); } diff --git a/Ical.Net/Evaluation/EventEvaluator.cs b/Ical.Net/Evaluation/EventEvaluator.cs index f71a4d432..e387c9597 100644 --- a/Ical.Net/Evaluation/EventEvaluator.cs +++ b/Ical.Net/Evaluation/EventEvaluator.cs @@ -27,10 +27,7 @@ public EventEvaluator(CalendarEvent evt) : base(evt) { } /// /// Evaluates this event to determine the dates and times for which the event occurs. - /// This method only evaluates events which occur between - /// and ; therefore, if you require a list of events which - /// occur outside of this range, you must specify a and - /// which encapsulate the date(s) of interest. + /// This method only evaluates events which occur at or after. /// /// /// For events with very complex recurrence rules, this method may be a bottleneck @@ -39,13 +36,12 @@ public EventEvaluator(CalendarEvent evt) : base(evt) { } /// /// /// The beginning date of the range to evaluate. - /// The end date of the range to evaluate. /// /// - public override IEnumerable Evaluate(CalDateTime referenceTime, CalDateTime? periodStart, CalDateTime? periodEnd, EvaluationOptions? options) + public override IEnumerable Evaluate(CalDateTime referenceTime, CalDateTime? periodStart, EvaluationOptions? options) { // Evaluate recurrences normally - var periods = base.Evaluate(referenceTime, periodStart, periodEnd, options) + var periods = base.Evaluate(referenceTime, periodStart, options) .Select(WithEndTime); return periods; diff --git a/Ical.Net/Evaluation/IEvaluator.cs b/Ical.Net/Evaluation/IEvaluator.cs index 92a8e27e1..fa1d77de9 100644 --- a/Ical.Net/Evaluation/IEvaluator.cs +++ b/Ical.Net/Evaluation/IEvaluator.cs @@ -14,13 +14,12 @@ public interface IEvaluator { /// /// Evaluates this item to determine the dates and times for which it occurs/recurs. - /// This method only evaluates items which occur/recur between - /// and ; therefore, if you require a list of items which - /// occur outside of this range, you must specify a and - /// which encapsulate the date(s) of interest. + /// This method only evaluates items which occur/recur at or after . + /// To apply an upper bound, consider using . /// This method evaluates using the as the beginning - /// point. For example, for a WEEKLY occurrence, the - /// determines the day of week that this item will recur on. + /// point. For example, for a WEEKLY occurrence, the + /// determines the day of week that this item will recur on. If + /// is set to null, all recurrences will be returned. /// /// Items are returned in ascending order. /// @@ -31,11 +30,10 @@ public interface IEvaluator /// /// /// - /// /// /// /// A sequence of objects for /// each date/time when this item occurs/recurs. /// - IEnumerable Evaluate(CalDateTime referenceDate, CalDateTime? periodStart, CalDateTime? periodEnd, EvaluationOptions? options); + IEnumerable Evaluate(CalDateTime referenceDate, CalDateTime? periodStart, EvaluationOptions? options); } diff --git a/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs b/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs index f3d9f23b8..8b07df17f 100644 --- a/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs +++ b/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs @@ -115,14 +115,13 @@ private RecurrencePattern ProcessRecurrencePattern(CalDateTime referenceDate) /// For example, if the search start date (start) is Wed, Mar 23, 12:19PM, but the recurrence is Mon - Fri, 9:00AM - 5:00PM, /// the start dates returned should all be at 9:00AM, and not 12:19PM. /// - private IEnumerable GetDates(CalDateTime seed, CalDateTime? periodStart, CalDateTime? periodEnd, RecurrencePattern pattern, + private IEnumerable GetDates(CalDateTime seed, CalDateTime? periodStart, RecurrencePattern pattern, EvaluationOptions? options) { // In the first step, we work with DateTime values, so we need to convert the CalDateTime to DateTime var originalDate = seed; var seedCopy = seed; var periodStartDt = periodStart?.ToTimeZone(seed.TzId); - var periodEndDt = periodEnd?.ToTimeZone(seed.TzId); if ((pattern.Frequency == FrequencyType.Yearly) && (pattern.ByWeekNo.Count != 0)) { @@ -151,14 +150,14 @@ private IEnumerable GetDates(CalDateTime seed, CalDateTime? periodS // Do the enumeration in a separate method, as it is a generator method that is // only executed after enumeration started. In order to do most validation upfront, // do as many steps outside the generator as possible. - return EnumerateDates(originalDate, seedCopy, periodStartDt, periodEndDt, pattern, options); + return EnumerateDates(originalDate, seedCopy, pattern, options); } - private IEnumerable EnumerateDates(CalDateTime originalDate, CalDateTime intervalRefTime, CalDateTime? periodStart, CalDateTime? periodEnd, RecurrencePattern pattern, EvaluationOptions? options) + private IEnumerable EnumerateDates(CalDateTime originalDate, CalDateTime intervalRefTime, RecurrencePattern pattern, EvaluationOptions? options) { var expandBehavior = RecurrenceUtil.GetExpandBehaviorList(pattern); - var searchEndDate = GetSearchEndDate(periodEnd, pattern); + var searchEndDate = GetSearchEndDate(pattern); var noCandidateIncrementCount = 0; @@ -168,7 +167,6 @@ private IEnumerable EnumerateDates(CalDateTime originalDate, CalDat if (dateCount >= pattern.Count) break; - //No need to continue if the interval's lower limit is after the periodEnd if (searchEndDate < GetIntervalLowerLimit(intervalRefTime, pattern)) break; @@ -182,14 +180,10 @@ private IEnumerable EnumerateDates(CalDateTime originalDate, CalDat // candidates MAY occur before periodStart // For example, FREQ=YEARLY;BYWEEKNO=1 could return dates // from the previous year. - // - // exclude candidates that start at the same moment as periodEnd if the period is a range but keep them if targeting a specific moment + if (dateCount >= pattern.Count) break; - if ((candidate >= periodEnd && periodStart != periodEnd) || candidate > periodEnd && periodStart == periodEnd) - continue; - // UNTIL is applied outside of this method, after TZ conversion has been applied. yield return candidate; @@ -205,7 +199,7 @@ private IEnumerable EnumerateDates(CalDateTime originalDate, CalDat } } - private static CalDateTime? GetSearchEndDate(CalDateTime? periodEnd, RecurrencePattern pattern) + private static CalDateTime? GetSearchEndDate(RecurrencePattern pattern) { // This value is only used for performance reasons to stop incrementing after // until is passed, even if no recurrences are being found. @@ -214,10 +208,7 @@ private IEnumerable EnumerateDates(CalDateTime originalDate, CalDat // Precise UNTIL handling is done outside this method after TZ conversion. var coarseUntil = pattern.Until?.AddDays(1); - if ((coarseUntil == null) || (periodEnd == null)) - return (coarseUntil ?? periodEnd); - - return (coarseUntil < periodEnd ? coarseUntil : periodEnd); + return coarseUntil; } /// @@ -768,10 +759,9 @@ private static Period CreatePeriod(CalDateTime dateTime, CalDateTime referenceDa /// /// The reference date, i.e. DTSTART. /// Start (incl.) of the period occurrences are generated for. - /// End (excl.) of the period occurrences are generated for. /// /// - public override IEnumerable Evaluate(CalDateTime referenceDate, CalDateTime? periodStart, CalDateTime? periodEnd, EvaluationOptions? options) + public override IEnumerable Evaluate(CalDateTime referenceDate, CalDateTime? periodStart, EvaluationOptions? options) { if (Pattern.Frequency != FrequencyType.None && Pattern.Frequency < FrequencyType.Daily && !referenceDate.HasTime) { @@ -783,7 +773,7 @@ public override IEnumerable Evaluate(CalDateTime referenceDate, CalDateT // Create a recurrence pattern suitable for use during evaluation. var pattern = ProcessRecurrencePattern(referenceDate); - var periodQuery = GetDates(referenceDate, periodStart, periodEnd, pattern, options) + var periodQuery = GetDates(referenceDate, periodStart, pattern, options) .Select(dt => CreatePeriod(dt, referenceDate)); if (pattern.Until is not null) diff --git a/Ical.Net/Evaluation/RecurrenceUtil.cs b/Ical.Net/Evaluation/RecurrenceUtil.cs index 78dbf154c..a59bfd0ab 100644 --- a/Ical.Net/Evaluation/RecurrenceUtil.cs +++ b/Ical.Net/Evaluation/RecurrenceUtil.cs @@ -13,10 +13,7 @@ namespace Ical.Net.Evaluation; internal class RecurrenceUtil { - public static IEnumerable GetOccurrences(IRecurrable recurrable, CalDateTime dt, EvaluationOptions? options = null) => GetOccurrences(recurrable, - new CalDateTime(dt.Date), new CalDateTime(dt.Date.AddDays(1)), options); - - public static IEnumerable GetOccurrences(IRecurrable recurrable, CalDateTime? periodStart, CalDateTime? periodEnd, EvaluationOptions? options = null) + public static IEnumerable GetOccurrences(IRecurrable recurrable, CalDateTime? periodStart, EvaluationOptions? options = null) { var evaluator = recurrable.Evaluator; if (evaluator == null || recurrable.Start == null) @@ -27,28 +24,25 @@ public static IEnumerable GetOccurrences(IRecurrable recurrable, Cal // Ensure the start time is associated with the object being queried var start = recurrable.Start; - // Change the time zone of periodStart/periodEnd as needed + // Change the time zone of periodStart as needed // so they can be used during the evaluation process. if (periodStart != null) periodStart = new CalDateTime(periodStart.Date, periodStart.Time, start.TzId); - if (periodEnd != null) - periodEnd = new CalDateTime(periodEnd.Date, periodEnd.Time, start.TzId); - - var periods = evaluator.Evaluate(start, periodStart, periodEnd, options); - var occurrences = - from p in periods - let endTime = p.EndTime ?? p.StartTime - where - (((periodStart == null) || endTime.GreaterThan(periodStart)) && ((periodEnd == null) || p.StartTime.LessThan(periodEnd)) || - (periodStart != null && periodStart.Equals(periodEnd) - && p.StartTime.LessThanOrEqual(periodStart) - && endTime.GreaterThan(periodEnd))) || //A period that starts at the same time it ends - (p.StartTime.Equals(endTime) && p.StartTime.Equals(periodStart)) //An event that starts at the same time it ends - select new Occurrence(recurrable, p); + var periods = evaluator.Evaluate(start, periodStart, options); + if (periodStart != null) + { + periods = + from p in periods + let endTime = p.EndTime ?? p.StartTime + where + p.StartTime.GreaterThanOrEqual(periodStart) + || endTime.GreaterThan(periodStart) + select p; + } - return occurrences; + return periods.Select(p => new Occurrence(recurrable, p)); } public static bool?[] GetExpandBehaviorList(RecurrencePattern p) diff --git a/Ical.Net/Evaluation/RecurringEvaluator.cs b/Ical.Net/Evaluation/RecurringEvaluator.cs index 980cfa30e..3c27011b6 100644 --- a/Ical.Net/Evaluation/RecurringEvaluator.cs +++ b/Ical.Net/Evaluation/RecurringEvaluator.cs @@ -26,9 +26,8 @@ public RecurringEvaluator(IRecurrable obj) /// /// /// The beginning date of the range to evaluate. - /// The end date of the range to evaluate. /// - protected IEnumerable EvaluateRRule(CalDateTime referenceDate, CalDateTime? periodStart, CalDateTime? periodEnd, EvaluationOptions? options) + protected IEnumerable EvaluateRRule(CalDateTime referenceDate, CalDateTime? periodStart, EvaluationOptions? options) { if (Recurrable.RecurrenceRules == null || !Recurrable.RecurrenceRules.Any()) return []; @@ -36,7 +35,7 @@ protected IEnumerable EvaluateRRule(CalDateTime referenceDate, CalDateTi var periodsQueries = Recurrable.RecurrenceRules.Select(rule => { var ruleEvaluator = new RecurrencePatternEvaluator(rule); - return ruleEvaluator.Evaluate(referenceDate, periodStart, periodEnd, options); + return ruleEvaluator.Evaluate(referenceDate, periodStart, options); }) // Enumerate the outer sequence (not the inner sequences of periods themselves) now to ensure // the initialization code is run, including validation and error handling. @@ -47,7 +46,7 @@ protected IEnumerable EvaluateRRule(CalDateTime referenceDate, CalDateTi } /// Evaluates the RDate component. - protected IEnumerable EvaluateRDate(CalDateTime referenceDate, CalDateTime? periodStart, CalDateTime? periodEnd) + protected IEnumerable EvaluateRDate(CalDateTime referenceDate, CalDateTime? periodStart) { var recurrences = Recurrable.RecurrenceDates .GetAllPeriodsByKind(PeriodKind.Period, PeriodKind.DateOnly, PeriodKind.DateTime) @@ -55,8 +54,6 @@ protected IEnumerable EvaluateRDate(CalDateTime referenceDate, CalDateTi if (periodStart != null) recurrences = recurrences.Where(p => p.StartTime.GreaterThanOrEqual(periodStart)); - if (periodEnd != null) - recurrences = recurrences.Where(p => p.StartTime.LessThan(periodEnd)); return new SortedSet(recurrences); } @@ -66,9 +63,8 @@ protected IEnumerable EvaluateRDate(CalDateTime referenceDate, CalDateTi /// /// /// The beginning date of the range to evaluate. - /// The end date of the range to evaluate. /// - protected IEnumerable EvaluateExRule(CalDateTime referenceDate, CalDateTime? periodStart, CalDateTime? periodEnd, EvaluationOptions? options) + protected IEnumerable EvaluateExRule(CalDateTime referenceDate, CalDateTime? periodStart, EvaluationOptions? options) { if (Recurrable.ExceptionRules == null || !Recurrable.ExceptionRules.Any()) return []; @@ -76,7 +72,7 @@ protected IEnumerable EvaluateExRule(CalDateTime referenceDate, CalDateT var exRuleEvaluatorQueries = Recurrable.ExceptionRules.Select(exRule => { var exRuleEvaluator = new RecurrencePatternEvaluator(exRule); - return exRuleEvaluator.Evaluate(referenceDate, periodStart, periodEnd, options); + return exRuleEvaluator.Evaluate(referenceDate, periodStart, options); }) // Enumerate the outer sequence (not the inner sequences of periods themselves) now to ensure // the initialization code is run, including validation and error handling. @@ -91,21 +87,18 @@ protected IEnumerable EvaluateExRule(CalDateTime referenceDate, CalDateT /// /// /// The beginning date of the range to evaluate. - /// The end date of the range to evaluate. - protected IEnumerable EvaluateExDate(CalDateTime referenceDate, CalDateTime? periodStart, CalDateTime? periodEnd) + protected IEnumerable EvaluateExDate(CalDateTime referenceDate, CalDateTime? periodStart) { var exDates = Recurrable.ExceptionDates.GetAllPeriodsByKind(PeriodKind.DateOnly, PeriodKind.DateTime) .AsEnumerable(); if (periodStart != null) exDates = exDates.Where(p => p.StartTime.GreaterThanOrEqual(periodStart)); - if (periodEnd != null) - exDates = exDates.Where(p => p.StartTime.LessThan(periodEnd)); return new SortedSet(exDates); } - public override IEnumerable Evaluate(CalDateTime referenceDate, CalDateTime? periodStart, CalDateTime? periodEnd, EvaluationOptions? options) + public override IEnumerable Evaluate(CalDateTime referenceDate, CalDateTime? periodStart, EvaluationOptions? options) { IEnumerable rruleOccurrences; @@ -115,12 +108,12 @@ public override IEnumerable Evaluate(CalDateTime referenceDate, CalDateT if ((Recurrable.RecurrenceRules == null) || !Recurrable.RecurrenceRules.Any()) rruleOccurrences = [new Period(referenceDate)]; else - rruleOccurrences = EvaluateRRule(referenceDate, periodStart, periodEnd, options); + rruleOccurrences = EvaluateRRule(referenceDate, periodStart, options); - var rdateOccurrences = EvaluateRDate(referenceDate, periodStart, periodEnd); + var rdateOccurrences = EvaluateRDate(referenceDate, periodStart); - var exRuleExclusions = EvaluateExRule(referenceDate, periodStart, periodEnd, options); - var exDateExclusions = EvaluateExDate(referenceDate, periodStart, periodEnd); + var exRuleExclusions = EvaluateExRule(referenceDate, periodStart, options); + var exDateExclusions = EvaluateExDate(referenceDate, periodStart); var periods = rruleOccurrences diff --git a/Ical.Net/Evaluation/TimeZoneInfoEvaluator.cs b/Ical.Net/Evaluation/TimeZoneInfoEvaluator.cs index 695c8e51d..60c2fde1e 100644 --- a/Ical.Net/Evaluation/TimeZoneInfoEvaluator.cs +++ b/Ical.Net/Evaluation/TimeZoneInfoEvaluator.cs @@ -20,11 +20,4 @@ protected VTimeZoneInfo TimeZoneInfo } public TimeZoneInfoEvaluator(IRecurrable tzi) : base(tzi) { } - - public override IEnumerable Evaluate(CalDateTime referenceDate, CalDateTime? periodStart, CalDateTime? periodEnd, EvaluationOptions? options) - { - // Always include the reference date in the results - var periods = base.Evaluate(referenceDate, periodStart, periodEnd, options); - return periods; - } } diff --git a/Ical.Net/Evaluation/TodoEvaluator.cs b/Ical.Net/Evaluation/TodoEvaluator.cs index f332cf924..5233b973f 100644 --- a/Ical.Net/Evaluation/TodoEvaluator.cs +++ b/Ical.Net/Evaluation/TodoEvaluator.cs @@ -46,7 +46,8 @@ internal IEnumerable EvaluateToPreviousOccurrence(CalDateTime completedD throw new InvalidOperationException("Todo.Start must not be null."); } - return Evaluate(Todo.Start, beginningDate, currDt.AddSeconds(1), options); + return Evaluate(Todo.Start, beginningDate, options) + .Where(p => p.StartTime <= currDt); } private static void DetermineStartingRecurrence(IEnumerable rdate, ref CalDateTime referenceDateTime) @@ -82,13 +83,12 @@ private void DetermineStartingRecurrence(RecurrencePattern recur, ref CalDateTim } } - public override IEnumerable Evaluate(CalDateTime referenceDate, CalDateTime? periodStart, CalDateTime? periodEnd, EvaluationOptions? options) + public override IEnumerable Evaluate(CalDateTime referenceDate, CalDateTime? periodStart, EvaluationOptions? options) { // Items can only recur if a start date is specified if (Todo.Start == null) return []; - return base.Evaluate(referenceDate, periodStart, periodEnd, options) - .Select(p => p); + return base.Evaluate(referenceDate, periodStart, options); } } diff --git a/Ical.Net/IGetOccurrences.cs b/Ical.Net/IGetOccurrences.cs index d60038ed1..84f130c4a 100644 --- a/Ical.Net/IGetOccurrences.cs +++ b/Ical.Net/IGetOccurrences.cs @@ -15,25 +15,23 @@ public interface IGetOccurrences { /// /// Returns all occurrences of this component that overlap with the date range provided. - /// All components that overlap with the time range between and will be returned. + /// All components that start at or after or end after will be returned. /// /// The starting date range - /// The ending date range /// /// An IEnumerable that calculates and returns Periods representing the occurrences of this object in ascending order. - IEnumerable GetOccurrences(CalDateTime? startTime = null, CalDateTime? endTime = null, EvaluationOptions? options = null); + IEnumerable GetOccurrences(CalDateTime? startTime = null, EvaluationOptions? options = null); } public interface IGetOccurrencesTyped : IGetOccurrences { /// /// Returns all occurrences of components of type T that start within the date range provided. - /// All components occurring between and + /// All components occurring at or after /// will be returned. /// /// The starting date range. If set to null, occurrences are returned from the beginning. - /// The ending date range. If set to null, occurrences are returned until the end. /// /// An IEnumerable that calculates and returns Periods representing the occurrences of this object in ascending order. - IEnumerable GetOccurrences(CalDateTime? startTime = null, CalDateTime? endTime = null, EvaluationOptions? options = null) where T : IRecurringComponent; + IEnumerable GetOccurrences(CalDateTime? startTime = null, EvaluationOptions? options = null) where T : IRecurringComponent; } diff --git a/Ical.Net/VTimeZoneInfo.cs b/Ical.Net/VTimeZoneInfo.cs index 8ba6026a4..d04424309 100644 --- a/Ical.Net/VTimeZoneInfo.cs +++ b/Ical.Net/VTimeZoneInfo.cs @@ -167,6 +167,6 @@ public virtual CalDateTime? RecurrenceId public IEvaluator? Evaluator => _evaluator; - public virtual IEnumerable GetOccurrences(CalDateTime? startTime = null, CalDateTime? endTime = null, EvaluationOptions? options = null) - => RecurrenceUtil.GetOccurrences(this, startTime, endTime, options); + public virtual IEnumerable GetOccurrences(CalDateTime? startTime = null, EvaluationOptions? options = null) + => RecurrenceUtil.GetOccurrences(this, startTime, options); } From bb6566063020dfd28eed19a7b79cdb0876a6fe7b Mon Sep 17 00:00:00 2001 From: Markus Minichmayr Date: Thu, 1 May 2025 21:48:50 +0200 Subject: [PATCH 3/5] Update tests to deal with removed `periodEnd` param of `GetOccurrences()` and `Evaluate()` --- Ical.Net.Tests/DeserializationTests.cs | 8 +- Ical.Net.Tests/DocumentationExamples.cs | 8 +- Ical.Net.Tests/GetOccurrenceTests.cs | 16 +-- Ical.Net.Tests/MatchTimeZoneTests.cs | 12 +- Ical.Net.Tests/ProgramTest.cs | 8 +- Ical.Net.Tests/RecurrenceTests.cs | 119 +++++++++--------- Ical.Net.Tests/RecurrenceTests_From_Issues.cs | 22 ++-- Ical.Net.Tests/SimpleDeserializationTests.cs | 8 +- Ical.Net.Tests/TestHelpers.cs | 21 ++++ Ical.Net.Tests/TodoTest.cs | 4 +- 10 files changed, 125 insertions(+), 101 deletions(-) create mode 100644 Ical.Net.Tests/TestHelpers.cs diff --git a/Ical.Net.Tests/DeserializationTests.cs b/Ical.Net.Tests/DeserializationTests.cs index 47f787c8b..ffacc7a52 100644 --- a/Ical.Net.Tests/DeserializationTests.cs +++ b/Ical.Net.Tests/DeserializationTests.cs @@ -123,7 +123,7 @@ public void Bug2938007() Assert.That(evt.End.HasTime, Is.EqualTo(true)); }); - foreach (var o in evt.GetOccurrences(new CalDateTime(2010, 1, 17, 0, 0, 0), new CalDateTime(2010, 2, 1, 0, 0, 0))) + foreach (var o in evt.GetOccurrences(new CalDateTime(2010, 1, 17, 0, 0, 0)).TakeUntil(new CalDateTime(2010, 2, 1, 0, 0, 0))) { Assert.Multiple(() => { @@ -328,7 +328,7 @@ public void Google1() CalDateTime dtStart = new CalDateTime(2006, 12, 18); CalDateTime dtEnd = new CalDateTime(2006, 12, 23); - var occurrences = iCal.GetOccurrences(dtStart, dtEnd).OrderBy(o => o.Period.StartTime).ToList(); + var occurrences = iCal.GetOccurrences(dtStart).TakeUntil(dtEnd).OrderBy(o => o.Period.StartTime).ToList(); var dateTimes = new[] { @@ -495,7 +495,7 @@ public void Language4() public void Outlook2007_LineFolds1() { var iCal = Calendar.Load(IcsFiles.Outlook2007LineFolds); - var events = iCal.GetOccurrences(new CalDateTime(2009, 06, 20), new CalDateTime(2009, 06, 22)).ToList(); + var events = iCal.GetOccurrences(new CalDateTime(2009, 06, 20)).TakeUntil(new CalDateTime(2009, 06, 22)).ToList(); Assert.That(events, Has.Count.EqualTo(1)); } @@ -504,7 +504,7 @@ public void Outlook2007_LineFolds2() { var longName = "The Exceptionally Long Named Meeting Room Whose Name Wraps Over Several Lines When Exported From Leading Calendar and Office Software Application Microsoft Office 2007"; var iCal = Calendar.Load(IcsFiles.Outlook2007LineFolds); - var events = iCal.GetOccurrences(new CalDateTime(2009, 06, 20), new CalDateTime(2009, 06, 22)).OrderBy(o => o.Period.StartTime).ToList(); + var events = iCal.GetOccurrences(new CalDateTime(2009, 06, 20)).TakeUntil(new CalDateTime(2009, 06, 22)).OrderBy(o => o.Period.StartTime).ToList(); Assert.That(((CalendarEvent)events[0].Source).Location, Is.EqualTo(longName)); } diff --git a/Ical.Net.Tests/DocumentationExamples.cs b/Ical.Net.Tests/DocumentationExamples.cs index 7b0925000..342a67294 100644 --- a/Ical.Net.Tests/DocumentationExamples.cs +++ b/Ical.Net.Tests/DocumentationExamples.cs @@ -40,7 +40,7 @@ public void Daily_Test() // July 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 var searchStart = new CalDateTime(2016, 07, 20); var searchEnd = new CalDateTime(2016, 08, 05); - var occurrences = calendar.GetOccurrences(searchStart, searchEnd).ToList(); + var occurrences = calendar.GetOccurrences(searchStart).TakeUntil(searchEnd).ToList(); Assert.That(occurrences, Has.Count.EqualTo(12)); } @@ -65,7 +65,7 @@ public void EveryOtherTuesdayUntilTheEndOfTheYear_Test() // The first Tuesday is July 5. There should be 13 in total var searchStart = new CalDateTime(2010, 01, 01); var searchEnd = new CalDateTime(2016, 12, 31); - var tuesdays = vEvent.GetOccurrences(searchStart, searchEnd).ToList(); + var tuesdays = vEvent.GetOccurrences(searchStart).TakeUntil(searchEnd).ToList(); Assert.That(tuesdays, Has.Count.EqualTo(13)); } @@ -93,7 +93,7 @@ public void FourthThursdayOfNovember_Tests() var searchStart = new CalDateTime(2000, 01, 01); var searchEnd = new CalDateTime(2017, 01, 01); - var usThanksgivings = vEvent.GetOccurrences(searchStart, searchEnd).ToList(); + var usThanksgivings = vEvent.GetOccurrences(searchStart).TakeUntil(searchEnd).ToList(); Assert.That(usThanksgivings, Has.Count.EqualTo(17)); foreach (var thanksgiving in usThanksgivings) @@ -126,7 +126,7 @@ public void DailyExceptSunday_Test() // We are essentially counting all the days that aren't Sunday in 2016, so there should be 314 var searchStart = new CalDateTime(2015, 12, 31); var searchEnd = new CalDateTime(2017, 01, 01); - var occurrences = calendar.GetOccurrences(searchStart, searchEnd).ToList(); + var occurrences = calendar.GetOccurrences(searchStart).TakeUntil(searchEnd).ToList(); Assert.That(occurrences, Has.Count.EqualTo(314)); } } diff --git a/Ical.Net.Tests/GetOccurrenceTests.cs b/Ical.Net.Tests/GetOccurrenceTests.cs index 03f42f5a1..72adbfacd 100644 --- a/Ical.Net.Tests/GetOccurrenceTests.cs +++ b/Ical.Net.Tests/GetOccurrenceTests.cs @@ -34,7 +34,7 @@ public void WrongDurationTest() var searchStart = new CalDateTime(2015, 12, 29); var searchEnd = new CalDateTime(2017, 02, 10); - var occurrences = calendar.GetOccurrences(searchStart, searchEnd).OrderBy(o => o.Period.StartTime).ToList(); + var occurrences = calendar.GetOccurrences(searchStart).TakeUntil(searchEnd).OrderBy(o => o.Period.StartTime).ToList(); var firstOccurrence = occurrences.First(); var firstStartCopy = firstStart.Copy(); @@ -81,8 +81,8 @@ public void SkippedOccurrenceOnWeeklyPattern() var occurrences = RecurrenceUtil.GetOccurrences( recurrable: vEvent, - periodStart: intervalStart, - periodEnd: intervalEnd); + periodStart: intervalStart) + .TakeUntil(intervalEnd); var occurrenceSet = new HashSet(occurrences.Select(o => o.Period.StartTime)); Assert.That(occurrenceSet, Has.Count.EqualTo(evaluationsCount)); @@ -144,7 +144,7 @@ public void EnumerationChangedException() var calendar = GetCalendars(ical); var date = new CalDateTime(2016, 10, 11); - var occurrences = calendar.GetOccurrences(date, date.AddDays(1)).ToList(); + var occurrences = calendar.GetOccurrences(date).TakeUntil(date.AddDays(1)).ToList(); //We really want to make sure this doesn't explode Assert.That(occurrences, Has.Count.EqualTo(1)); @@ -208,7 +208,7 @@ public void GetOccurrencesWithRecurrenceIdShouldEnumerate() var collection = Calendar.Load(ical); var startCheck = new CalDateTime(2016, 11, 11); - var occurrences = collection.GetOccurrences(startCheck, startCheck.AddMonths(1)).ToList(); + var occurrences = collection.GetOccurrences(startCheck).TakeUntil(startCheck.AddMonths(1)).ToList(); CalDateTime[] expectedStartDates = [ new CalDateTime("20161114T000100", "W. Europe Standard Time"), @@ -223,7 +223,7 @@ public void GetOccurrencesWithRecurrenceIdShouldEnumerate() // Specify end time that is between the original occurrence at 20161128T0001 and the overridden one at 20161128T0030. // The overridden one shouldn't be returned, because it was replaced and the other one is in the future. - var occurrences2 = collection.GetOccurrences(new CalDateTime(startCheck), new CalDateTime("20161128T002000", "W. Europe Standard Time")) + var occurrences2 = collection.GetOccurrences(new CalDateTime(startCheck)).TakeUntil(new CalDateTime("20161128T002000", "W. Europe Standard Time")) .ToList(); Assert.Multiple(() => @@ -262,10 +262,10 @@ public void GetOccurrencesWithRecurrenceId_DateOnly_ShouldEnumerate() var collection = Calendar.Load(ical); var startCheck = new CalDateTime(2023, 10, 1); - var occurrences = collection.GetOccurrences(startCheck, startCheck.AddMonths(1)) + var occurrences = collection.GetOccurrences(startCheck).TakeUntil(startCheck.AddMonths(1)) .ToList(); - var occurrences2 = collection.GetOccurrences(new CalDateTime(startCheck), new CalDateTime(2023, 12, 31)) + var occurrences2 = collection.GetOccurrences(new CalDateTime(startCheck)).TakeUntil(new CalDateTime(2023, 12, 31)) .ToList(); CalDateTime[] expectedStartDates = [ diff --git a/Ical.Net.Tests/MatchTimeZoneTests.cs b/Ical.Net.Tests/MatchTimeZoneTests.cs index 02630d3cf..8a792de74 100644 --- a/Ical.Net.Tests/MatchTimeZoneTests.cs +++ b/Ical.Net.Tests/MatchTimeZoneTests.cs @@ -38,7 +38,7 @@ public void MatchTimeZone_LocalTimeUsaWithTimeZone() var until = evt.RecurrenceRules.First().Until; var expectedUntil = new CalDateTime(2023, 11, 05, 13, 00, 00, CalDateTime.UtcTzId); - var occurrences = evt.GetOccurrences(new CalDateTime(2023, 11, 01), new CalDateTime(2023, 11, 06)); + var occurrences = evt.GetOccurrences(new CalDateTime(2023, 11, 01)).TakeUntil(new CalDateTime(2023, 11, 06)); Assert.Multiple(() => { @@ -88,7 +88,7 @@ public void MatchTimeZone_LocalTimeAustraliaWithTimeZone(string inputUntil, int System.Globalization.DateTimeStyles.AssumeUniversal | System.Globalization.DateTimeStyles.AdjustToUniversal).AsCalDateTime(); - var occurrences = evt.GetOccurrences(new CalDateTime(2024, 10, 01), new CalDateTime(2024, 10, 07)); + var occurrences = evt.GetOccurrences(new CalDateTime(2024, 10, 01)).TakeUntil(new CalDateTime(2024, 10, 07)); Assert.Multiple(() => { @@ -133,7 +133,7 @@ public void MatchTimeZone_UTCTime() var until = evt.RecurrenceRules.First().Until; var expectedUntil = new CalDateTime(2023, 11, 05, 09, 00, 00, CalDateTime.UtcTzId); - var occurrences = evt.GetOccurrences(new CalDateTime(2023, 11, 01), new CalDateTime(2023, 11, 06)); + var occurrences = evt.GetOccurrences(new CalDateTime(2023, 11, 01)).TakeUntil(new CalDateTime(2023, 11, 06)); Assert.Multiple(() => { @@ -166,7 +166,7 @@ public void MatchTimeZone_FloatingTime() var until = evt.RecurrenceRules.First().Until; var expectedUntil = new CalDateTime(2023, 11, 05, 09, 00, 00, null); - var occurrences = evt.GetOccurrences(new CalDateTime(2023, 11, 01), new CalDateTime(2023, 11, 06)); + var occurrences = evt.GetOccurrences(new CalDateTime(2023, 11, 01)).TakeUntil(new CalDateTime(2023, 11, 06)); Assert.Multiple(() => { @@ -200,7 +200,7 @@ public void MatchTimeZone_LocalTimeNoTimeZone() var until = evt.RecurrenceRules.First().Until; var expectedUntil = new CalDateTime(2023, 11, 05, 09, 00, 00, null); - var occurrences = evt.GetOccurrences(new CalDateTime(2023, 11, 01), new CalDateTime(2023, 11, 06)); + var occurrences = evt.GetOccurrences(new CalDateTime(2023, 11, 01)).TakeUntil(new CalDateTime(2023, 11, 06)); Assert.Multiple(() => { @@ -233,7 +233,7 @@ public void MatchTimeZone_DateOnly() var until = evt.RecurrenceRules.First().Until; var expectedUntil = new CalDateTime(2023, 11, 05); - var occurrences = evt.GetOccurrences(new CalDateTime(2023, 11, 01), new CalDateTime(2023, 11, 06)); + var occurrences = evt.GetOccurrences(new CalDateTime(2023, 11, 01)).TakeUntil(new CalDateTime(2023, 11, 06)); Assert.Multiple(() => { diff --git a/Ical.Net.Tests/ProgramTest.cs b/Ical.Net.Tests/ProgramTest.cs index 4cac77a9e..c5797d019 100644 --- a/Ical.Net.Tests/ProgramTest.cs +++ b/Ical.Net.Tests/ProgramTest.cs @@ -52,8 +52,8 @@ public void Merge1() // Get occurrences for the first event var occurrences = evt1.GetOccurrences( - new CalDateTime(1996, 1, 1), - new CalDateTime(2000, 1, 1)).OrderBy(o => o.Period.StartTime).ToList(); + new CalDateTime(1996, 1, 1)) + .TakeUntil(new CalDateTime(2000, 1, 1)).OrderBy(o => o.Period.StartTime).ToList(); var dateTimes = new[] { @@ -104,8 +104,8 @@ public void Merge1() // Get occurrences for the 2nd event occurrences = evt2.GetOccurrences( - new CalDateTime(1996, 1, 1), - new CalDateTime(1998, 4, 1)).OrderBy(o => o.Period.StartTime).ToList(); + new CalDateTime(1996, 1, 1)) + .TakeUntil(new CalDateTime(1998, 4, 1)).OrderBy(o => o.Period.StartTime).ToList(); var dateTimes1 = new[] { diff --git a/Ical.Net.Tests/RecurrenceTests.cs b/Ical.Net.Tests/RecurrenceTests.cs index 10c5ce257..d0591bd31 100644 --- a/Ical.Net.Tests/RecurrenceTests.cs +++ b/Ical.Net.Tests/RecurrenceTests.cs @@ -37,7 +37,7 @@ int eventIndex var evt = cal.Events.Skip(eventIndex).First(); var rule = evt.RecurrenceRules.FirstOrDefault(); - var occurrences = evt.GetOccurrences(fromDate, toDate) + var occurrences = evt.GetOccurrences(fromDate).TakeUntil(toDate) .OrderBy(o => o.Period.StartTime) .ToList(); @@ -132,8 +132,8 @@ public void YearlyComplex1() ProgramTest.TestCal(iCal); var evt = iCal.Events.First(); var occurrences = evt.GetOccurrences( - new CalDateTime(2006, 1, 1), - new CalDateTime(2011, 1, 1)).OrderBy(o => o.Period.StartTime).ToList(); + new CalDateTime(2006, 1, 1)) + .TakeUntil(new CalDateTime(2011, 1, 1)).OrderBy(o => o.Period.StartTime).ToList(); var dt = new CalDateTime(2007, 1, 1, 8, 30, 0, _tzid); var i = 0; @@ -196,8 +196,8 @@ public void DailyUntil1() var evt = iCal.Events.First(); var occurrences = evt.GetOccurrences( - new CalDateTime(1997, 9, 1), - new CalDateTime(1998, 1, 1)).OrderBy(o => o.Period.StartTime).ToList(); + new CalDateTime(1997, 9, 1)) + .TakeUntil(new CalDateTime(1998, 1, 1)).OrderBy(o => o.Period.StartTime).ToList(); var dt = new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid); var i = 0; @@ -368,8 +368,8 @@ public void ByMonth1() var evt = iCal.Events.First(); var occurrences = evt.GetOccurrences( - new CalDateTime(1998, 1, 1), - new CalDateTime(2000, 12, 31)).OrderBy(o => o.Period.StartTime).ToList(); + new CalDateTime(1998, 1, 1)) + .TakeUntil(new CalDateTime(2000, 12, 31)).OrderBy(o => o.Period.StartTime).ToList(); var dt = new CalDateTime(1998, 1, 1, 9, 0, 0, _tzid); var i = 0; @@ -405,8 +405,8 @@ public void ByMonth2() var evt1 = iCal1.Events.First(); var evt2 = iCal2.Events.First(); - var evt1Occurrences = evt1.GetOccurrences(new CalDateTime(1997, 9, 1), new CalDateTime(2000, 12, 31)).OrderBy(o => o.Period.StartTime).ToList(); - var evt2Occurrences = evt2.GetOccurrences(new CalDateTime(1997, 9, 1), new CalDateTime(2000, 12, 31)).OrderBy(o => o.Period.StartTime).ToList(); + var evt1Occurrences = evt1.GetOccurrences(new CalDateTime(1997, 9, 1)).TakeUntil(new CalDateTime(2000, 12, 31)).OrderBy(o => o.Period.StartTime).ToList(); + var evt2Occurrences = evt2.GetOccurrences(new CalDateTime(1997, 9, 1)).TakeUntil(new CalDateTime(2000, 12, 31)).OrderBy(o => o.Period.StartTime).ToList(); Assert.That(evt1Occurrences.Count == evt2Occurrences.Count, Is.True, "ByMonth1 does not match ByMonth2 as it should"); for (var i = 0; i < evt1Occurrences.Count; i++) Assert.That(evt2Occurrences[i].Period, Is.EqualTo(evt1Occurrences[i].Period), "PERIOD " + i + " from ByMonth1 (" + evt1Occurrences[i] + ") does not match PERIOD " + i + " from ByMonth2 (" + evt2Occurrences[i] + ")"); @@ -587,8 +587,8 @@ public void WeeklyCountWkst1() var evt1 = iCal1.Events.First(); var evt2 = iCal2.Events.First(); - var evt1Occ = evt1.GetOccurrences(new CalDateTime(1997, 9, 1), new CalDateTime(1999, 1, 1)).OrderBy(o => o.Period.StartTime).ToList(); - var evt2Occ = evt2.GetOccurrences(new CalDateTime(1997, 9, 1), new CalDateTime(1999, 1, 1)).OrderBy(o => o.Period.StartTime).ToList(); + var evt1Occ = evt1.GetOccurrences(new CalDateTime(1997, 9, 1)).TakeUntil(new CalDateTime(1999, 1, 1)).OrderBy(o => o.Period.StartTime).ToList(); + var evt2Occ = evt2.GetOccurrences(new CalDateTime(1997, 9, 1)).TakeUntil(new CalDateTime(1999, 1, 1)).OrderBy(o => o.Period.StartTime).ToList(); Assert.That(evt2Occ, Has.Count.EqualTo(evt1Occ.Count), "WeeklyCountWkst1() does not match WeeklyUntilWkst1() as it should"); for (var i = 0; i < evt1Occ.Count; i++) { @@ -1232,8 +1232,8 @@ public void WeekNoOrderingShouldNotMatter() var rpe1 = new RecurrencePatternEvaluator(new RecurrencePattern("FREQ=YEARLY;WKST=MO;BYDAY=MO;BYWEEKNO=1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49,51,53")); var rpe2 = new RecurrencePatternEvaluator(new RecurrencePattern("FREQ=YEARLY;WKST=MO;BYDAY=MO;BYWEEKNO=53,51,49,47,45,43,41,39,37,35,33,31,29,27,25,23,21,19,17,15,13,11,9,7,5,3,1")); - var recurringPeriods1 = rpe1.Evaluate(new CalDateTime(start), start, end, null).ToList(); - var recurringPeriods2 = rpe2.Evaluate(new CalDateTime(start), start, end, null).ToList(); + var recurringPeriods1 = rpe1.Evaluate(new CalDateTime(start), start, null).TakeUntil(end).ToList(); + var recurringPeriods2 = rpe2.Evaluate(new CalDateTime(start), start, null).TakeUntil(end).ToList(); Assert.That(recurringPeriods2, Has.Count.EqualTo(recurringPeriods1.Count)); } @@ -1831,8 +1831,8 @@ public void MinutelyByHour1() var evt1 = iCal1.Events.First(); var evt2 = iCal2.Events.First(); - var evt1Occ = evt1.GetOccurrences(new CalDateTime(1997, 9, 1), new CalDateTime(1997, 9, 3)).OrderBy(o => o.Period.StartTime).ToList(); - var evt2Occ = evt2.GetOccurrences(new CalDateTime(1997, 9, 1), new CalDateTime(1997, 9, 3)).OrderBy(o => o.Period.StartTime).ToList(); + var evt1Occ = evt1.GetOccurrences(new CalDateTime(1997, 9, 1)).TakeUntil(new CalDateTime(1997, 9, 3)).OrderBy(o => o.Period.StartTime).ToList(); + var evt2Occ = evt2.GetOccurrences(new CalDateTime(1997, 9, 1)).TakeUntil(new CalDateTime(1997, 9, 3)).OrderBy(o => o.Period.StartTime).ToList(); Assert.That(evt1Occ.Count == evt2Occ.Count, Is.True, "MinutelyByHour1() does not match DailyByHourMinute1() as it should"); for (var i = 0; i < evt1Occ.Count; i++) Assert.That(evt2Occ[i].Period, Is.EqualTo(evt1Occ[i].Period), "PERIOD " + i + " from DailyByHourMinute1 (" + evt1Occ[i].Period + ") does not match PERIOD " + i + " from MinutelyByHour1 (" + evt2Occ[i].Period + ")"); @@ -2627,7 +2627,7 @@ public void BugByWeekNoNotWorking() var end = new CalDateTime(2019, 12, 31); var rpe = new RecurrencePatternEvaluator(new RecurrencePattern("FREQ=WEEKLY;BYDAY=MO;BYWEEKNO=2")); - var recurringPeriods = rpe.Evaluate(start, start, end, default).ToList(); + var recurringPeriods = rpe.Evaluate(start, start, default).TakeUntil(end).ToList(); Assert.That(recurringPeriods, Has.Count.EqualTo(1)); Assert.That(recurringPeriods.First().StartTime, Is.EqualTo(new CalDateTime(2019, 1, 7))); @@ -2643,7 +2643,7 @@ public void BugByMonthWhileFreqIsWeekly() var end = new CalDateTime(2020, 12, 31); var rpe = new RecurrencePatternEvaluator(new RecurrencePattern("FREQ=WEEKLY;BYDAY=MO;BYMONTH=1")); - var recurringPeriods = rpe.Evaluate(start, start, end, default).OrderBy(x => x).ToList(); + var recurringPeriods = rpe.Evaluate(start, start, default).TakeUntil(end).OrderBy(x => x).ToList(); Assert.That(recurringPeriods, Has.Count.EqualTo(4)); Assert.Multiple(() => @@ -2672,7 +2672,7 @@ public void ReccurencePattern_MaxDate_StopsOnCount() evt.RecurrenceRules.Add(pattern); - var occurrences = evt.GetOccurrences(new CalDateTime(2018, 1, 1), new CalDateTime(DateTime.MaxValue, false)).ToList(); + var occurrences = evt.GetOccurrences(new CalDateTime(2018, 1, 1)).TakeUntil(new CalDateTime(DateTime.MaxValue, false)).ToList(); Assert.That(occurrences, Has.Count.EqualTo(10), "There should be 10 occurrences of this event."); } @@ -2686,7 +2686,7 @@ public void BugByMonthWhileFreqIsMonthly() var end = new CalDateTime(2020, 12, 31); var rpe = new RecurrencePatternEvaluator(new RecurrencePattern("FREQ=MONTHLY;BYDAY=MO;BYMONTH=1")); - var recurringPeriods = rpe.Evaluate(start, start, end, default).OrderBy(x => x).ToList(); + var recurringPeriods = rpe.Evaluate(start, start, default).TakeUntil(end).OrderBy(x => x).ToList(); Assert.That(recurringPeriods, Has.Count.EqualTo(4)); Assert.Multiple(() => @@ -2710,7 +2710,7 @@ public void Bug3119920() var serializer = new RecurrencePatternSerializer(); var rp = (RecurrencePattern)serializer.Deserialize(sr)!; var rpe = new RecurrencePatternEvaluator(rp); - var recurringPeriods = rpe.Evaluate(start, start, rp.Until, default).ToList(); + var recurringPeriods = rpe.Evaluate(start, start, default).TakeUntil(rp.Until).ToList(); var period = recurringPeriods.ElementAt(recurringPeriods.Count - 1); @@ -2741,7 +2741,7 @@ public void Bug3178652() evt.RecurrenceRules.Add(pattern); - var occurrences = evt.GetOccurrences(new CalDateTime(2011, 1, 1), new CalDateTime(2012, 1, 1)).ToList(); + var occurrences = evt.GetOccurrences(new CalDateTime(2011, 1, 1)).TakeUntil(new CalDateTime(2012, 1, 1)).ToList(); Assert.That(occurrences, Has.Count.EqualTo(10), "There should be 10 occurrences of this event, one for each month except February and December."); } @@ -2785,19 +2785,19 @@ public void Issue432() var checkTime = new CalDateTime(2019, 01, 04, 08, 00, 00, CalDateTime.UtcTzId); checkTime = checkTime.AddDays(i); //Valid asking for the exact moment - var occurrences = vEvent.GetOccurrences(checkTime, checkTime).ToList(); + var occurrences = vEvent.GetOccurrences(checkTime).TakeWhile(p => p.Period.StartTime == checkTime).ToList(); Assert.That(occurrences, Has.Count.EqualTo(1)); //Valid if asking for a range starting at the same moment - occurrences = vEvent.GetOccurrences(checkTime, checkTime.AddSeconds(1)).ToList(); + occurrences = vEvent.GetOccurrences(checkTime).TakeUntil(checkTime.AddSeconds(1)).ToList(); Assert.That(occurrences, Has.Count.EqualTo(1)); //Valid if asking for a range starting before and ending after - occurrences = vEvent.GetOccurrences(checkTime.AddSeconds(-1), checkTime.AddSeconds(1)).ToList(); + occurrences = vEvent.GetOccurrences(checkTime.AddSeconds(-1)).TakeUntil(checkTime.AddSeconds(1)).ToList(); Assert.That(occurrences, Has.Count.EqualTo(1)); //Not valid if asking for a range starting before but ending at the same moment - occurrences = vEvent.GetOccurrences(checkTime.AddSeconds(-1), checkTime).ToList(); + occurrences = vEvent.GetOccurrences(checkTime.AddSeconds(-1)).TakeUntil(checkTime).ToList(); Assert.That(occurrences.Count, Is.EqualTo(0)); } } @@ -2811,7 +2811,7 @@ public void Issue432_AllDay() End = new CalDateTime(DateTime.Parse("2020-01-11T00:00")), }; - var occurrences = vEvent.GetOccurrences(new CalDateTime(2020,01, 10, 0, 0, 0), new CalDateTime(2020, 01, 11, 00, 00, 00)); + var occurrences = vEvent.GetOccurrences(new CalDateTime(2020,01, 10, 0, 0, 0)).TakeUntil(new CalDateTime(2020, 01, 11, 00, 00, 00)); Assert.That(occurrences.Count, Is.EqualTo(0)); } @@ -2855,8 +2855,8 @@ public void UsHolidays() }; var occurrences = iCal.GetOccurrences( - new CalDateTime(2006, 1, 1), - new CalDateTime(2006, 12, 31)) + new CalDateTime(2006, 1, 1)) + .TakeUntil(new CalDateTime(2006, 12, 31)) .ToList(); Assert.That(occurrences, Has.Count.EqualTo(items.Count), "The number of holidays did not evaluate correctly."); @@ -2896,7 +2896,7 @@ public void Evaluate1(string freq, int secsPerInterval, bool hasTime) // ical.net handles the case by pretending DTSTART has the time set to midnight. evt.RecurrenceRules.Add(new RecurrencePattern($"FREQ={freq};INTERVAL=10;COUNT=5")); - var occurrences = evt.GetOccurrences(evt.Start.AddDays(-1), evt.Start.AddDays(100)) + var occurrences = evt.GetOccurrences(evt.Start.AddDays(-1)).TakeUntil(evt.Start.AddDays(100)) .OrderBy(x => x) .ToList(); @@ -2930,7 +2930,8 @@ public void RecurrencePattern1() var occurrences = evaluator.Evaluate( startDate, fromDate, - toDate, default) + default) + .TakeUntil(toDate) .OrderBy(o => o.StartTime) .ToList(); Assert.That(occurrences, Has.Count.EqualTo(4)); @@ -2961,7 +2962,8 @@ public void RecurrencePattern2() var occurrences = evaluator.Evaluate( startDate, fromDate, - toDate, default); + default) + .TakeUntil(toDate); Assert.That(occurrences.Count, Is.Not.EqualTo(0)); } @@ -2981,32 +2983,32 @@ public void GetOccurrences1() var laterDateAndTime = new CalDateTime(2009, 11, 19, 11, 0, 0); var end = new CalDateTime(2009, 11, 23, 0, 0, 0); - var occurrences = evt.GetOccurrences(previousDateAndTime, end).ToList(); + var occurrences = evt.GetOccurrences(previousDateAndTime).TakeUntil(end).ToList(); Assert.That(occurrences, Has.Count.EqualTo(5)); - occurrences = evt.GetOccurrences(previousDateOnly, end).ToList(); + occurrences = evt.GetOccurrences(previousDateOnly).TakeUntil(end).ToList(); Assert.That(occurrences, Has.Count.EqualTo(5)); - occurrences = evt.GetOccurrences(laterDateOnly, end).ToList(); + occurrences = evt.GetOccurrences(laterDateOnly).TakeUntil(end).ToList(); Assert.That(occurrences, Has.Count.EqualTo(4)); - occurrences = evt.GetOccurrences(laterDateAndTime, end).ToList(); + occurrences = evt.GetOccurrences(laterDateAndTime).TakeUntil(end).ToList(); Assert.That(occurrences, Has.Count.EqualTo(3)); // Add ByHour "9" and "12" evt.RecurrenceRules[0].ByHour.Add(9); evt.RecurrenceRules[0].ByHour.Add(12); - occurrences = evt.GetOccurrences(previousDateAndTime, end).ToList(); + occurrences = evt.GetOccurrences(previousDateAndTime).TakeUntil(end).ToList(); Assert.That(occurrences, Has.Count.EqualTo(10)); - occurrences = evt.GetOccurrences(previousDateOnly, end).ToList(); + occurrences = evt.GetOccurrences(previousDateOnly).TakeUntil(end).ToList(); Assert.That(occurrences, Has.Count.EqualTo(10)); - occurrences = evt.GetOccurrences(laterDateOnly, end).ToList(); + occurrences = evt.GetOccurrences(laterDateOnly).TakeUntil(end).ToList(); Assert.That(occurrences, Has.Count.EqualTo(8)); - occurrences = evt.GetOccurrences(laterDateAndTime, end).ToList(); + occurrences = evt.GetOccurrences(laterDateAndTime).TakeUntil(end).ToList(); Assert.That(occurrences, Has.Count.EqualTo(7)); } @@ -3023,7 +3025,7 @@ public void Test1() Assert.That(() => { - _ = evt.GetOccurrences(CalDateTime.Today.AddDays(1), CalDateTime.Today.AddDays(2)); + _ = evt.GetOccurrences(CalDateTime.Today.AddDays(1)).TakeUntil(CalDateTime.Today.AddDays(2)); }, Throws.Exception, "An exception should be thrown when evaluating a recurrence with no specified FREQUENCY"); } @@ -3067,7 +3069,8 @@ public void Test4() var periods = evaluator.Evaluate( evtStart, evtStart, - evtEnd, default) + default) + .TakeUntil(evtEnd) .OrderBy(p => p.StartTime) .ToList(); Assert.That(periods, Has.Count.EqualTo(10)); @@ -3109,7 +3112,7 @@ public void ExDateShouldFilterOutAllPeriods() var startSearch = new CalDateTime(2010, 1, 1); var endSearch = new CalDateTime(2016, 12, 31); - var occurrences = firstEvent.GetOccurrences(startSearch, endSearch).Select(o => o.Period).ToList(); + var occurrences = firstEvent.GetOccurrences(startSearch).TakeUntil(endSearch).Select(o => o.Period).ToList(); Assert.That(occurrences.Count == 0, Is.True); } @@ -3138,7 +3141,7 @@ public void RDateShouldBeUnionedWithRecurrenceSet() var startSearch = new CalDateTime(DateTime.Parse("2015-08-28T07:00:00"), _tzid); var endSearch = new CalDateTime(DateTime.Parse("2016-08-28T07:00:00").AddDays(7), _tzid); - var occurrences = firstEvent.GetOccurrences(startSearch, endSearch) + var occurrences = firstEvent.GetOccurrences(startSearch).TakeUntil(endSearch) .Select(o => o.Period) .OrderBy(p => p.StartTime) .ToList(); @@ -3199,7 +3202,7 @@ public void OccurrenceMustBeCompletelyContainedWithinSearchRange() var endSearch = new CalDateTime(DateTime.Parse("2016-08-31T07:00:00"), "UTC"); var lastExpected = new CalDateTime(DateTime.Parse("2016-08-31T07:00:00"), "UTC"); - var occurrences = firstEvent.GetOccurrences(startSearch, endSearch) + var occurrences = firstEvent.GetOccurrences(startSearch).TakeUntil(endSearch) .Select(o => o.Period) .OrderBy(p => p.StartTime) .ToList(); @@ -3208,7 +3211,7 @@ public void OccurrenceMustBeCompletelyContainedWithinSearchRange() //Create 1 second of overlap endSearch = new CalDateTime(endSearch.Value.AddSeconds(1), "UTC"); - occurrences = firstEvent.GetOccurrences(startSearch, endSearch) + occurrences = firstEvent.GetOccurrences(startSearch).TakeUntil(endSearch) .Select(o => o.Period) .OrderBy(p => p.StartTime) .ToList(); @@ -3307,18 +3310,18 @@ public void AddExDateToEventAfterGetOccurrencesShouldRecomputeResult() var searchStart = _now.AddDays(-1); var searchEnd = _now.AddDays(7); var e = GetEventWithRecurrenceRules(); - var occurrences = e.GetOccurrences(searchStart, searchEnd).ToList(); + var occurrences = e.GetOccurrences(searchStart).TakeUntil(searchEnd).ToList(); Assert.That(occurrences, Has.Count.EqualTo(5)); var exDate = _now.AddDays(1); e.ExceptionDates.Add(exDate); - occurrences = e.GetOccurrences(searchStart, searchEnd).ToList(); + occurrences = e.GetOccurrences(searchStart).TakeUntil(searchEnd).ToList(); Assert.That(occurrences, Has.Count.EqualTo(4)); //Specifying just a date should "black out" that date var excludeTwoDaysFromNow = new CalDateTime(_now.Date).AddDays(2); e.ExceptionDates.Add(excludeTwoDaysFromNow); - occurrences = e.GetOccurrences(searchStart, searchEnd).ToList(); + occurrences = e.GetOccurrences(searchStart).TakeUntil(searchEnd).ToList(); Assert.That(occurrences, Has.Count.EqualTo(3)); } @@ -3407,7 +3410,7 @@ public void OneDayRange() checkTime = checkTime.AddDays(i); //Valid if asking for a range starting at the same moment - var occurrences = vEvent.GetOccurrences(checkTime, checkTime.AddDays(1)).ToList(); + var occurrences = vEvent.GetOccurrences(checkTime).TakeUntil(checkTime.AddDays(1)).ToList(); Assert.That(occurrences, Has.Count.EqualTo(i == 0 ? 1 : 0)); } } @@ -3430,19 +3433,19 @@ public void SpecificMinute() // Exactly on start time var testingTime = new CalDateTime(2019, 6, 7, 9, 0, 0); - var occurrences = vEvent.GetOccurrences(testingTime, testingTime).ToList(); + var occurrences = vEvent.GetOccurrences(testingTime).TakeWhile(p => p.Period.StartTime == testingTime).ToList(); Assert.That(occurrences, Has.Count.EqualTo(1)); // One second before end time testingTime = new CalDateTime(2019, 6, 7, 16, 59, 59); - occurrences = vEvent.GetOccurrences(testingTime, testingTime).ToList(); + occurrences = vEvent.GetOccurrences(testingTime).TakeUntil(testingTime).ToList(); Assert.That(occurrences, Has.Count.EqualTo(1)); // Exactly on end time testingTime = new CalDateTime(2019, 6, 7, 17, 0, 0); - occurrences = vEvent.GetOccurrences(testingTime, testingTime).ToList(); + occurrences = vEvent.GetOccurrences(testingTime).TakeUntil(testingTime).ToList(); Assert.That(occurrences.Count, Is.EqualTo(0)); } @@ -3670,7 +3673,7 @@ public void InclusiveRruleUntil() var startSearch = new CalDateTime(DateTime.Parse("2017-07-01T00:00:00"), timeZoneId); var endSearch = new CalDateTime(DateTime.Parse("2018-07-01T00:00:00"), timeZoneId); - var occurrences = firstEvent.GetOccurrences(startSearch, endSearch).ToList(); + var occurrences = firstEvent.GetOccurrences(startSearch).TakeUntil(endSearch).ToList(); Assert.That(occurrences, Has.Count.EqualTo(5)); } @@ -3789,7 +3792,7 @@ public void ExecuteRecurrenceTestCase(RecurrenceTestCase testCase) evt.RecurrenceRules.Add(new RecurrencePattern(testCase.RRule!)); - var occurrences = evt.GetOccurrences(testCase.StartAt ?? new CalDateTime(DateTime.MinValue), new CalDateTime(DateTime.MaxValue)) + var occurrences = evt.GetOccurrences(testCase.StartAt ?? new CalDateTime(DateTime.MinValue)).TakeUntil(new CalDateTime(DateTime.MaxValue)) .OrderBy(x => x) .ToList(); @@ -3816,7 +3819,7 @@ public void ShouldCreateARecurringYearlyEvent() calendar.Events.Add(springAdminEvent); var searchStart = new CalDateTime(2024, 04, 15); var searchEnd = new CalDateTime(2050, 05, 31); - var occurrences = calendar.GetOccurrences(searchStart, searchEnd); + var occurrences = calendar.GetOccurrences(searchStart).TakeUntil(searchEnd); Assert.That(occurrences.Count, Is.EqualTo(27)); springAdminEvent.Start = new CalDateTime(2024, 04, 16); @@ -3825,7 +3828,7 @@ public void ShouldCreateARecurringYearlyEvent() searchStart = new CalDateTime(2024, 04, 16); searchEnd = new CalDateTime(2050, 05, 31); - occurrences = calendar.GetOccurrences(searchStart, searchEnd); + occurrences = calendar.GetOccurrences(searchStart).TakeUntil(searchEnd); // occurrences are 26 here, omitting 4/16/2024 Assert.That(occurrences.Count, Is.EqualTo(27)); @@ -3850,7 +3853,7 @@ public void GetOccurrenceShouldExcludeDtEndFloating() var calendar = Calendar.Load(ical)!; // Set start date for occurrences to search to the end date of the event - var occurrences = calendar.GetOccurrences(new CalDateTime(2024, 12, 2), new CalDateTime(2024, 12, 3)); + var occurrences = calendar.GetOccurrences(new CalDateTime(2024, 12, 2)).TakeUntil(new CalDateTime(2024, 12, 3)); Assert.That(occurrences, Is.Empty); } @@ -3904,7 +3907,7 @@ public void TestDtStartTimezone(string? tzId) var cal = Calendar.Load(icalText)!; var evt = cal.Events.First(); var ev = new EventEvaluator(evt); - var occurrences = ev.Evaluate(evt.DtStart!, evt.DtStart!.ToTimeZone(tzId), evt.DtStart.AddMinutes(61).ToTimeZone(tzId), null); + var occurrences = ev.Evaluate(evt.DtStart!, evt.DtStart!.ToTimeZone(tzId), null).TakeUntil(evt.DtStart.AddMinutes(61).ToTimeZone(tzId)); var occurrencesStartTimes = occurrences.Select(x => x.StartTime).Take(2).ToList(); var expectedStartTimes = new[] diff --git a/Ical.Net.Tests/RecurrenceTests_From_Issues.cs b/Ical.Net.Tests/RecurrenceTests_From_Issues.cs index 774325f93..a66c3c82f 100644 --- a/Ical.Net.Tests/RecurrenceTests_From_Issues.cs +++ b/Ical.Net.Tests/RecurrenceTests_From_Issues.cs @@ -48,7 +48,7 @@ public void GetOccurrence_DtEnd_ShouldBeExcluded() var calendar = Calendar.Load(ical)!; // Event ends on 2024-10-27, at 02:00:00 GMT (when DST ends). The end time is excluded by RFC 5545 definition. - var occurrences = calendar.GetOccurrences(endDate, new CalDateTime("20250101T000000", "Europe/London")).ToList(); + var occurrences = calendar.GetOccurrences(endDate).TakeUntil(new CalDateTime("20250101T000000", "Europe/London")).ToList(); Assert.That(occurrences, Is.Empty); } @@ -75,7 +75,7 @@ public void ClockGoingForwardTest() CalDateTime start = new CalDateTime(2025, 3, 30, 0, 0, 0, tzId); CalDateTime end = new CalDateTime(2025, 3, 31, 0, 0, 0, tzId); - var occurrences = calendar.GetOccurrences(start, end).ToList(); + var occurrences = calendar.GetOccurrences(start).TakeUntil(end).ToList(); var occurrence = occurrences.Single(); // Assert @@ -109,7 +109,7 @@ public void ClockGoingBackTest() CalDateTime start = new CalDateTime(2024, 10, 27, 0, 0, 0, tzId); CalDateTime end = new CalDateTime(2024, 10, 28, 0, 0, 0, tzId); - var occurrences = calendar.GetOccurrences(start, end).ToList(); + var occurrences = calendar.GetOccurrences(start).TakeUntil(end).ToList(); var occurrence = occurrences.Single(); // Assert @@ -145,7 +145,7 @@ public void ClockGoingForwardAllDayTest() CalDateTime start = new CalDateTime(2025, 3, 30, 0, 0, 0, timeZoneId); CalDateTime end = new CalDateTime(2025, 3, 31, 0, 0, 0, timeZoneId); - var occurrences = calendar.GetOccurrences(start, end).ToList(); + var occurrences = calendar.GetOccurrences(start).TakeUntil(end).ToList(); var occurrence = occurrences.Single(); // Assert @@ -184,7 +184,7 @@ public void ClockGoingBackAllDayTest() CalDateTime end = new CalDateTime(2024, 10, 28, 0, 0, 0, timeZoneId); // Duration can't be used at the same time as End. - var occurrences = calendar.GetOccurrences(start, end).ToList(); + var occurrences = calendar.GetOccurrences(start).TakeUntil(end).ToList(); var occurrence = occurrences.Single(); // Assert @@ -221,7 +221,7 @@ public void ClockGoingBackAllDayNonLocalTest() CalDateTime start = new CalDateTime(2024, 10, 27, 0, 0, 0, timeZoneId); CalDateTime end = new CalDateTime(2024, 10, 28, 0, 0, 0, timeZoneId); - var occurrences = calendar.GetOccurrences(start, end).ToList(); + var occurrences = calendar.GetOccurrences(start).TakeUntil(end).ToList(); var occurrence = occurrences.Single(); // Assert @@ -259,7 +259,7 @@ public void ClockGoingForwardAllDayNonLocalTest() CalDateTime start = new CalDateTime(2025, 3, 30, 0, 0, 0, timeZoneId); CalDateTime end = new CalDateTime(2025, 3, 31, 0, 0, 0, timeZoneId); - var occurrences = calendar.GetOccurrences(start, end).ToList(); + var occurrences = calendar.GetOccurrences(start).TakeUntil(end).ToList(); var occurrence = occurrences.Single(); // Assert @@ -304,7 +304,7 @@ public void CheckCalendarZone() var start = new CalDateTime(2024, 12, 9, 8, 5, 0, CalDateTime.UtcTzId); var end = new CalDateTime(2024, 12, 10, 7, 59, 59, CalDateTime.UtcTzId); - var occurrence = calendar.GetOccurrences(start, end); + var occurrence = calendar.GetOccurrences(start).TakeUntil(end); Assert.That(occurrence.Count, Is.EqualTo(1)); } @@ -329,7 +329,7 @@ public void Daylight_Savings_Changes_567() Sequence = 0, RecurrenceRules = new List { pattern } }; - var col = ev.GetOccurrences(calStart, calFinish); + var col = ev.GetOccurrences(calStart).TakeUntil(calFinish); Assert.That(col, Is.Empty); } @@ -359,7 +359,7 @@ private static void CheckDates(DateTime startDate, DateTime endDate, CalDateTime RecurrenceRules = new List { rule } }; - var occurrences = calendarEvent.GetOccurrences(new CalDateTime(startDate), new CalDateTime(endDate)); + var occurrences = calendarEvent.GetOccurrences(new CalDateTime(startDate)).TakeUntil(new CalDateTime(endDate)); var occurrencesDates = occurrences.Select(o => new CalDateTime(o.Period.StartTime.Date)).ToList(); // Sort both collections to ensure they are in the same order @@ -501,7 +501,7 @@ public void Except_Tuesday_Thursday_Saturday_Sunday() var calendar = new Calendar(); calendar.Events.Add(vEvent); - var occurrences = vEvent.GetOccurrences(new CalDateTime(2017, 06, 01, 00, 00, 00), new CalDateTime(2017, 06, 30, 23, 59, 0)).ToList(); + var occurrences = vEvent.GetOccurrences(new CalDateTime(2017, 06, 01, 00, 00, 00)).TakeUntil(new CalDateTime(2017, 06, 30, 23, 59, 0)).ToList(); var excludedDays = new List { DayOfWeek.Sunday, DayOfWeek.Saturday, DayOfWeek.Tuesday, DayOfWeek.Thursday }; Assert.Multiple(() => diff --git a/Ical.Net.Tests/SimpleDeserializationTests.cs b/Ical.Net.Tests/SimpleDeserializationTests.cs index ba85fd59e..3229cbde9 100644 --- a/Ical.Net.Tests/SimpleDeserializationTests.cs +++ b/Ical.Net.Tests/SimpleDeserializationTests.cs @@ -127,7 +127,7 @@ public void Bug2938007() Assert.That(evt.End.HasTime, Is.EqualTo(true)); }); - foreach (var o in evt.GetOccurrences(new CalDateTime(2010, 1, 17, 0, 0, 0), new CalDateTime(2010, 2, 1, 0, 0, 0))) + foreach (var o in evt.GetOccurrences(new CalDateTime(2010, 1, 17, 0, 0, 0)).TakeUntil(new CalDateTime(2010, 2, 1, 0, 0, 0))) { Assert.Multiple(() => { @@ -329,7 +329,7 @@ public void Google1() CalDateTime dtStart = new CalDateTime(2006, 12, 18); CalDateTime dtEnd = new CalDateTime(2006, 12, 23); - var occurrences = iCal.GetOccurrences(dtStart, dtEnd).OrderBy(o => o.Period.StartTime).ToList(); + var occurrences = iCal.GetOccurrences(dtStart).TakeUntil(dtEnd).OrderBy(o => o.Period.StartTime).ToList(); var dateTimes = new[] { @@ -476,7 +476,7 @@ public void Language4() public void Outlook2007_LineFolds1() { var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Outlook2007LineFolds)).Cast().Single(); - var events = iCal.GetOccurrences(new CalDateTime(2009, 06, 20), new CalDateTime(2009, 06, 22)).ToList(); + var events = iCal.GetOccurrences(new CalDateTime(2009, 06, 20)).TakeUntil(new CalDateTime(2009, 06, 22)).ToList(); Assert.That(events, Has.Count.EqualTo(1)); } @@ -485,7 +485,7 @@ public void Outlook2007_LineFolds2() { var longName = "The Exceptionally Long Named Meeting Room Whose Name Wraps Over Several Lines When Exported From Leading Calendar and Office Software Application Microsoft Office 2007"; var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Outlook2007LineFolds)).Cast().Single(); - var events = iCal.GetOccurrences(new CalDateTime(2009, 06, 20), new CalDateTime(2009, 06, 22)).OrderBy(o => o.Period.StartTime).ToList(); + var events = iCal.GetOccurrences(new CalDateTime(2009, 06, 20)).TakeUntil(new CalDateTime(2009, 06, 22)).OrderBy(o => o.Period.StartTime).ToList(); Assert.That(((CalendarEvent)events[0].Source).Location, Is.EqualTo(longName)); } diff --git a/Ical.Net.Tests/TestHelpers.cs b/Ical.Net.Tests/TestHelpers.cs new file mode 100644 index 000000000..076c82c47 --- /dev/null +++ b/Ical.Net.Tests/TestHelpers.cs @@ -0,0 +1,21 @@ +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using Ical.Net.DataTypes; + +namespace Ical.Net.Tests; + +public static class TestHelpers +{ + public static IEnumerable TakeUntil(this IEnumerable sequence, CalDateTime periodEnd) + => (periodEnd == null) ? sequence : sequence.TakeWhile(p => p.StartTime < periodEnd); + + public static IEnumerable TakeUntil(this IEnumerable sequence, CalDateTime periodEnd) + => (periodEnd == null) ? sequence : sequence.TakeWhile(p => p.Period.StartTime < periodEnd); +} diff --git a/Ical.Net.Tests/TodoTest.cs b/Ical.Net.Tests/TodoTest.cs index 7fa59dc6d..8b5677432 100644 --- a/Ical.Net.Tests/TodoTest.cs +++ b/Ical.Net.Tests/TodoTest.cs @@ -211,8 +211,8 @@ public void Todo7_1() }; var occurrences = todo[0].GetOccurrences( - new CalDateTime(2006, 7, 1, 9, 0, 0), - new CalDateTime(2007, 7, 1, 9, 0, 0)).OrderBy(o => o.Period.StartTime).ToList(); + new CalDateTime(2006, 7, 1, 9, 0, 0)) + .TakeUntil(new CalDateTime(2007, 7, 1, 9, 0, 0)).OrderBy(o => o.Period.StartTime).ToList(); Assert.That( occurrences, From f141c0fd9aab92325f6ef4d0e6b5c3f825541c29 Mon Sep 17 00:00:00 2001 From: Markus Minichmayr Date: Thu, 1 May 2025 22:27:20 +0200 Subject: [PATCH 4/5] Test: Stop ordering occurrences as they are returned already ordered. --- Ical.Net.Benchmarks/ApplicationWorkflows.cs | 8 ++--- Ical.Net.Benchmarks/OccurencePerfTests.cs | 12 ++++---- Ical.Net.Benchmarks/ThroughputTests.cs | 4 +-- Ical.Net.Tests/DeserializationTests.cs | 4 +-- Ical.Net.Tests/GetOccurrenceTests.cs | 2 +- Ical.Net.Tests/ProgramTest.cs | 4 +-- Ical.Net.Tests/RecurrenceTests.cs | 30 +++++++------------ Ical.Net.Tests/RecurrenceTests_From_Issues.cs | 4 +-- Ical.Net.Tests/SimpleDeserializationTests.cs | 4 +-- Ical.Net.Tests/TodoTest.cs | 2 +- 10 files changed, 32 insertions(+), 42 deletions(-) diff --git a/Ical.Net.Benchmarks/ApplicationWorkflows.cs b/Ical.Net.Benchmarks/ApplicationWorkflows.cs index 669a211c9..92aa8b29e 100644 --- a/Ical.Net.Benchmarks/ApplicationWorkflows.cs +++ b/Ical.Net.Benchmarks/ApplicationWorkflows.cs @@ -38,7 +38,7 @@ public List SingleThreaded() return _manyCalendars .SelectMany(Calendar.Load) .SelectMany(c => c.Events) - .SelectMany(e => e.GetOccurrences(_searchStart, _searchEnd)) + .SelectMany(e => e.GetOccurrences(_searchStart).TakeWhile(p => p.Period.StartTime < _searchEnd)) .ToList(); } @@ -49,7 +49,7 @@ public List ParallelUponDeserialize() .AsParallel() .SelectMany(Calendar.Load) .SelectMany(c => c.Events) - .SelectMany(e => e.GetOccurrences(_searchStart, _searchEnd)) + .SelectMany(e => e.GetOccurrences(_searchStart).TakeWhile(p => p.Period.StartTime < _searchEnd)) .ToList(); } @@ -60,7 +60,7 @@ public List ParallelUponGetOccurrences() .SelectMany(Calendar.Load) .SelectMany(c => c.Events) .AsParallel() - .SelectMany(e => e.GetOccurrences(_searchStart, _searchEnd)) + .SelectMany(e => e.GetOccurrences(_searchStart).TakeWhile(p => p.Period.StartTime < _searchEnd)) .ToList(); } @@ -72,7 +72,7 @@ public List ParallelDeserializeSequentialGatherEventsParallelGetOccu .SelectMany(Calendar.Load) .AsSequential() .SelectMany(c => c.Events) - .SelectMany(e => e.GetOccurrences(_searchStart, _searchEnd)) + .SelectMany(e => e.GetOccurrences(_searchStart).TakeWhile(p => p.Period.StartTime < _searchEnd)) .ToList(); } } diff --git a/Ical.Net.Benchmarks/OccurencePerfTests.cs b/Ical.Net.Benchmarks/OccurencePerfTests.cs index be9f043e6..8a49f6631 100644 --- a/Ical.Net.Benchmarks/OccurencePerfTests.cs +++ b/Ical.Net.Benchmarks/OccurencePerfTests.cs @@ -35,7 +35,7 @@ public void MultipleEventsWithUntilOccurrencesSearchingByWholeCalendar() { var searchStart = _calendarFourEvents.Events.First().DtStart.AddYears(-1); var searchEnd = _calendarFourEvents.Events.Last().DtStart.AddYears(1); - _ = _calendarFourEvents.GetOccurrences(searchStart, searchEnd); + _ = _calendarFourEvents.GetOccurrences(searchStart).TakeWhile(p => p.Period.StartTime < searchEnd); } [Benchmark] @@ -44,7 +44,7 @@ public void MultipleEventsWithUntilOccurrences() var searchStart = _calendarFourEvents.Events.First().DtStart.AddYears(-1); var searchEnd = _calendarFourEvents.Events.Last().DtStart.AddYears(1); _ = _calendarFourEvents.Events - .SelectMany(e => e.GetOccurrences(searchStart, searchEnd)) + .SelectMany(e => e.GetOccurrences(searchStart).TakeWhile(p => p.Period.StartTime < searchEnd)) .ToList(); } @@ -55,7 +55,7 @@ public void MultipleEventsWithUntilOccurrencesEventsAsParallel() var searchEnd = _calendarFourEvents.Events.Last().DtStart.AddYears(1).AddDays(10); _ = _calendarFourEvents.Events .AsParallel() - .SelectMany(e => e.GetOccurrences(searchStart, searchEnd)) + .SelectMany(e => e.GetOccurrences(searchStart).TakeWhile(p => p.Period.StartTime < searchEnd)) .ToList(); } @@ -113,7 +113,7 @@ public void MultipleEventsWithCountOccurrencesSearchingByWholeCalendar() var calendar = GetFourCalendarEventsWithCountRule(); var searchStart = calendar.Events.First().DtStart.AddYears(-1); var searchEnd = calendar.Events.Last().DtStart.AddYears(1); - _ = calendar.GetOccurrences(searchStart, searchEnd); + _ = calendar.GetOccurrences(searchStart).TakeWhile(p => p.Period.StartTime < searchEnd); } [Benchmark] @@ -123,7 +123,7 @@ public void MultipleEventsWithCountOccurrences() var searchStart = calendar.Events.First().DtStart.AddYears(-1); var searchEnd = calendar.Events.Last().DtStart.AddYears(1); _ = calendar.Events - .SelectMany(e => e.GetOccurrences(searchStart, searchEnd)) + .SelectMany(e => e.GetOccurrences(searchStart).TakeWhile(p => p.Period.StartTime < searchEnd)) .ToList(); } @@ -135,7 +135,7 @@ public void MultipleEventsWithCountOccurrencesEventsAsParallel() var searchEnd = calendar.Events.Last().DtStart.AddYears(1).AddDays(10); _ = calendar.Events .AsParallel() - .SelectMany(e => e.GetOccurrences(searchStart, searchEnd)) + .SelectMany(e => e.GetOccurrences(searchStart).TakeWhile(p => p.Period.StartTime < searchEnd)) .ToList(); } diff --git a/Ical.Net.Benchmarks/ThroughputTests.cs b/Ical.Net.Benchmarks/ThroughputTests.cs index 9bfd9f089..4f11835b7 100644 --- a/Ical.Net.Benchmarks/ThroughputTests.cs +++ b/Ical.Net.Benchmarks/ThroughputTests.cs @@ -71,7 +71,7 @@ rsion 08.00.0681.000"">\n\n\n\n