From 2e2249dc572ef810e6f02509f4eec9020dc93fe7 Mon Sep 17 00:00:00 2001 From: Markus Minichmayr Date: Mon, 30 Dec 2024 10:00:51 +0100 Subject: [PATCH 1/7] Remove obsolete `IEvaluator.AssociatedObject` --- Ical.Net/Evaluation/Evaluator.cs | 8 -------- Ical.Net/Evaluation/IEvaluator.cs | 5 ----- Ical.Net/Evaluation/RecurringEvaluator.cs | 13 ------------- 3 files changed, 26 deletions(-) diff --git a/Ical.Net/Evaluation/Evaluator.cs b/Ical.Net/Evaluation/Evaluator.cs index 1223e912..a8fcbcae 100644 --- a/Ical.Net/Evaluation/Evaluator.cs +++ b/Ical.Net/Evaluation/Evaluator.cs @@ -13,8 +13,6 @@ namespace Ical.Net.Evaluation; public abstract class Evaluator : IEvaluator { - private ICalendarObject _mAssociatedObject; - protected Evaluator() { Initialize(); @@ -69,11 +67,5 @@ protected void IncrementDate(ref DateTime dt, RecurrencePattern pattern, int int public System.Globalization.Calendar Calendar { get; private set; } - public virtual ICalendarObject AssociatedObject - { - get => _mAssociatedObject; - protected set => _mAssociatedObject = value; - } - public abstract IEnumerable Evaluate(IDateTime referenceDate, DateTime? periodStart, DateTime? periodEnd, bool includeReferenceDateInResults); } diff --git a/Ical.Net/Evaluation/IEvaluator.cs b/Ical.Net/Evaluation/IEvaluator.cs index 778581f0..c92ccd84 100644 --- a/Ical.Net/Evaluation/IEvaluator.cs +++ b/Ical.Net/Evaluation/IEvaluator.cs @@ -16,11 +16,6 @@ public interface IEvaluator /// System.Globalization.Calendar Calendar { get; } - /// - /// Gets the object associated with this evaluator. - /// - ICalendarObject AssociatedObject { get; } - /// /// Evaluates this item to determine the dates and times for which it occurs/recurs. /// This method only evaluates items which occur/recur between diff --git a/Ical.Net/Evaluation/RecurringEvaluator.cs b/Ical.Net/Evaluation/RecurringEvaluator.cs index 54755aee..b97305bd 100644 --- a/Ical.Net/Evaluation/RecurringEvaluator.cs +++ b/Ical.Net/Evaluation/RecurringEvaluator.cs @@ -19,19 +19,6 @@ public class RecurringEvaluator : Evaluator public RecurringEvaluator(IRecurrable obj) { Recurrable = obj; - - // We're not sure if the object is a calendar object - // or a calendar data type, so we need to assign - // the associated object manually - if (obj is ICalendarObject) - { - AssociatedObject = (ICalendarObject)obj; - } - if (obj is ICalendarDataType) - { - var dt = (ICalendarDataType)obj; - AssociatedObject = dt.AssociatedObject; - } } /// From 4a93cca4daee86ad4df0af096a4ba2f665d3d042 Mon Sep 17 00:00:00 2001 From: Markus Minichmayr Date: Mon, 30 Dec 2024 10:01:33 +0100 Subject: [PATCH 2/7] Remove obsolete `Evaluator.ConvertToIDateTime()` --- Ical.Net/Evaluation/Evaluator.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Ical.Net/Evaluation/Evaluator.cs b/Ical.Net/Evaluation/Evaluator.cs index a8fcbcae..136bbe39 100644 --- a/Ical.Net/Evaluation/Evaluator.cs +++ b/Ical.Net/Evaluation/Evaluator.cs @@ -23,13 +23,6 @@ private void Initialize() Calendar = CultureInfo.CurrentCulture.Calendar; } - protected IDateTime ConvertToIDateTime(DateTime dt, IDateTime referenceDate) - { - IDateTime newDt = new CalDateTime(dt, referenceDate.TzId); - newDt.AssociateWith(referenceDate); - return newDt; - } - protected void IncrementDate(ref DateTime dt, RecurrencePattern pattern, int interval) { if (interval == 0) From 341a1dd1c5d62f618535934007e8c877b0236f19 Mon Sep 17 00:00:00 2001 From: Markus Minichmayr Date: Thu, 23 Jan 2025 16:03:46 +0100 Subject: [PATCH 3/7] Enable `#nullable` for `Evaluator` classes. --- Ical.Net/Evaluation/Evaluator.cs | 6 +----- Ical.Net/Evaluation/IEvaluator.cs | 1 + Ical.Net/Evaluation/PeriodListEvaluator.cs | 1 + Ical.Net/Evaluation/RecurringEvaluator.cs | 1 + Ical.Net/Evaluation/TimeZoneInfoEvaluator.cs | 3 ++- Ical.Net/Evaluation/TodoEvaluator.cs | 3 ++- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Ical.Net/Evaluation/Evaluator.cs b/Ical.Net/Evaluation/Evaluator.cs index 136bbe39..6122bb00 100644 --- a/Ical.Net/Evaluation/Evaluator.cs +++ b/Ical.Net/Evaluation/Evaluator.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. // +#nullable enable using System; using System.Collections.Generic; using System.Globalization; @@ -14,11 +15,6 @@ namespace Ical.Net.Evaluation; public abstract class Evaluator : IEvaluator { protected Evaluator() - { - Initialize(); - } - - private void Initialize() { Calendar = CultureInfo.CurrentCulture.Calendar; } diff --git a/Ical.Net/Evaluation/IEvaluator.cs b/Ical.Net/Evaluation/IEvaluator.cs index c92ccd84..ca2815e3 100644 --- a/Ical.Net/Evaluation/IEvaluator.cs +++ b/Ical.Net/Evaluation/IEvaluator.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. // +#nullable enable using System; using System.Collections.Generic; using Ical.Net.DataTypes; diff --git a/Ical.Net/Evaluation/PeriodListEvaluator.cs b/Ical.Net/Evaluation/PeriodListEvaluator.cs index d90585fe..dd65bdef 100644 --- a/Ical.Net/Evaluation/PeriodListEvaluator.cs +++ b/Ical.Net/Evaluation/PeriodListEvaluator.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. // +#nullable enable using System; using System.Collections.Generic; using Ical.Net.DataTypes; diff --git a/Ical.Net/Evaluation/RecurringEvaluator.cs b/Ical.Net/Evaluation/RecurringEvaluator.cs index b97305bd..9d36ea78 100644 --- a/Ical.Net/Evaluation/RecurringEvaluator.cs +++ b/Ical.Net/Evaluation/RecurringEvaluator.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. // +#nullable enable using System; using System.Collections.Generic; using System.Linq; diff --git a/Ical.Net/Evaluation/TimeZoneInfoEvaluator.cs b/Ical.Net/Evaluation/TimeZoneInfoEvaluator.cs index c43368f3..cd1887a0 100644 --- a/Ical.Net/Evaluation/TimeZoneInfoEvaluator.cs +++ b/Ical.Net/Evaluation/TimeZoneInfoEvaluator.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. // +#nullable enable using System; using System.Collections.Generic; using Ical.Net.CalendarComponents; @@ -14,7 +15,7 @@ public class TimeZoneInfoEvaluator : RecurringEvaluator { protected VTimeZoneInfo TimeZoneInfo { - get => Recurrable as VTimeZoneInfo; + get => Recurrable as VTimeZoneInfo ?? throw new InvalidOperationException(); set => Recurrable = value; } diff --git a/Ical.Net/Evaluation/TodoEvaluator.cs b/Ical.Net/Evaluation/TodoEvaluator.cs index c7e38d7d..8982fdb6 100644 --- a/Ical.Net/Evaluation/TodoEvaluator.cs +++ b/Ical.Net/Evaluation/TodoEvaluator.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. // +#nullable enable using System; using System.Collections.Generic; using System.Linq; @@ -14,7 +15,7 @@ namespace Ical.Net.Evaluation; public class TodoEvaluator : RecurringEvaluator { - protected Todo Todo => Recurrable as Todo; + protected Todo Todo => Recurrable as Todo ?? throw new InvalidOperationException(); public TodoEvaluator(Todo todo) : base(todo) { } From a08fa99ebb45e7f1379a29c8b7222ff8ddf2a5c4 Mon Sep 17 00:00:00 2001 From: Markus Minichmayr Date: Mon, 30 Dec 2024 10:18:31 +0100 Subject: [PATCH 4/7] Change `IEvaluator.Evaluate()` start/end params from `DateTime` to `IDateTime` to avoid ambiguity. --- Ical.Net.Tests/RecurrenceTests.cs | 55 +++++++------------ Ical.Net/Evaluation/Evaluator.cs | 2 +- Ical.Net/Evaluation/EventEvaluator.cs | 2 +- Ical.Net/Evaluation/IEvaluator.cs | 2 +- Ical.Net/Evaluation/PeriodListEvaluator.cs | 4 +- .../Evaluation/RecurrencePatternEvaluator.cs | 10 ++-- Ical.Net/Evaluation/RecurrenceUtil.cs | 2 +- Ical.Net/Evaluation/RecurringEvaluator.cs | 10 ++-- Ical.Net/Evaluation/TimeZoneInfoEvaluator.cs | 2 +- Ical.Net/Evaluation/TodoEvaluator.cs | 4 +- 10 files changed, 39 insertions(+), 54 deletions(-) diff --git a/Ical.Net.Tests/RecurrenceTests.cs b/Ical.Net.Tests/RecurrenceTests.cs index 8d13d79d..a38fe325 100644 --- a/Ical.Net.Tests/RecurrenceTests.cs +++ b/Ical.Net.Tests/RecurrenceTests.cs @@ -1189,8 +1189,8 @@ public void YearlyByDay1() [Test, Category("Recurrence")] public void WeekNoOrderingShouldNotMatter() { - var start = new DateTime(2019, 1, 1); - var end = new DateTime(2019, 12, 31); + var start = new CalDateTime(2019, 1, 1); + var end = new CalDateTime(2019, 12, 31); 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")); @@ -2585,11 +2585,11 @@ public void DurationOfRecurrencesOverDst(string dtStart, string dtEnd, string d1 [Test, Category("Recurrence")] public void BugByWeekNoNotWorking() { - var start = new DateTime(2019, 1, 1); - var end = new DateTime(2019, 12, 31); + var start = new CalDateTime(2019, 1, 1); + var end = new CalDateTime(2019, 12, 31); var rpe = new RecurrencePatternEvaluator(new RecurrencePattern("FREQ=WEEKLY;BYDAY=MO;BYWEEKNO=2")); - var recurringPeriods = rpe.Evaluate(new CalDateTime(start, false), start, end, false).ToList(); + var recurringPeriods = rpe.Evaluate(start, start, end, false).ToList(); Assert.That(recurringPeriods, Has.Count.EqualTo(1)); Assert.That(recurringPeriods.First().StartTime, Is.EqualTo(new CalDateTime(2019, 1, 7))); @@ -2601,11 +2601,11 @@ public void BugByWeekNoNotWorking() [Test, Category("Recurrence")] public void BugByMonthWhileFreqIsWeekly() { - var start = new DateTime(2020, 1, 1); - var end = new DateTime(2020, 12, 31); + var start = new CalDateTime(2020, 1, 1); + var end = new CalDateTime(2020, 12, 31); var rpe = new RecurrencePatternEvaluator(new RecurrencePattern("FREQ=WEEKLY;BYDAY=MO;BYMONTH=1")); - var recurringPeriods = rpe.Evaluate(new CalDateTime(start, false), start, end, false).OrderBy(x => x).ToList(); + var recurringPeriods = rpe.Evaluate(start, start, end, false).OrderBy(x => x).ToList(); Assert.That(recurringPeriods, Has.Count.EqualTo(4)); Assert.Multiple(() => @@ -2644,11 +2644,11 @@ public void ReccurencePattern_MaxDate_StopsOnCount() [Test, Category("Recurrence")] public void BugByMonthWhileFreqIsMonthly() { - var start = new DateTime(2020, 1, 1); - var end = new DateTime(2020, 12, 31); + var start = new CalDateTime(2020, 1, 1); + var end = new CalDateTime(2020, 12, 31); var rpe = new RecurrencePatternEvaluator(new RecurrencePattern("FREQ=MONTHLY;BYDAY=MO;BYMONTH=1")); - var recurringPeriods = rpe.Evaluate(new CalDateTime(start, false), start, end, false).OrderBy(x => x).ToList(); + var recurringPeriods = rpe.Evaluate(start, start, end, false).OrderBy(x => x).ToList(); Assert.That(recurringPeriods, Has.Count.EqualTo(4)); Assert.Multiple(() => @@ -2669,11 +2669,11 @@ public void Bug3119920() { using (var sr = new StringReader("FREQ=WEEKLY;UNTIL=20251126T120000;INTERVAL=1;BYDAY=MO")) { - var start = DateTime.Parse("2010-11-27 9:00:00"); + var start = new CalDateTime(2010, 11, 27, 9, 0, 0); var serializer = new RecurrencePatternSerializer(); var rp = (RecurrencePattern)serializer.Deserialize(sr); var rpe = new RecurrencePatternEvaluator(rp); - var recurringPeriods = rpe.Evaluate(new CalDateTime(start), start, rp.Until, false).ToList(); + var recurringPeriods = rpe.Evaluate(new CalDateTime(start), start, rp.Until?.AsCalDateTime(), false).ToList(); var period = recurringPeriods.ElementAt(recurringPeriods.Count - 1); @@ -2897,8 +2897,8 @@ public void RecurrencePattern1() var occurrences = evaluator.Evaluate( startDate, - SimpleDateTimeToMatch(fromDate, startDate), - SimpleDateTimeToMatch(toDate, startDate), + fromDate, + toDate, false) .OrderBy(o => o.StartTime) .ToList(); @@ -2930,8 +2930,8 @@ public void RecurrencePattern2() var occurrences = evaluator.Evaluate( startDate, - SimpleDateTimeToMatch(fromDate, startDate), - SimpleDateTimeToMatch(toDate, startDate), + fromDate, + toDate, false); Assert.That(occurrences.Count, Is.Not.EqualTo(0)); } @@ -3038,8 +3038,8 @@ public void Test4() // Add the exception dates var periods = evaluator.Evaluate( evtStart, - DateUtil.GetSimpleDateTimeData(evtStart), - SimpleDateTimeToMatch(evtEnd, evtStart), + evtStart, + evtEnd, false) .OrderBy(p => p.StartTime) .ToList(); @@ -3830,23 +3830,6 @@ public void ShouldCreateARecurringYearlyEvent() Assert.That(occurrences.Count, Is.EqualTo(27)); } - private static DateTime SimpleDateTimeToMatch(IDateTime dt, IDateTime toMatch) - { - if (toMatch.IsUtc && dt.IsUtc) - { - return dt.Value; - } - if (toMatch.IsUtc) - { - return dt.Value.ToUniversalTime(); - } - if (dt.IsUtc) - { - return dt.Value.ToLocalTime(); - } - return dt.Value; - } - [Test] public void GetOccurrenceShouldExcludeDtEndFloating() { diff --git a/Ical.Net/Evaluation/Evaluator.cs b/Ical.Net/Evaluation/Evaluator.cs index 6122bb00..a037f84a 100644 --- a/Ical.Net/Evaluation/Evaluator.cs +++ b/Ical.Net/Evaluation/Evaluator.cs @@ -56,5 +56,5 @@ protected void IncrementDate(ref DateTime dt, RecurrencePattern pattern, int int public System.Globalization.Calendar Calendar { get; private set; } - public abstract IEnumerable Evaluate(IDateTime referenceDate, DateTime? periodStart, DateTime? periodEnd, bool includeReferenceDateInResults); + public abstract IEnumerable Evaluate(IDateTime referenceDate, IDateTime? periodStart, IDateTime? periodEnd, bool includeReferenceDateInResults); } diff --git a/Ical.Net/Evaluation/EventEvaluator.cs b/Ical.Net/Evaluation/EventEvaluator.cs index cd640c40..45d9e6b7 100644 --- a/Ical.Net/Evaluation/EventEvaluator.cs +++ b/Ical.Net/Evaluation/EventEvaluator.cs @@ -42,7 +42,7 @@ public EventEvaluator(CalendarEvent evt) : base(evt) { } /// The end date of the range to evaluate. /// /// - public override IEnumerable Evaluate(IDateTime referenceTime, DateTime? periodStart, DateTime? periodEnd, bool includeReferenceDateInResults) + public override IEnumerable Evaluate(IDateTime referenceTime, IDateTime? periodStart, IDateTime? periodEnd, bool includeReferenceDateInResults) { // Evaluate recurrences normally var periods = base.Evaluate(referenceTime, periodStart, periodEnd, includeReferenceDateInResults) diff --git a/Ical.Net/Evaluation/IEvaluator.cs b/Ical.Net/Evaluation/IEvaluator.cs index ca2815e3..8007f29f 100644 --- a/Ical.Net/Evaluation/IEvaluator.cs +++ b/Ical.Net/Evaluation/IEvaluator.cs @@ -42,5 +42,5 @@ public interface IEvaluator /// A sequence of objects for /// each date/time when this item occurs/recurs. /// - IEnumerable Evaluate(IDateTime referenceDate, DateTime? periodStart, DateTime? periodEnd, bool includeReferenceDateInResults); + IEnumerable Evaluate(IDateTime referenceDate, IDateTime? periodStart, IDateTime? periodEnd, bool includeReferenceDateInResults); } diff --git a/Ical.Net/Evaluation/PeriodListEvaluator.cs b/Ical.Net/Evaluation/PeriodListEvaluator.cs index dd65bdef..ef433a7f 100644 --- a/Ical.Net/Evaluation/PeriodListEvaluator.cs +++ b/Ical.Net/Evaluation/PeriodListEvaluator.cs @@ -19,7 +19,7 @@ public PeriodListEvaluator(PeriodList rdt) _mPeriodList = rdt; } - public override IEnumerable Evaluate(IDateTime referenceDate, DateTime? periodStart, DateTime? periodEnd, bool includeReferenceDateInResults) + public override IEnumerable Evaluate(IDateTime referenceDate, IDateTime? periodStart, IDateTime? periodEnd, bool includeReferenceDateInResults) { var periods = new SortedSet(); @@ -29,7 +29,7 @@ public override IEnumerable Evaluate(IDateTime referenceDate, DateTime? periods.Add(p); } - if (periodEnd < periodStart) + if ((periodStart is not null) && (periodEnd is not null) && periodEnd.LessThan(periodStart)) { return periods; } diff --git a/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs b/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs index 427f43d9..140c5157 100644 --- a/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs +++ b/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs @@ -109,12 +109,14 @@ private RecurrencePattern ProcessRecurrencePattern(IDateTime 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(IDateTime seed, DateTime? periodStart, DateTime? periodEnd, int maxCount, RecurrencePattern pattern, + private IEnumerable GetDates(IDateTime seed, IDateTime? periodStart, IDateTime? periodEnd, int maxCount, RecurrencePattern pattern, bool includeReferenceDateInResults) { // In the first step, we work with DateTime values, so we need to convert the IDateTime to DateTime var originalDate = DateUtil.GetSimpleDateTimeData(seed); var seedCopy = DateUtil.GetSimpleDateTimeData(seed); + var periodStartDt = periodStart?.ToTimeZone(seed.TzId)?.Value; + var periodEndDt = periodEnd?.ToTimeZone(seed.TzId)?.Value; if ((pattern.Frequency == FrequencyType.Yearly) && (pattern.ByWeekNo.Count != 0)) { @@ -129,7 +131,7 @@ private IEnumerable GetDates(IDateTime seed, DateTime? periodStart, Da if (pattern.Count is null) { var incremented = seedCopy; - while (incremented < periodStart) + while (incremented < periodStartDt) { seedCopy = incremented; IncrementDate(ref incremented, pattern, pattern.Interval); @@ -143,7 +145,7 @@ private IEnumerable GetDates(IDateTime seed, DateTime? periodStart, Da // 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, periodStart, periodEnd, maxCount, pattern); + return EnumerateDates(originalDate, seedCopy, periodStartDt, periodEndDt, maxCount, pattern); } private IEnumerable EnumerateDates(DateTime originalDate, DateTime seedCopy, DateTime? periodStart, DateTime? periodEnd, int maxCount, RecurrencePattern pattern) @@ -868,7 +870,7 @@ private static Period CreatePeriod(DateTime dateTime, IDateTime referenceDate) /// End (excl.) of the period occurrences are generated for. /// Whether the referenceDate itself should be returned. Ignored as the reference data MUST equal the first occurrence of an RRULE. /// - public override IEnumerable Evaluate(IDateTime referenceDate, DateTime? periodStart, DateTime? periodEnd, bool includeReferenceDateInResults) + public override IEnumerable Evaluate(IDateTime referenceDate, IDateTime? periodStart, IDateTime? periodEnd, bool includeReferenceDateInResults) { if (Pattern.Frequency != FrequencyType.None && Pattern.Frequency < FrequencyType.Daily && !referenceDate.HasTime) { diff --git a/Ical.Net/Evaluation/RecurrenceUtil.cs b/Ical.Net/Evaluation/RecurrenceUtil.cs index 50bd65fa..d82f5e7b 100644 --- a/Ical.Net/Evaluation/RecurrenceUtil.cs +++ b/Ical.Net/Evaluation/RecurrenceUtil.cs @@ -37,7 +37,7 @@ public static IEnumerable GetOccurrences(IRecurrable recurrable, IDa if (periodEnd != null) periodEnd = new CalDateTime(periodEnd.Date, periodEnd.Time, start.TzId); - var periods = evaluator.Evaluate(start, periodStart?.Value, periodEnd?.Value, + var periods = evaluator.Evaluate(start, periodStart, periodEnd, includeReferenceDateInResults); var occurrences = diff --git a/Ical.Net/Evaluation/RecurringEvaluator.cs b/Ical.Net/Evaluation/RecurringEvaluator.cs index 9d36ea78..0b40a962 100644 --- a/Ical.Net/Evaluation/RecurringEvaluator.cs +++ b/Ical.Net/Evaluation/RecurringEvaluator.cs @@ -29,7 +29,7 @@ public RecurringEvaluator(IRecurrable obj) /// The beginning date of the range to evaluate. /// The end date of the range to evaluate. /// - protected IEnumerable EvaluateRRule(IDateTime referenceDate, DateTime? periodStart, DateTime? periodEnd, bool includeReferenceDateInResults) + protected IEnumerable EvaluateRRule(IDateTime referenceDate, IDateTime? periodStart, IDateTime? periodEnd, bool includeReferenceDateInResults) { if (Recurrable.RecurrenceRules == null || !Recurrable.RecurrenceRules.Any()) return []; @@ -59,7 +59,7 @@ protected IEnumerable EvaluateRRule(IDateTime referenceDate, DateTime? p } /// Evaluates the RDate component. - protected IEnumerable EvaluateRDate(IDateTime referenceDate, DateTime? periodStart, DateTime? periodEnd) + protected IEnumerable EvaluateRDate(IDateTime referenceDate, IDateTime? periodStart, IDateTime? periodEnd) { var recurrences = new SortedSet(Recurrable.RecurrenceDates @@ -74,7 +74,7 @@ protected IEnumerable EvaluateRDate(IDateTime referenceDate, DateTime? p /// /// The beginning date of the range to evaluate. /// The end date of the range to evaluate. - protected IEnumerable EvaluateExRule(IDateTime referenceDate, DateTime? periodStart, DateTime? periodEnd) + protected IEnumerable EvaluateExRule(IDateTime referenceDate, IDateTime? periodStart, IDateTime? periodEnd) { if (Recurrable.ExceptionRules == null || !Recurrable.ExceptionRules.Any()) return []; @@ -102,14 +102,14 @@ protected IEnumerable EvaluateExRule(IDateTime referenceDate, DateTime? /// /// The beginning date of the range to evaluate. /// The end date of the range to evaluate. - protected IEnumerable EvaluateExDate(IDateTime referenceDate, DateTime? periodStart, DateTime? periodEnd) + protected IEnumerable EvaluateExDate(IDateTime referenceDate, IDateTime? periodStart, IDateTime? periodEnd) { var exDates = new SortedSet(Recurrable .ExceptionDates.GetAllPeriodsByKind(PeriodKind.DateOnly, PeriodKind.DateTime)); return exDates; } - public override IEnumerable Evaluate(IDateTime referenceDate, DateTime? periodStart, DateTime? periodEnd, bool includeReferenceDateInResults) + public override IEnumerable Evaluate(IDateTime referenceDate, IDateTime? periodStart, IDateTime? periodEnd, bool includeReferenceDateInResults) { var rruleOccurrences = EvaluateRRule(referenceDate, periodStart, periodEnd, includeReferenceDateInResults); //Only add referenceDate if there are no RecurrenceRules defined diff --git a/Ical.Net/Evaluation/TimeZoneInfoEvaluator.cs b/Ical.Net/Evaluation/TimeZoneInfoEvaluator.cs index cd1887a0..41190545 100644 --- a/Ical.Net/Evaluation/TimeZoneInfoEvaluator.cs +++ b/Ical.Net/Evaluation/TimeZoneInfoEvaluator.cs @@ -21,7 +21,7 @@ protected VTimeZoneInfo TimeZoneInfo public TimeZoneInfoEvaluator(IRecurrable tzi) : base(tzi) { } - public override IEnumerable Evaluate(IDateTime referenceDate, DateTime? periodStart, DateTime? periodEnd, bool includeReferenceDateInResults) + public override IEnumerable Evaluate(IDateTime referenceDate, IDateTime? periodStart, IDateTime? periodEnd, bool includeReferenceDateInResults) { // Time zones must include an effective start date/time // and must provide an evaluator. diff --git a/Ical.Net/Evaluation/TodoEvaluator.cs b/Ical.Net/Evaluation/TodoEvaluator.cs index 8982fdb6..11e9c221 100644 --- a/Ical.Net/Evaluation/TodoEvaluator.cs +++ b/Ical.Net/Evaluation/TodoEvaluator.cs @@ -44,7 +44,7 @@ internal IEnumerable EvaluateToPreviousOccurrence(IDateTime completedDat DetermineStartingRecurrence(Todo.ExceptionDates.GetAllDates(), ref beginningDate); - return Evaluate(Todo.Start, DateUtil.GetSimpleDateTimeData(beginningDate), DateUtil.GetSimpleDateTimeData(currDt).AddTicks(1), true); + return Evaluate(Todo.Start, beginningDate, currDt.AddSeconds(1), true); } private static void DetermineStartingRecurrence(IEnumerable rdate, ref IDateTime referenceDateTime) @@ -79,7 +79,7 @@ private void DetermineStartingRecurrence(RecurrencePattern recur, ref IDateTime } } - public override IEnumerable Evaluate(IDateTime referenceDate, DateTime? periodStart, DateTime? periodEnd, bool includeReferenceDateInResults) + public override IEnumerable Evaluate(IDateTime referenceDate, IDateTime? periodStart, IDateTime? periodEnd, bool includeReferenceDateInResults) { // TODO items can only recur if a start date is specified if (Todo.Start == null) From 7a3ed409807587d50072d9468df8f7553445fba6 Mon Sep 17 00:00:00 2001 From: Markus Minichmayr Date: Mon, 30 Dec 2024 10:24:33 +0100 Subject: [PATCH 5/7] Add test `TestDtStartTimezone` from #406. --- Ical.Net.Tests/RecurrenceTests.cs | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Ical.Net.Tests/RecurrenceTests.cs b/Ical.Net.Tests/RecurrenceTests.cs index a38fe325..ada94a14 100644 --- a/Ical.Net.Tests/RecurrenceTests.cs +++ b/Ical.Net.Tests/RecurrenceTests.cs @@ -3880,4 +3880,38 @@ public void TestGetOccurrenceIndefinite() Assert.That(instances.Count(), Is.EqualTo(100)); } + + [Test, Category("Recurrence")] + [TestCase(null)] + [TestCase("UTC")] + [TestCase("Europe/Vienna")] + [TestCase("America/New_York")] + public void TestDtStartTimezone(string tzId) + { + var icalText = """ + BEGIN:VCALENDAR + BEGIN:VEVENT + UID:ignore + DTSTAMP:20180613T154237Z + DTSTART;TZID=Europe/Vienna:20180612T180000 + DTEND;TZID=Europe/Vienna:20180612T181000 + RRULE:FREQ=HOURLY + END:VEVENT + END:VCALENDAR + """; + + 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), false); + var occurrencesStartTimes = occurrences.Select(x => x.StartTime).Take(2).ToList(); + + var expectedStartTimes = new[] + { + new CalDateTime(2018, 06, 12, 18, 0, 0, "Europe/Vienna"), + new CalDateTime(2018, 06, 12, 19, 0, 0, "Europe/Vienna"), + }; + + Assert.That(expectedStartTimes.SequenceEqual(occurrencesStartTimes), Is.True); + } } From 63f583dfa450ed4fa328904bbdb4e9ebda64fd36 Mon Sep 17 00:00:00 2001 From: Markus Minichmayr Date: Mon, 30 Dec 2024 10:43:16 +0100 Subject: [PATCH 6/7] CalDateTime: Disallow initializing from `DateTimeKind.Local`. --- Ical.Net.Tests/CalDateTimeTests.cs | 18 ++++++++++++++++++ Ical.Net/DataTypes/CalDateTime.cs | 16 +++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/Ical.Net.Tests/CalDateTimeTests.cs b/Ical.Net.Tests/CalDateTimeTests.cs index f8e41414..b204fd2e 100644 --- a/Ical.Net.Tests/CalDateTimeTests.cs +++ b/Ical.Net.Tests/CalDateTimeTests.cs @@ -6,6 +6,7 @@ using Ical.Net.CalendarComponents; using Ical.Net.DataTypes; using NUnit.Framework; +using NUnit.Framework.Constraints; using System; using System.Collections; using System.Collections.Generic; @@ -343,4 +344,21 @@ public void AddAndSubtract_ShouldBeReversible(CalDateTime t, Duration d) Assert.That(t.Add(d).SubtractExact(t), Is.EqualTo(d.ToTimeSpan())); }); } + + private static TestCaseData[] CalDateTime_FromDateTime_HandlesKindCorrectlyTestCases => + [ + new TestCaseData(DateTimeKind.Unspecified, Is.EqualTo(new CalDateTime(2024, 12, 30, 10, 44, 50, null))), + new TestCaseData(DateTimeKind.Utc, Is.EqualTo(new CalDateTime(2024, 12, 30, 10, 44, 50, "UTC"))), + new TestCaseData(DateTimeKind.Local, Throws.ArgumentException), + ]; + + [Test, TestCaseSource(nameof(CalDateTime_FromDateTime_HandlesKindCorrectlyTestCases))] + + + public void CalDateTime_FromDateTime_HandlesKindCorrectly(DateTimeKind kind, IResolveConstraint constraint) + { + var dt = new DateTime(2024, 12, 30, 10, 44, 50, kind); + + Assert.That(() => new CalDateTime(dt), constraint); + } } diff --git a/Ical.Net/DataTypes/CalDateTime.cs b/Ical.Net/DataTypes/CalDateTime.cs index c18ecd56..d5e48b6d 100644 --- a/Ical.Net/DataTypes/CalDateTime.cs +++ b/Ical.Net/DataTypes/CalDateTime.cs @@ -78,15 +78,25 @@ public CalDateTime(IDateTime value) /// It will represent an RFC 5545, Section 3.3.4, DATE value, if is . /// /// The will be set to "UTC" if the - /// has and is . - /// Otherwise will be . + /// has kind and is . + /// It will be set to if the kind is kind + /// and will throw otherwise. /// /// The value. Its will be ignored. /// /// The instance will represent an RFC 5545, Section 3.3.5, DATE-TIME value, if is . /// It will represent an RFC 5545, Section 3.3.4, DATE value, if is . /// - public CalDateTime(DateTime value, bool hasTime = true) : this(value, value.Kind == DateTimeKind.Utc ? UtcTzId : null, hasTime) + /// If the specified value's kind is + public CalDateTime(DateTime value, bool hasTime = true) : this( + value, + value.Kind switch + { + DateTimeKind.Utc => UtcTzId, + DateTimeKind.Unspecified => null, + _ => throw new ArgumentException($"An instance of {nameof(CalDateTime)} can only be initializd from a {nameof(DateTime)} of kind {nameof(DateTimeKind.Utc)} or {nameof(DateTimeKind.Unspecified)}.") + }, + hasTime) { } /// From 3db7dea729fb3ef100c490763825a1456ff8056e Mon Sep 17 00:00:00 2001 From: Markus Minichmayr Date: Mon, 30 Dec 2024 10:50:47 +0100 Subject: [PATCH 7/7] Avoid using `DateTimeKind.Local` in test cases. --- Ical.Net.Tests/CopyComponentTests.cs | 6 +++--- Ical.Net.Tests/EqualityAndHashingTests.cs | 2 +- Ical.Net.Tests/RecurrenceTests.cs | 6 +++--- Ical.Net.Tests/SerializationTests.cs | 2 +- Ical.Net.Tests/SymmetricSerializationTests.cs | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Ical.Net.Tests/CopyComponentTests.cs b/Ical.Net.Tests/CopyComponentTests.cs index bc876ddf..c4df1d63 100644 --- a/Ical.Net.Tests/CopyComponentTests.cs +++ b/Ical.Net.Tests/CopyComponentTests.cs @@ -53,7 +53,7 @@ public static IEnumerable CopyCalendarTest_TestCases() yield return new TestCaseData(IcsFiles.XProperty2).SetName("XProperty2"); } - private static readonly DateTime _now = DateTime.Now; + private static readonly DateTime _now = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Unspecified); private static readonly DateTime _later = _now.AddHours(1); private static CalendarEvent GetSimpleEvent() => new CalendarEvent @@ -154,7 +154,7 @@ public void CopyTodoTest() { Summary = "Test Todo", Description = "This is a test todo", - Due = new CalDateTime(DateTime.Now.AddDays(10)), + Due = new CalDateTime(DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Unspecified).AddDays(10)), Priority = 1, Contacts = new[] { "John", "Paul" }, Status = "NeedsAction" @@ -180,7 +180,7 @@ public void CopyJournalTest() { Summary = "Test Journal", Description = "This is a test journal", - DtStart = new CalDateTime(DateTime.Now), + DtStart = new CalDateTime(DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Unspecified)), Categories = new List { "Category1", "Category2" }, Priority = 1, Status = "Draft" diff --git a/Ical.Net.Tests/EqualityAndHashingTests.cs b/Ical.Net.Tests/EqualityAndHashingTests.cs index 1a7a54ad..550a5efd 100644 --- a/Ical.Net.Tests/EqualityAndHashingTests.cs +++ b/Ical.Net.Tests/EqualityAndHashingTests.cs @@ -19,7 +19,7 @@ namespace Ical.Net.Tests; public class EqualityAndHashingTests { private const string TzId = "America/Los_Angeles"; - private static readonly DateTime _nowTime = DateTime.Parse("2016-07-16T16:47:02.9310521-04:00"); + private static readonly DateTime _nowTime = DateTime.SpecifyKind(DateTime.Parse("2016-07-16T16:47:02.9310521-04:00"), DateTimeKind.Unspecified); private static readonly DateTime _later = _nowTime.AddHours(1); [Test, TestCaseSource(nameof(CalDateTime_TestCases))] diff --git a/Ical.Net.Tests/RecurrenceTests.cs b/Ical.Net.Tests/RecurrenceTests.cs index ada94a14..bfe33f26 100644 --- a/Ical.Net.Tests/RecurrenceTests.cs +++ b/Ical.Net.Tests/RecurrenceTests.cs @@ -2740,7 +2740,7 @@ public void Issue432() }; var vEvent = new CalendarEvent { - Start = new CalDateTime(DateTime.Parse("2019-01-04T08:00Z")), + Start = new CalDateTime(DateTime.Parse("2019-01-04T08:00Z").ToUniversalTime()), }; vEvent.RecurrenceRules.Add(rrule); @@ -2748,7 +2748,7 @@ public void Issue432() //Testing on both the first day and the next, results used to be different for (var i = 0; i <= 1; i++) { - var checkTime = DateTime.Parse("2019-01-04T08:00Z"); + var checkTime = DateTime.Parse("2019-01-04T08:00Z").ToUniversalTime(); checkTime = checkTime.AddDays(i); //Valid asking for the exact moment var occurrences = vEvent.GetOccurrences(checkTime, checkTime).ToList(); @@ -3295,7 +3295,7 @@ public void AddExDateToEventAfterGetOccurrencesShouldRecomputeResult() Assert.That(occurrences, Has.Count.EqualTo(3)); } - private static readonly DateTime _now = DateTime.Now; + private static readonly DateTime _now = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Unspecified); private static readonly DateTime _later = _now.AddHours(1); private static CalendarEvent GetEventWithRecurrenceRules() { diff --git a/Ical.Net.Tests/SerializationTests.cs b/Ical.Net.Tests/SerializationTests.cs index 9d01c551..865e8c2d 100644 --- a/Ical.Net.Tests/SerializationTests.cs +++ b/Ical.Net.Tests/SerializationTests.cs @@ -22,7 +22,7 @@ namespace Ical.Net.Tests; [TestFixture] public class SerializationTests { - private static readonly DateTime _nowTime = DateTime.Now; + private static readonly DateTime _nowTime = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Unspecified); private static readonly DateTime _later = _nowTime.AddHours(1); private static CalendarSerializer GetNewSerializer() => new CalendarSerializer(); private static string SerializeToString(Calendar c) => GetNewSerializer().SerializeToString(c); diff --git a/Ical.Net.Tests/SymmetricSerializationTests.cs b/Ical.Net.Tests/SymmetricSerializationTests.cs index b216d9aa..9c22082c 100644 --- a/Ical.Net.Tests/SymmetricSerializationTests.cs +++ b/Ical.Net.Tests/SymmetricSerializationTests.cs @@ -22,7 +22,7 @@ public class SymmetricSerializationTests { private const string _ldapUri = "ldap://example.com:6666/o=eDABC Industries,c=3DUS??(cn=3DBMary Accepted)"; - private static readonly DateTime _nowTime = DateTime.Now; + private static readonly DateTime _nowTime = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Unspecified); private static readonly DateTime _later = _nowTime.AddHours(1); private static CalendarSerializer GetNewSerializer() => new CalendarSerializer(); private static string SerializeToString(Calendar c) => GetNewSerializer().SerializeToString(c);