diff --git a/test_files/thunderbird_duration_pt0s_workaround.ics b/test_files/thunderbird_duration_pt0s_workaround.ics new file mode 100644 index 0000000..2adb7c1 --- /dev/null +++ b/test_files/thunderbird_duration_pt0s_workaround.ics @@ -0,0 +1,69 @@ +BEGIN:VCALENDAR +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN +VERSION:2.0 + +BEGIN:VTIMEZONE +TZID:Europe/Berlin +BEGIN:DAYLIGHT +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +TZNAME:CEST +DTSTART:19700329T020000 +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +TZNAME:CET +DTSTART:19701025T030000 +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 +END:STANDARD +END:VTIMEZONE + +BEGIN:VEVENT +CREATED:20190812T160437Z +LAST-MODIFIED:20190813T140315Z +DTSTAMP:20190813T140315Z +UID:a13cca03-860a-4094-b162-e06f161a7681 +SUMMARY:flummy +EXDATE:20190826T170000Z +RRULE:FREQ=WEEKLY;UNTIL=20190923T170000Z;INTERVAL=2 +X-MOZ-LASTACK:20190812T164503Z +DTSTART;TZID=Europe/Berlin:20190812T190000 +DTEND;TZID=Europe/Berlin:20190812T200000 +DESCRIPTION:descr +LOCATION:location +SEQUENCE:2 +TRANSP:OPAQUE +X-MOZ-GENERATION:5 +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER;VALUE=DURATION:-PT15M +DESCRIPTION:Default Mozilla Description +END:VALARM +END:VEVENT + +BEGIN:VEVENT +CREATED:20190813T140257Z +LAST-MODIFIED:20190813T140315Z +DTSTAMP:20190813T140315Z +UID:a13cca03-860a-4094-b162-e06f161a7681 +SUMMARY:flummy +RECURRENCE-ID;TZID=Europe/Berlin:20190909T190000 +DTSTART;TZID=Europe/Berlin:20190909T130000 +DTEND;TZID=Europe/Berlin:20190909T140000 +DESCRIPTION:descr +LOCATION:location +SEQUENCE:3 +TRANSP:OPAQUE +X-MOZ-GENERATION:5 +DURATION:PT0S +CLASS: +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER;VALUE=DURATION:-PT15M +DESCRIPTION:Default Mozilla Description +END:VALARM +END:VEVENT + +END:VCALENDAR diff --git a/tests.py b/tests.py index fcab478..339f70f 100644 --- a/tests.py +++ b/tests.py @@ -444,6 +444,14 @@ def test_parseParams(self): ['NEXT', 'Nope'], ['BAR']] ) + def test_thunderbird_duration_pt0s_workaround(self): + """ + Test parsing thundebird generated ics streams having both a duration of + 0 seconds and a dtend attribute. + """ + cal = get_test_file("thunderbird_duration_pt0s_workaround.ics") + c = base.readOne(cal, validate=True) + class TestVcards(unittest.TestCase): """ diff --git a/vobject/icalendar.py b/vobject/icalendar.py index a44fffb..3ee5839 100644 --- a/vobject/icalendar.py +++ b/vobject/icalendar.py @@ -1161,13 +1161,23 @@ class VEvent(RecurringBehavior): @classmethod def validate(cls, obj, raiseException, *args): if 'dtend' in obj.contents and 'duration' in obj.contents: - if raiseException: - m = "VEVENT components cannot contain both DTEND and DURATION\ - components" - raise ValidateError(m) - return False - else: - return super(VEvent, cls).validate(obj, raiseException, *args) + # Hack: Thunderbird's lightning calendar sometimes generates + # VEvents with dtend and a zero duration. Clean up these errors. + ignore = True + for duration in obj.contents.get('duration', ()): + duration.transformToNative() + if duration.value != datetime.timedelta(0): + ignore = False + if ignore: + # Delete redundant zero duration + del obj.contents['duration'] + else: + if raiseException: + m = "VEVENT components cannot contain both DTEND and DURATION\ + components" + raise ValidateError(m) + return False + return super(VEvent, cls).validate(obj, raiseException, *args) registerBehavior(VEvent)