diff --git a/Ical.Net.Tests/contrib/libical/icalrecur_test.out b/Ical.Net.Tests/contrib/libical/icalrecur_test.out index 7b1dde8cd..87e3cbc73 100644 --- a/Ical.Net.Tests/contrib/libical/icalrecur_test.out +++ b/Ical.Net.Tests/contrib/libical/icalrecur_test.out @@ -357,11 +357,10 @@ START-AT:20210302T100000 INSTANCES:20210302T102000,20210302T112000,20210302T122000,20210302T132000,20210302T142000,20210302T152000,20210302T162000,20210302T172000,20210302T182000,20210302T192000,20210302T202000,20210302T212000,20210302T222000,20210302T232000 PREV-INSTANCES:20210302T092000,20210302T082000,20210302T072000,20210302T062000,20210302T052000,20210302T042000,20210302T032000,20210302T022000,20210302T012000,20210302T002000,20210301T232000,20210301T222000,20210301T212000,20210301T202000,20210301T192000,20210301T182000,20210301T172000,20210301T162000,20210301T152000,20210301T142000 -# TODO: FIX (see https://github.com/ical-org/ical.net/issues/618) -# RRULE:FREQ=YEARLY;BYWEEKNO=6;BYDAY=TU;WKST=TH;UNTIL=20210612T000000Z -# DTSTART:20180206T080001 -# INSTANCES:20180213T080001,20190212T080001,20200211T080001,20210209T080001 -# PREV-INSTANCES:20210209T080001,20200211T080001,20190212T080001,20180213T080001 +RRULE:FREQ=YEARLY;BYWEEKNO=6;BYDAY=TU;WKST=TH;UNTIL=20210612T000000Z +DTSTART:20180213T080001 +INSTANCES:20180213T080001,20190212T080001,20200211T080001,20210209T080001 +PREV-INSTANCES:20210209T080001,20200211T080001,20190212T080001,20180213T080001 RRULE:FREQ=DAILY;BYMINUTE=1,2,3,4;INTERVAL=2;COUNT=3 DTSTART:20241018 diff --git a/Ical.Net/CalendarExtensions.cs b/Ical.Net/CalendarExtensions.cs index 0f80956f1..6848ef293 100644 --- a/Ical.Net/CalendarExtensions.cs +++ b/Ical.Net/CalendarExtensions.cs @@ -11,20 +11,32 @@ namespace Ical.Net; public static class CalendarExtensions { /// - /// https://blogs.msdn.microsoft.com/shawnste/2006/01/24/iso-8601-week-of-year-format-in-microsoft-net/ + /// Calculate the week number according to ISO.8601, as required by RFC 5545. /// - public static int GetIso8601WeekOfYear(this System.Globalization.Calendar calendar, DateTime time, CalendarWeekRule rule, DayOfWeek firstDayOfWeek) + public static int GetIso8601WeekOfYear(this System.Globalization.Calendar calendar, DateTime time, DayOfWeek firstDayOfWeek) { - // Seriously cheat. If its Monday, Tuesday or Wednesday, then it'll - // be the same week# as whatever Thursday, Friday or Saturday are, - // and we always get those right - var day = calendar.GetDayOfWeek(time); - if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday) - { - time = time.AddDays(3); - } + // A week is defined as a + // seven day period, starting on the day of the week defined to be + // the week start(see WKST). Week number one of the calendar year + // is the first week that contains at least four (4) days in that + // calendar year. - // Return the week of our adjusted day - return calendar.GetWeekOfYear(time, rule, firstDayOfWeek); + // We add 3 to make sure the test date is in the 'right' year, because + // otherwise we might end up with week 53 in a year that only has 52. + var tTest = GetStartOfWeek(time, firstDayOfWeek).AddDays(3); + var res = calendar.GetWeekOfYear(tTest, CalendarWeekRule.FirstFourDayWeek, firstDayOfWeek); + + return res; + } + + /// + /// Calculate and return the date that represents the first day of the week the given date is + /// in, according to the week numbering required by RFC 5545. + /// + private static DateTime GetStartOfWeek(this DateTime t, DayOfWeek firstDayOfWeek) + { + var t0 = ((int) firstDayOfWeek) % 7; + var tn = ((int) t.DayOfWeek) % 7; + return t.AddDays(-((tn + 7 - t0) % 7)); } -} \ No newline at end of file +} diff --git a/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs b/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs index e5edcd30c..118bdbc0d 100644 --- a/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs +++ b/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using Ical.Net.DataTypes; using Ical.Net.Utility; @@ -400,7 +399,7 @@ private List GetWeekNoVariants(List dates, RecurrencePattern { var date = t; // Determine our current week number - var currWeekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); + var currWeekNo = Calendar.GetIso8601WeekOfYear(date, pattern.FirstDayOfWeek); while (currWeekNo > weekNo) { // If currWeekNo > weekNo, then we're likely at the start of a year @@ -408,7 +407,7 @@ private List GetWeekNoVariants(List dates, RecurrencePattern // we should be back to week 1, where we can easily make the calculation // to move to weekNo. date = date.AddDays(7); - currWeekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); + currWeekNo = Calendar.GetIso8601WeekOfYear(date, pattern.FirstDayOfWeek); } // Move ahead to the correct week of the year @@ -629,7 +628,7 @@ private List GetAbsWeekDays(DateTime date, WeekDay weekDay, Recurrence } else if (pattern.Frequency == FrequencyType.Weekly || pattern.ByWeekNo.Count > 0) { - var weekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); + var weekNo = Calendar.GetIso8601WeekOfYear(date, pattern.FirstDayOfWeek); // Go to the first day of the week date = date.AddDays(-GetWeekDayOffset(date, pattern.FirstDayOfWeek)); @@ -640,8 +639,8 @@ private List GetAbsWeekDays(DateTime date, WeekDay weekDay, Recurrence date = date.AddDays(1); } - var nextWeekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); - var currentWeekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); + var nextWeekNo = Calendar.GetIso8601WeekOfYear(date, pattern.FirstDayOfWeek); + var currentWeekNo = Calendar.GetIso8601WeekOfYear(date, pattern.FirstDayOfWeek); //When we manage weekly recurring pattern and we have boundary case: //Weekdays: Dec 31, Jan 1, Feb 1, Mar 1, Apr 1, May 1, June 1, Dec 31 - It's the 53th week of the year, but all another are 1st week number. @@ -655,7 +654,7 @@ private List GetAbsWeekDays(DateTime date, WeekDay weekDay, Recurrence } date = date.AddDays(7); - currentWeekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); + currentWeekNo = Calendar.GetIso8601WeekOfYear(date, pattern.FirstDayOfWeek); } } else if (pattern.Frequency == FrequencyType.Monthly || pattern.ByMonth.Count > 0) @@ -671,7 +670,7 @@ private List GetAbsWeekDays(DateTime date, WeekDay weekDay, Recurrence while (date.Month == month) { - var currentWeekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); + var currentWeekNo = Calendar.GetIso8601WeekOfYear(date, pattern.FirstDayOfWeek); if ((pattern.ByWeekNo.Count == 0 || pattern.ByWeekNo.Contains(currentWeekNo)) && (pattern.ByMonth.Count == 0 || pattern.ByMonth.Contains(date.Month)))