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): """ 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