From 4f93dcd5f92a283a8f7053df7a78b7894f6ed652 Mon Sep 17 00:00:00 2001 From: Markus Minichmayr Date: Wed, 20 Nov 2024 23:36:30 +0100 Subject: [PATCH 1/3] RecurrencePatternSerializer: Ignore extra semicolons when parsing RRULEs. --- Ical.Net.Tests/contrib/libical/icalrecur_test.out | 9 ++++----- .../DataTypes/RecurrencePatternSerializer.cs | 15 ++++++++++++++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Ical.Net.Tests/contrib/libical/icalrecur_test.out b/Ical.Net.Tests/contrib/libical/icalrecur_test.out index ddc57c84..90104e2b 100644 --- a/Ical.Net.Tests/contrib/libical/icalrecur_test.out +++ b/Ical.Net.Tests/contrib/libical/icalrecur_test.out @@ -189,11 +189,10 @@ DTSTART:19970805T090000 INSTANCES:19970805T090000,19970817T090000,19970819T090000,19970831T090000 PREV-INSTANCES:19970819T090000,19970817T090000,19970805T090000 -# TODO: FIX (see https://github.com/ical-org/ical.net/issues/618) -# RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=2;BYDAY=MO; -# DTSTART:20141006T090000 -# INSTANCES:20141006T090000,20141013T090000 -# PREV-INSTANCES:20141006T090000 +RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=2;BYDAY=MO; +DTSTART:20141006T090000 +INSTANCES:20141006T090000,20141013T090000 +PREV-INSTANCES:20141006T090000 RRULE:FREQ=DAILY;COUNT=10 DTSTART:19970902T090000 diff --git a/Ical.Net/Serialization/DataTypes/RecurrencePatternSerializer.cs b/Ical.Net/Serialization/DataTypes/RecurrencePatternSerializer.cs index 0d7e9597..6e21655e 100644 --- a/Ical.Net/Serialization/DataTypes/RecurrencePatternSerializer.cs +++ b/Ical.Net/Serialization/DataTypes/RecurrencePatternSerializer.cs @@ -244,7 +244,20 @@ public override object Deserialize(TextReader tr) var keywordPairs = match.Groups[2].Value.Split(';'); foreach (var keywordPair in keywordPairs) { + if (keywordPair.Length == 0) + { + // This is illegal but ignore for now. + continue; + } + var keyValues = keywordPair.Split('='); + if (keyValues.Length != 2) + { + // ArgumentExceptions seem to be the Exception of choise for this class. Should + // probably be changed to a more specific exception type. + throw new ArgumentException($"The recurrence rule part '{keywordPair}' is invalid."); + } + var keyword = keyValues[0]; var keyValue = keyValues[1]; @@ -493,4 +506,4 @@ public override object Deserialize(TextReader tr) return r; } -} \ No newline at end of file +} From 6b8e6e8ff68dd17c4ebf72acfeca996c6beba648 Mon Sep 17 00:00:00 2001 From: Markus Minichmayr Date: Thu, 21 Nov 2024 00:15:19 +0100 Subject: [PATCH 2/3] Recurrence evaluation: Fix `BYDAY` expansion in case of `FREQ=WEEKLY` or `BYWEEKNO`, which didn't properly consider the start of the week. --- .../contrib/libical/icalrecur_test.out | 24 +++++++++---------- .../Evaluation/RecurrencePatternEvaluator.cs | 9 +++++++ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/Ical.Net.Tests/contrib/libical/icalrecur_test.out b/Ical.Net.Tests/contrib/libical/icalrecur_test.out index 90104e2b..7b1dde8c 100644 --- a/Ical.Net.Tests/contrib/libical/icalrecur_test.out +++ b/Ical.Net.Tests/contrib/libical/icalrecur_test.out @@ -306,19 +306,17 @@ START-AT:20170915T090000 INSTANCES:20171006T090000,20171103T090000,20171201T090000 PREV-INSTANCES:20170901T090000 -# TODO: FIX (see https://github.com/ical-org/ical.net/issues/618) -# RRULE:FREQ=WEEKLY;UNTIL=20170127T000000Z;WKST=MO;BYDAY=SU,TU,TH;INTERVAL=2 -# DTSTART:20161229T090000 -# START-AT:20161231T090000 -# INSTANCES:20170101T090000,20170110T090000,20170112T090000,20170115T090000,20170124T090000,20170126T090000 -# PREV-INSTANCES:20161229T090000 - -# TODO: FIX (see https://github.com/ical-org/ical.net/issues/618) -# RRULE:FREQ=WEEKLY;UNTIL=20170127T000000Z;WKST=MO;BYDAY=SU,TU,TH;INTERVAL=2 -# DTSTART:20161229T090000 -# START-AT:20170102T090000 -# INSTANCES:20170110T090000,20170112T090000,20170115T090000,20170124T090000,20170126T090000 -# PREV-INSTANCES:20170101T090000,20161229T090000 +RRULE:FREQ=WEEKLY;UNTIL=20170127T000000Z;WKST=MO;BYDAY=SU,TU,TH;INTERVAL=2 +DTSTART:20161229T090000 +START-AT:20161231T090000 +INSTANCES:20170101T090000,20170110T090000,20170112T090000,20170115T090000,20170124T090000,20170126T090000 +PREV-INSTANCES:20161229T090000 + +RRULE:FREQ=WEEKLY;UNTIL=20170127T000000Z;WKST=MO;BYDAY=SU,TU,TH;INTERVAL=2 +DTSTART:20161229T090000 +START-AT:20170102T090000 +INSTANCES:20170110T090000,20170112T090000,20170115T090000,20170124T090000,20170126T090000 +PREV-INSTANCES:20170101T090000,20161229T090000 RRULE:FREQ=DAILY;UNTIL=20170131T140000Z;BYMONTH=1;INTERVAL=3 DTSTART:20170101T090000 diff --git a/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs b/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs index 32b2967b..7b8fb255 100644 --- a/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs +++ b/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs @@ -658,6 +658,9 @@ private List GetAbsWeekDays(DateTime date, WeekDay weekDay, Recurrence { var weekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); + // Go to the first day of the week + date = date.AddDays(-GetWeekDayOffset(date, pattern.FirstDayOfWeek)); + // construct a list of possible week days.. while (date.DayOfWeek != dayOfWeek) { @@ -725,6 +728,12 @@ private List GetAbsWeekDays(DateTime date, WeekDay weekDay, Recurrence return GetOffsetDates(days, weekDay.Offset); } + /// + /// Returns the days since the start of the week, 0 if the date is on the first day of the week. + /// + private static int GetWeekDayOffset(DateTime date, DayOfWeek startOfWeek) + => date.DayOfWeek + ((date.DayOfWeek < startOfWeek) ? 7 : 0) - startOfWeek; + /// /// Returns a single-element sublist containing the element of at . /// Valid offsets are from 1 to the size of the list. If an invalid offset is supplied, all elements from From 64adc9709a339046642003115286a6d6b56fc827 Mon Sep 17 00:00:00 2001 From: Markus Minichmayr Date: Thu, 21 Nov 2024 00:46:40 +0100 Subject: [PATCH 3/3] Test: Add test for illegal rule parts. --- .../Calendars/Recurrence/RecurrenceTestCases.txt | 4 ++++ Ical.Net.Tests/RecurrenceTests.cs | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Ical.Net.Tests/Calendars/Recurrence/RecurrenceTestCases.txt b/Ical.Net.Tests/Calendars/Recurrence/RecurrenceTestCases.txt index 05dafe27..af6a5f73 100644 --- a/Ical.Net.Tests/Calendars/Recurrence/RecurrenceTestCases.txt +++ b/Ical.Net.Tests/Calendars/Recurrence/RecurrenceTestCases.txt @@ -2,3 +2,7 @@ RRULE:FREQ=WEEKLY;BYDAY=MO,TH;COUNT=3 DTSTART:20241024 INSTANCES:20241024,20241028,20241031 + +# Illegal rule part with multiple '=' +RRULE:FREQ=WEEKLY;BYDAY=MO=;COUNT=3 +EXCEPTION:System.ArgumentException diff --git a/Ical.Net.Tests/RecurrenceTests.cs b/Ical.Net.Tests/RecurrenceTests.cs index bac40fba..3eac6109 100644 --- a/Ical.Net.Tests/RecurrenceTests.cs +++ b/Ical.Net.Tests/RecurrenceTests.cs @@ -3631,6 +3631,8 @@ public class RecurrenceTestCase public IReadOnlyList Instances { get; set; } + public string Exception { get; set; } + public override string ToString() => $"Line {LineNumber}: {DtStart}, {RRule}"; } @@ -3686,6 +3688,10 @@ private static IEnumerable ParseTestCaseFile(string fileCont case "INSTANCES": current.Instances = val.Split(',').Select(dt => new CalDateTime(dt) { TzId = "UTC" }).ToList(); break; + + case "EXCEPTION": + current.Exception = val; + break; } } @@ -3718,6 +3724,14 @@ public void ExecuteRecurrenceTestCase(RecurrenceTestCase testCase) // Start at midnight, UTC time evt.Start = testCase.DtStart; + + if (testCase.Exception != null) + { + var exceptionType = Type.GetType(testCase.Exception); + Assert.Throws(exceptionType, () => new RecurrencePattern(testCase.RRule)); + return; + } + evt.RecurrenceRules.Add(new RecurrencePattern(testCase.RRule)); var occurrences = evt.GetOccurrences(testCase.StartAt?.Value ?? DateTime.MinValue, DateTime.MaxValue) @@ -3728,4 +3742,4 @@ public void ExecuteRecurrenceTestCase(RecurrenceTestCase testCase) Assert.That(startDates, Is.EqualTo(testCase.Instances)); } -} \ No newline at end of file +}