Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EXDATE should serialize TZID (worked in v4.1.11) #614

Open
RemcoBlok opened this issue Oct 18, 2024 · 6 comments
Open

EXDATE should serialize TZID (worked in v4.1.11) #614

RemcoBlok opened this issue Oct 18, 2024 · 6 comments
Labels

Comments

@RemcoBlok
Copy link

RemcoBlok commented Oct 18, 2024

I have an EXDATE at a specific time with a specific TZID. Presently ical.net does not serialize the TZID with the EXDATE. When deserializing the calendar this results in GetOccurrences() returning an occurrence on what was supposed to be the EXDATE had it not lost its TZID.

I first saw this issue in ical.net 4.2.0. It is also present in 4.3.1.

First of all, without serialization and deserialization, this passes:

Example 1

        [Fact]
        public void ShouldNotOccurOnExceptionDate()
        {
            // Arrange
            Guid id = Guid.NewGuid();
            string timeZoneId = TimeZoneInfo.Local.Id; // GMT Standard Time
            DateTime start = new DateTime(2024, 10, 19, 18, 0, 0, DateTimeKind.Local);
            DateTime end = new DateTime(2024, 10, 19, 19, 0, 0, DateTimeKind.Local);
            DateTime exceptionDate = new DateTime(2024, 10, 19, 21, 0, 0, DateTimeKind.Local);

            RecurrencePattern recurrencePattern = new RecurrencePattern(FrequencyType.Hourly);
            recurrencePattern.Count = 2;
            recurrencePattern.Interval = 3;

            IDateTime exceptionOccurrsOn = new CalDateTime(exceptionDate, timeZoneId);

            PeriodList exceptionDates = new PeriodList();
            exceptionDates.Add(exceptionOccurrsOn);

            CalendarEvent myRecurringEvent = new CalendarEvent();
            myRecurringEvent.Summary = "My Recurring Event";
            myRecurringEvent.Uid = id.ToString();
            myRecurringEvent.Start = new CalDateTime(start, timeZoneId);
            myRecurringEvent.End = new CalDateTime(end, timeZoneId);
            myRecurringEvent.RecurrenceRules.Add(recurrencePattern);
            myRecurringEvent.ExceptionDates.Add(exceptionDates);

            Calendar calendar = new Calendar();
            calendar.Events.Add(myRecurringEvent);

            // Act
            DateTime date = new DateTime(2024, 10, 19);
            HashSet<Occurrence> occurrences = calendar.GetOccurrences<CalendarEvent>(date);

            // Assert
            Assert.Single(occurrences);
        }

This correctly returns a single occurrence. It does not return an occurrence on the EXDATE.

Add serialization and deserializtion, and this fails:

Example 2

        [Fact]
        public void ShouldNotOccurOnExceptionDateWithSerialization()
        {
            // Arrange
            Guid id = Guid.NewGuid();
            string timeZoneId = TimeZoneInfo.Local.Id; // GMT Standard Time
            DateTime start = new DateTime(2024, 10, 19, 18, 0, 0, DateTimeKind.Local);
            DateTime end = new DateTime(2024, 10, 19, 19, 0, 0, DateTimeKind.Local);
            DateTime exceptionDate = new DateTime(2024, 10, 19, 21, 0, 0, DateTimeKind.Local);
            
            RecurrencePattern recurrencePattern = new RecurrencePattern(FrequencyType.Hourly);
            recurrencePattern.Count = 2;
            recurrencePattern.Interval = 3;

            IDateTime exceptionOccurrsOn = new CalDateTime(exceptionDate, timeZoneId);
            
            PeriodList exceptionDates = new PeriodList();
            exceptionDates.Add(exceptionOccurrsOn);

            CalendarEvent myRecurringEvent = new CalendarEvent();
            myRecurringEvent.Summary = "My Recurring Event";
            myRecurringEvent.Uid = id.ToString();
            myRecurringEvent.Start = new CalDateTime(start, timeZoneId);
            myRecurringEvent.End = new CalDateTime(end, timeZoneId);
            myRecurringEvent.RecurrenceRules.Add(recurrencePattern);
            myRecurringEvent.ExceptionDates.Add(exceptionDates);

            Calendar calendar = new Calendar();
            calendar.Events.Add(myRecurringEvent);

            // Act
            CalendarSerializer serializer = new CalendarSerializer();
            string schedulerData = serializer.SerializeToString(calendar);

            Calendar deserializedCalendar = Calendar.Load(schedulerData);

            DateTime date = new DateTime(2024, 10, 19);
            HashSet<Occurrence> occurrences = deserializedCalendar.GetOccurrences<CalendarEvent>(date);

            // Assert
            Assert.Single(occurrences);
        }

This now returns two occurrences instead of one. It returns an occurrence on the EXDATE, or what was supposed to be the EXDATE if it had not lost its TZID. It turns out ical.net does not serialize the TZID with the EXDATE.

BEGIN:VCALENDAR
PRODID:-//github.com/ical-org/ical.net//NONSGML ical.net 4.0//EN
VERSION:2.0
BEGIN:VEVENT
DTEND;TZID=GMT Standard Time:20241019T190000
DTSTAMP:20241018T083657Z
DTSTART;TZID=GMT Standard Time:20241019T180000
EXDATE:20241019T210000
RRULE:FREQ=HOURLY;INTERVAL=3;COUNT=2
SEQUENCE:0
SUMMARY:My Recurring Event
UID:80740413-d27d-4837-b79a-eac1563bf7c8
END:VEVENT
END:VCALENDAR

I attempted the following temporary workaround. I convert the EXDATE to UTC, while keeping the CalendarEvent Start in GMT Standard Time, because I want to preserve and roundtrip the original TZID, but this still fails:

Example 3

        [Fact]
        public void ShouldNotOccurOnUtcExceptionDateWithSerialization()
        {
            // Arrange
            Guid id = Guid.NewGuid();
            string timeZoneId = TimeZoneInfo.Local.Id; // GMT Standard Time
            DateTime start = new DateTime(2024, 10, 19, 18, 0, 0, DateTimeKind.Local);
            DateTime end = new DateTime(2024, 10, 19, 19, 0, 0, DateTimeKind.Local);
            DateTime exceptionDate = new DateTime(2024, 10, 19, 21, 0, 0, DateTimeKind.Local);

            RecurrencePattern recurrencePattern = new RecurrencePattern(FrequencyType.Hourly);
            recurrencePattern.Count = 2;
            recurrencePattern.Interval = 3;

            DateTime exceptinDateUtc = exceptionDate.ToUniversalTime();
            IDateTime exceptionOccurrsOn = new CalDateTime(exceptinDateUtc, TimeZoneInfo.Utc.Id);

            PeriodList exceptionDates = new PeriodList();
            exceptionDates.Add(exceptionOccurrsOn);

            CalendarEvent myRecurringEvent = new CalendarEvent();
            myRecurringEvent.Summary = "My Recurring Event";
            myRecurringEvent.Uid = id.ToString();
            myRecurringEvent.Start = new CalDateTime(start, timeZoneId);
            myRecurringEvent.End = new CalDateTime(end, timeZoneId);
            myRecurringEvent.RecurrenceRules.Add(recurrencePattern);
            myRecurringEvent.ExceptionDates.Add(exceptionDates);

            Calendar calendar = new Calendar();
            calendar.Events.Add(myRecurringEvent);

            // Act
            CalendarSerializer serializer = new CalendarSerializer();
            string schedulerData = serializer.SerializeToString(calendar);

            Calendar deserializedCalendar = Calendar.Load(schedulerData);

            DateTime date = new DateTime(2024, 10, 19);
            HashSet<Occurrence> occurrences = deserializedCalendar.GetOccurrences<CalendarEvent>(date);

            // Assert
            Assert.Single(occurrences);
        }
BEGIN:VCALENDAR
PRODID:-//github.com/ical-org/ical.net//NONSGML ical.net 4.0//EN
VERSION:2.0
BEGIN:VEVENT
DTEND;TZID=GMT Standard Time:20241019T190000
DTSTAMP:20241018T083839Z
DTSTART;TZID=GMT Standard Time:20241019T180000
EXDATE:20241019T200000Z
RRULE:FREQ=HOURLY;INTERVAL=3;COUNT=2
SEQUENCE:0
SUMMARY:My Recurring Event
UID:c9f3a28d-97d6-43f7-872e-6cd79f67093d
END:VEVENT
END:VCALENDAR

This still returns two occurrences instead of one. This is possibly a second issue in ical.net, separate from the issue that ical.net does not serialize the TZID of an EXDATE. It seems the ical.net occurrence evaluator does not handle an EXDATE in a time zone that does not equal the time zone of the CalendarEvent Start.

A final workaround that does work is to convert the EXDATE back to the TZID of the CalendarEvent Start after deserializing:

Example 4

        [Fact]
        public void ShouldNotOccurOnUtcExceptionDateWithSerializationAndFixingTimeZone()
        {
            // Arrange
            Guid id = Guid.NewGuid();
            string timeZoneId = TimeZoneInfo.Local.Id; // GMT Standard Time
            DateTime start = new DateTime(2024, 10, 19, 18, 0, 0, DateTimeKind.Local);
            DateTime end = new DateTime(2024, 10, 19, 19, 0, 0, DateTimeKind.Local);
            DateTime exceptionDate = new DateTime(2024, 10, 19, 21, 0, 0, DateTimeKind.Local);

            RecurrencePattern recurrencePattern = new RecurrencePattern(FrequencyType.Hourly);
            recurrencePattern.Count = 2;
            recurrencePattern.Interval = 3;

            DateTime exceptinDateUtc = exceptionDate.ToUniversalTime();
            IDateTime exceptionOccurrsOn = new CalDateTime(exceptinDateUtc, TimeZoneInfo.Utc.Id);

            PeriodList exceptionDates = new PeriodList();
            exceptionDates.Add(exceptionOccurrsOn);

            CalendarEvent myRecurringEvent = new CalendarEvent();
            myRecurringEvent.Summary = "My Recurring Event";
            myRecurringEvent.Uid = id.ToString();
            myRecurringEvent.Start = new CalDateTime(start, timeZoneId);
            myRecurringEvent.End = new CalDateTime(end, timeZoneId);
            myRecurringEvent.RecurrenceRules.Add(recurrencePattern);
            myRecurringEvent.ExceptionDates.Add(exceptionDates);

            Calendar calendar = new Calendar();
            calendar.Events.Add(myRecurringEvent);

            // Act
            CalendarSerializer serializer = new CalendarSerializer();
            string schedulerData = serializer.SerializeToString(calendar);

            Calendar deserializedCalendar = Calendar.Load(schedulerData);

            CalendarEvent myDeserialezedEvent = deserializedCalendar.Events[0];

            PeriodList deserializedExceptionDates = myDeserialezedEvent.ExceptionDates[0];
            Period deserializedExceptionDate = deserializedExceptionDates[0];

            deserializedExceptionDate.StartTime = deserializedExceptionDate.StartTime.ToTimeZone(myDeserialezedEvent.Start.TzId);

            DateTime date = new DateTime(2024, 10, 19);
            HashSet<Occurrence> occurrences = deserializedCalendar.GetOccurrences<CalendarEvent>(date);

            // Assert
            Assert.Single(occurrences);
        }

Not a good workaround. Another possible workaround is to not preserve any time zone information and convert everything to UTC. Also not a good workaround.

Of course, most importantly, ical.net should serialize the TZID with the EXDATE.

@axunonb
Copy link
Collaborator

axunonb commented Oct 20, 2024

@RemcoBlok Thanks for the really helpful description of the issue. It deserves a gold medal.
EXDATE will require fundamental refactoring.

@RemcoBlok
Copy link
Author

RemcoBlok commented Oct 21, 2024

@axunonb Thanks for looking into it.

I realise a better unit test is one that does not rely on the local time zone. A build server in the UTC timezone may unknowingly produce false positives if the test uses the local time zone. So the test that should pass, but currently fails is:

        [Fact]
        public void ShouldNotOccurOnExceptionDateWithSerializationNotLocal()
        {
            // Arrange
            Guid id = Guid.NewGuid();
            string timeZoneId = "GMT Standard Time";
            if (timeZoneId == TimeZoneInfo.Local.Id)
            {
                timeZoneId = "Pacific Standard Time";
            }

            DateTime start = new DateTime(2024, 10, 19, 18, 0, 0, DateTimeKind.Unspecified);
            DateTime end = new DateTime(2024, 10, 19, 19, 0, 0, DateTimeKind.Unspecified);
            DateTime exceptionDate = new DateTime(2024, 10, 19, 21, 0, 0, DateTimeKind.Unspecified);

            RecurrencePattern recurrencePattern = new RecurrencePattern(FrequencyType.Hourly);
            recurrencePattern.Count = 2;
            recurrencePattern.Interval = 3;

            IDateTime exceptionOccurrsOn = new CalDateTime(exceptionDate, timeZoneId);

            PeriodList exceptionDates = new PeriodList();
            exceptionDates.Add(exceptionOccurrsOn);

            CalendarEvent myRecurringEvent = new CalendarEvent();
            myRecurringEvent.Summary = "My Recurring Event";
            myRecurringEvent.Uid = id.ToString();
            myRecurringEvent.Start = new CalDateTime(start, timeZoneId);
            myRecurringEvent.End = new CalDateTime(end, timeZoneId);
            myRecurringEvent.RecurrenceRules.Add(recurrencePattern);
            myRecurringEvent.ExceptionDates.Add(exceptionDates);

            Calendar calendar = new Calendar();
            calendar.Events.Add(myRecurringEvent);

            // Act
            CalendarSerializer serializer = new CalendarSerializer();
            string schedulerData = serializer.SerializeToString(calendar);

            Calendar deserializedCalendar = Calendar.Load(schedulerData);

            DateTime date = new DateTime(2024, 10, 19);
            HashSet<Occurrence> occurrences = deserializedCalendar.GetOccurrences<CalendarEvent>(date);

            // Assert
            Assert.Single(occurrences);
        }

@RemcoBlok
Copy link
Author

RemcoBlok commented Oct 21, 2024

I wrote similar unit tests using RecurrencePattern.Until instead of EXDATE. As mentioned in the linked issue #588 RecurrencePattern.Until also does not serialize a TZID either. However, the icalendar standard seems to suggest RecurrencePattern.Until should always be in UTC if the Calendar Event DtStart specified a TZID (the standard does not say that about EXDATE). However, if you do set RecurrencePattern.Until in UTC after serializing and deserializing you get an occurrence after the RecurrencePattern.Until.

The following test should pass but currently fails:

        [Fact]
        public void ShouldNotOccurHourlyAfterUtcUntilWithSerializationNotLocal()
        {
            // Arrange
            Calendar calendar = new Calendar();

            Guid id = Guid.NewGuid();
            string timeZoneId = "GMT Standard Time";
            if (timeZoneId == TimeZoneInfo.Local.Id)
            {
                timeZoneId = "Pacific Standard Time";
            }

            DateTime start = new DateTime(2024, 10, 19, 23, 0, 0, DateTimeKind.Unspecified);
            DateTime end = new DateTime(2024, 10, 20, 00, 0, 0, DateTimeKind.Unspecified);
            DateTime until = new DateTime(2024, 10, 19, 23, 59, 59, DateTimeKind.Unspecified);

            IDateTime calUntil = new CalDateTime(until, timeZoneId);

            RecurrencePattern recurrencePattern = new RecurrencePattern(FrequencyType.Hourly);
            recurrencePattern.Until = calUntil.AsUtc;
            recurrencePattern.Interval = 2;

            CalendarEvent myRecurringEvent = new CalendarEvent();
            myRecurringEvent.Summary = "My Recurring Event";
            myRecurringEvent.Uid = id.ToString();
            myRecurringEvent.Start = new CalDateTime(start, timeZoneId);
            myRecurringEvent.End = new CalDateTime(end, timeZoneId);
            myRecurringEvent.RecurrenceRules.Add(recurrencePattern);

            calendar.Events.Add(myRecurringEvent);

            // Act
            CalendarSerializer serializer = new CalendarSerializer();
            string schedulerData = serializer.SerializeToString(calendar);

            Calendar deserializedCalendar = Calendar.Load(schedulerData);

            DateTime startTime = new DateTime(2024, 10, 19);
            DateTime endTime = startTime.AddDays(2).AddTicks(-1);
            HashSet<Occurrence> occurrences = deserializedCalendar.GetOccurrences<CalendarEvent>(startTime, endTime);

            // Assert
            Assert.Single(occurrences);
        }

So after deserializing you need to convert the RecurrencePattern.Until back to the time zone of the Calendar Event DtStart.

In addition, for an All-Day event, RecurrencePattern.Until should not have a time component at all.

@axunonb
Copy link
Collaborator

axunonb commented Oct 21, 2024

I specified some test cases and asked Co-Pilot for the ICS and the expected recurring results. No that bad, is it?
@RemcoBlok Can you think of more to add in terms of EXDATE variations?

Unit test 1: Single Exclusion Date

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//github.com/ical-org/ical.net//NONSGML ical.net 4.0//EN
BEGIN:VEVENT
UID:[email protected]
DTSTAMP:20231021T162159Z
DTSTART;TZID=Europe/Berlin:20231025T090000
DTEND;TZID=Europe/Berlin:20231025T100000
RRULE:FREQ=WEEKLY;COUNT=10
EXDATE;TZID=Europe/Berlin:20231029T090000
SUMMARY:Weekly Meeting
END:VEVENT
END:VCALENDAR

Recurrences in UTC
October 25, 2023: 07:00 - 08:00 UTC
November 1, 2023: 08:00 - 09:00 UTC
November 8, 2023: 08:00 - 09:00 UTC
November 15, 2023: 08:00 - 09:00 UTC
November 22, 2023: 08:00 - 09:00 UTC
November 29, 2023: 08:00 - 09:00 UTC
December 6, 2023: 08:00 - 09:00 UTC
December 13, 2023: 08:00 - 09:00 UTC
December 20, 2023: 08:00 - 09:00 UTC
Explanation
The event starts on October 25, 2023, at 09:00 AM in Europe/Berlin time zone, which is 07:00 UTC.
The EXDATE property excludes October 29, 2023, at 09:00 AM in Europe/Berlin time zone (08:00 UTC).
The recurrences are adjusted for the switch from Daylight Saving Time to Standard Time, which occurs on October 29, 2023, in the Europe/Berlin time zone.

Unit test 2: Multiple Exclusion Dates

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//github.com/ical-org/ical.net//NONSGML ical.net 4.0//EN
BEGIN:VEVENT
UID:[email protected]
DTSTAMP:20231021T162159Z
DTSTART;TZID=Europe/Berlin:20231025T090000
DTEND;TZID=Europe/Berlin:20231025T100000
RRULE:FREQ=WEEKLY;COUNT=10
EXDATE;TZID=Europe/Berlin:20231029T090000,20231105T090000,20231112T090000
SUMMARY:Weekly Meeting
END:VEVENT
END:VCALENDAR

Recurrences in UTC
October 25, 2023: 07:00 - 08:00 UTC
November 1, 2023: 08:00 - 09:00 UTC
November 8, 2023: 08:00 - 09:00 UTC
November 15, 2023: 08:00 - 09:00 UTC
November 22, 2023: 08:00 - 09:00 UTC
November 29, 2023: 08:00 - 09:00 UTC
December 6, 2023: 08:00 - 09:00 UTC
Explanation
The event starts on October 25, 2023, at 09:00 AM in Europe/Berlin time zone, which is 07:00 UTC.
The EXDATE property excludes October 29, 2023, at 09:00 AM in Europe/Berlin time zone (07:00 UTC), November 5, 2023, at 09:00 AM in Europe/Berlin time zone (08:00 UTC), and November 12, 2023, at 09:00 AM in Europe/Berlin time zone (08:00 UTC).
The recurrences are adjusted for the switch from Daylight Saving Time to Standard Time, which occurs on October 29, 2023, in the Europe/Berlin time zone.

Unit test 3: EXDATE with DATE Value Type

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//github.com/ical-org/ical.net//NONSGML ical.net 4.0//EN
BEGIN:VEVENT
UID:[email protected]
DTSTAMP:20231021T162159Z
DTSTART;VALUE=DATE:20231025
RRULE:FREQ=DAILY;COUNT=5
EXDATE;VALUE=DATE:20231027
SUMMARY:Daily Meeting
END:VEVENT
END:VCALENDAR

Recurrences in UTC
October 25, 2023: All day (UTC)
October 26, 2023: All day (UTC)
October 28, 2023: All day (UTC)
October 29, 2023: All day (UTC)
October 30, 2023: All day (UTC)
Explanation
The event starts on October 25, 2023, and recurs daily for 5 occurrences.
The EXDATE property excludes October 27, 2023, from the recurrence.
Since DTSTART is specified as a date without a time, each occurrence is considered an all-day event in UTC.

Unit test 4: EXDATE with Multiple Time Zones

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//github.com/ical-org/ical.net//NONSGML ical.net 4.0//EN
BEGIN:VEVENT
UID:[email protected]
DTSTAMP:20231021T162159Z
DTSTART;TZID=America/New_York:20231025T090000
DTEND;TZID=America/New_York:20231025T100000
RRULE:FREQ=WEEKLY;COUNT=10
EXDATE;TZID=America/New_York:20231029T090000
EXDATE;TZID=Europe/London:20231105T140000
SUMMARY:Weekly Meeting
END:VEVENT
END:VCALENDAR

Recurrences in UTC
October 25, 2023: 13:00 - 14:00 UTC
November 1, 2023: 13:00 - 14:00 UTC
November 8, 2023: 14:00 - 15:00 UTC
November 15, 2023: 14:00 - 15:00 UTC
November 22, 2023: 14:00 - 15:00 UTC
November 29, 2023: 14:00 - 15:00 UTC
December 6, 2023: 14:00 - 15:00 UTC
December 13, 2023: 14:00 - 15:00 UTC
Explanation
The event starts on October 25, 2023, at 09:00 AM in America/New_York time zone, which is 13:00 UTC.
The EXDATE property excludes October 29, 2023, at 09:00 AM in America/New_York time zone (13:00 UTC) and November 5, 2023, at 14:00 in Europe/London time zone (14:00 UTC).

Unit test 5: EXDATE with UTC Time and DTSTART with Time Zone

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//github.com/ical-org/ical.net//NONSGML ical.net 4.0//EN
BEGIN:VEVENT
UID:[email protected]
DTSTAMP:20231021T162159Z
DTSTART;TZID=America/New_York:20231025T090000
DTEND;TZID=America/New_York:20231025T100000
RRULE:FREQ=WEEKLY;COUNT=10
EXDATE:20231029T130000Z
EXDATE:20231105T140000Z
SUMMARY:Weekly Meeting
END:VEVENT
END:VCALENDAR

Expected recurrences in UTC
October 25, 2023: 13:00 - 14:00 UTC
November 1, 2023: 13:00 - 14:00 UTC
November 8, 2023: 14:00 - 15:00 UTC
November 15, 2023: 14:00 - 15:00 UTC
November 22, 2023: 14:00 - 15:00 UTC
November 29, 2023: 14:00 - 15:00 UTC
December 6, 2023: 14:00 - 15:00 UTC
December 13, 2023: 14:00 - 15:00 UTC
Explanation
The event starts on October 25, 2023, at 09:00 AM in America/New_York time zone, which is 13:00 UTC.
The EXDATE property excludes October 29, 2023, at 13:00 UTC and November 5, 2023, at 14:00 UTC.
The recurrences are adjusted for the switch from Daylight Saving Time to Standard Time, which occurs on November 5, 2023, in the America/New_York time zone.

@axunonb axunonb changed the title EXDATE should serialize TZID EXDATE should serialize TZID (> v4.2.0, worked before) Oct 21, 2024
@axunonb axunonb changed the title EXDATE should serialize TZID (> v4.2.0, worked before) EXDATE should serialize TZID Oct 21, 2024
@axunonb
Copy link
Collaborator

axunonb commented Oct 21, 2024

I first saw this issue in ical.net 4.2.0. It is also present in 4.3.1.

Installed v4.1.11 locally, where the test from example 2 failed.

Gave it a another try on dotnetfiddle: https://dotnetfiddle.net/016xWU referencing Ical.Net v4.1.11 under NetCore3.1
Example 2: assertion succeeds indeed. Strange.

BEGIN:VCALENDAR
PRODID:-//github.com/rianjs/ical.net//NONSGML ical.net 4.0//EN
VERSION:2.0
BEGIN:VEVENT
DTEND:20241019T190000Z
DTSTAMP:20241021T225931Z
DTSTART:20241019T180000Z
EXDATE:20241019T210000Z
RRULE:FREQ=HOURLY;INTERVAL=3;COUNT=2
SEQUENCE:0
SUMMARY:My Recurring Event
UID:d9a9e96b-3d53-4001-995a-02afc3643e69
END:VEVENT
END:VCALENDAR 

We'll see whether this finding can be helpful

@axunonb axunonb changed the title EXDATE should serialize TZID EXDATE should serialize TZID (worked in v4.11) Oct 21, 2024
@axunonb axunonb changed the title EXDATE should serialize TZID (worked in v4.11) EXDATE should serialize TZID (worked in v4.1.11) Oct 22, 2024
@RemcoBlok
Copy link
Author

@axunonb Thanks for looking into this so quickly. The Co-Pilot generated occurrences look good. I cannot think of other test cases for EXDATE. I suppose similar unit test variations apply to RecurrenceRule.Until as well?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants