diff --git a/icalevents/icalparser.py b/icalevents/icalparser.py index fcff72c..32fb17d 100644 --- a/icalevents/icalparser.py +++ b/icalevents/icalparser.py @@ -340,7 +340,9 @@ def parse_events( # Deal with the fact that sometimes it's a list and # sometimes it's a singleton exlist = [] - if isinstance(component["EXDATE"], list): + if isinstance(component["EXDATE"], vDDDLists): + exlist = component["EXDATE"].dts + elif isinstance(component["EXDATE"], list): exlist = component["EXDATE"] else: exlist.append(component["EXDATE"]) @@ -487,13 +489,29 @@ def parse_rrule(component): def conform_until(until, dtstart): if type(dtstart) is datetime: - return ( + # If we have timezone defined adjust for daylight saving time + if type(until) is datetime: + until_offset = ( + until.astimezone(dtstart.tzinfo).utcoffset() + if until.tzinfo is not None and dtstart.tzinfo is not None + else None + ) + if until_offset is not None: + return until + abs(until_offset) + + offset = dtstart.tzinfo.utcoffset(dtstart) if dtstart.tzinfo else None + normalized_until = ( until.astimezone(UTC) if type(until) is datetime else datetime( year=until.year, month=until.month, day=until.day, tzinfo=UTC ) - ) + dtstart.tzinfo.utcoffset(dtstart) + ) + if offset: + return normalized_until + offset + + return normalized_until + elif type(dtstart) is date: return ( until.date() + timedelta(days=1) if type(until) is datetime else until diff --git a/test/test_data/multi_exdate_same_line_ms.ics b/test/test_data/multi_exdate_same_line_ms.ics new file mode 100644 index 0000000..f3808e6 --- /dev/null +++ b/test/test_data/multi_exdate_same_line_ms.ics @@ -0,0 +1,47 @@ +BEGIN:VCALENDAR +METHOD:PUBLISH +PRODID:Microsoft Exchange Server 2010 +VERSION:2.0 +X-WR-CALNAME:Calendar +BEGIN:VTIMEZONE +TZID:Eastern Standard Time +BEGIN:STANDARD +DTSTART:16010101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11 +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:16010101T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3 +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +DESCRIPTION:Test event that has an RRULE with multiple EXDATE +RRULE:FREQ=WEEKLY;UNTIL=20220429T150000Z;INTERVAL=1;BYDAY=FR;WKST=MO +EXDATE;TZID=Eastern Standard Time:20220318T110000,20220401T110000,20220408T + 110000 +UID:040000008200R00074P5O7101N82R00800000000R0Q793689428Q801000000000000000 + 010000000QOPN4SS024R0264S9P0Q7OOQQ16PN399 +SUMMARY:Recurring With Exclusions +DTSTART;TZID=Eastern Standard Time:20220311T110000 +DTEND;TZID=Eastern Standard Time:20220311T113000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20220330T125447Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +LOCATION:Microsoft Teams Meeting +X-MICROSOFT-CDO-APPT-SEQUENCE:0 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +X-MICROSOFT-CDO-ALLDAYEVENT:FALSE +X-MICROSOFT-CDO-IMPORTANCE:1 +X-MICROSOFT-CDO-INSTTYPE:1 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +END:VCALENDAR diff --git a/test/test_icalevents.py b/test/test_icalevents.py index bc4ed5f..8a899f9 100644 --- a/test/test_icalevents.py +++ b/test/test_icalevents.py @@ -776,3 +776,22 @@ class ICalEventsTests(unittest.TestCase): ) self.assertEqual(len(events), len(times)) + + def test_multi_exdate_same_line(self): + ical = "test/test_data/multi_exdate_same_line_ms.ics" + tz = gettz("America/New_York") + start = date(2022, 3, 1) + end = date(2022, 5, 1) + + evs = icalevents.events(file=ical, start=start, end=end) + + # parsing starts at 2022-03-01 + self.assertEqual(evs[0].start, datetime(2022, 3, 11, 11, 0, 0, tzinfo=tz)) + # 2022-03-18 is excluded by EXDATE rule + self.assertEqual(evs[1].start, datetime(2022, 3, 25, 11, 0, 0, tzinfo=tz)) + # 2022-04-01 is excluded by EXDATE rule + # 2022-04-08 is excluded by EXDATE rule + self.assertEqual(evs[2].start, datetime(2022, 4, 15, 11, 0, 0, tzinfo=tz)) + self.assertEqual(evs[3].start, datetime(2022, 4, 22, 11, 0, 0, tzinfo=tz)) + self.assertEqual(evs[4].start, datetime(2022, 4, 29, 11, 0, 0, tzinfo=tz)) + # parsing stops at 2022-05-01