From 02861e3904ca94ebbd67dc590bfed1673f3fc2b8 Mon Sep 17 00:00:00 2001 From: Joachim Falk Date: Mon, 2 Sep 2019 21:32:12 +0200 Subject: [PATCH 1/2] Added testcase recurrence-mixed-with-and-without-tz --- .../recurrence-mixed-with-and-without-tz.ics | 49 +++++++++++++++++++ tests.py | 18 +++++++ 2 files changed, 67 insertions(+) create mode 100644 test_files/recurrence-mixed-with-and-without-tz.ics diff --git a/test_files/recurrence-mixed-with-and-without-tz.ics b/test_files/recurrence-mixed-with-and-without-tz.ics new file mode 100644 index 0000000..a99adc4 --- /dev/null +++ b/test_files/recurrence-mixed-with-and-without-tz.ics @@ -0,0 +1,49 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN + +BEGIN:VTIMEZONE +TZID:Europe/Berlin +BEGIN:STANDARD +DTSTART:19701025T030000 +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 +TZNAME:CET +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19700329T020000 +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 +TZNAME:CEST +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +END:DAYLIGHT +END:VTIMEZONE + +BEGIN:VEVENT +UID:24151946-f737-44b2-eeee-a85f560944b0 +DTSTAMP:19910520T070708Z +DTSTART;VALUE=DATE:19910520 +DTEND;VALUE=DATE:19910521 +CREATED:20101031T171909Z +DESCRIPTION: +EXDATE;VALUE=DATE-TIME:19920520T000000Z +RRULE:FREQ=YEARLY;WKST=MO;UNTIL=19940519T235959Z;BYMONTH=5;BYMONTHDAY=20 +SUMMARY:Test +END:VEVENT + +BEGIN:VEVENT +DTSTAMP:20171023T070708Z +CREATED:20171023T070428Z +UID:24151946-f737-44b2-9db5-a85f560944b0 +SEQUENCE:2 +LAST-MODIFIED:20171023T070708Z +SUMMARY:EES Praktikum +RRULE:FREQ=WEEKLY;UNTIL=20171115T120000Z;BYDAY=WE +EXDATE;VALUE=DATE:20171101 +DTSTART;TZID=Europe/Berlin:20171025T140000 +DTEND;TZID=Europe/Berlin:20171025T183000 +TRANSP:OPAQUE +END:VEVENT + +END:VCALENDAR diff --git a/tests.py b/tests.py index fcab478..a97630a 100644 --- a/tests.py +++ b/tests.py @@ -828,6 +828,24 @@ def test_recurrence_offset_naive(self): self.assertEqual(dates[1], datetime.datetime(2013, 1, 24, 0, 0)) self.assertEqual(dates[-1], datetime.datetime(2013, 3, 28, 0, 0)) + def test_recurrence_mixed_with_and_without_tz(self): + """ + Ensure recurring vevent with DTSTART and EXDATE not both have or + not have timezone information is parsing. + """ + test_file = get_test_file("recurrence-mixed-with-and-without-tz.ics") + vcalendar = base.readOne(test_file, validate=True) + expected = None + for vobj in vcalendar.getChildren(): + if vobj.name == 'VTIMEZONE': + expected = iter([ + [datetime.datetime(1991, 5, 20, 0, 0, 0), + datetime.datetime(1993, 5, 20, 0, 0, 0)], + [datetime.datetime(2017, 10, 25, 14, 0, 0, tzinfo=vobj.gettzinfo()), + datetime.datetime(2017, 11, 8, 14, 0, 0, tzinfo=vobj.gettzinfo())]]); + elif vobj.name == 'VEVENT': + dates = list(vobj.getrruleset(addRDate=True)) + self.assertEqual(dates, next(expected)) class TestChangeTZ(unittest.TestCase): """ From bde0814b3ef48e2eaf158a90a2c4fedda6261b00 Mon Sep 17 00:00:00 2001 From: Joachim Falk Date: Mon, 2 Sep 2019 23:08:08 +0200 Subject: [PATCH 2/2] Fixed exception can't compare offset-naive and offset-aware datetimes Fixed exception 'TypeError: can't compare offset-naive and offset-aware datetimes' triggered when DTSTART and EXDATE not both have or not have timez one information. Fix will propagte time zone information from from DTSTART to EXDATE if EXDATE does not have a TZ or strip the TZ from EXDATE when DTSTART does not have time zone information. --- vobject/icalendar.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/vobject/icalendar.py b/vobject/icalendar.py index a44fffb..8a26367 100644 --- a/vobject/icalendar.py +++ b/vobject/icalendar.py @@ -430,12 +430,23 @@ def getrruleset(self, addRDate=False): logging.error('failed to find DUE at all.') return None + if isinstance(dtstart, datetime.datetime): + dtstart_tzinfo = dtstart.tzinfo + else: + dtstart_tzinfo = None if name in DATENAMES: if type(line.value[0]) == datetime.datetime: - list(map(addfunc, line.value)) + for dt in line.value: + if dtstart_tzinfo is None: + addfunc(dt.replace(tzinfo=None)) + else: + addfunc(dt) elif type(line.value[0]) == datetime.date: for dt in line.value: - addfunc(datetime.datetime(dt.year, dt.month, dt.day)) + if isinstance(dtstart, datetime.datetime): + addfunc(dtstart.replace(dt.year, dt.month, dt.day)) + else: + addfunc(datetime.datetime(dt.year, dt.month, dt.day, tzinfo=dtstart_tzinfo)) else: # ignore RDATEs with PERIOD values for now pass