Merge pull request #2153 from pbiering/issue-2151

Improve: sanitize item add timezone to EXDATE or RDATE if missing but DTSTART has
This commit is contained in:
Peter Bieringer
2026-06-07 09:49:45 +03:00
committed by GitHub
5 changed files with 177 additions and 1 deletions

View File

@@ -7,6 +7,7 @@
* Add: [sharing] conversion_bday_categories (customize)
* Add: [sharing] conversion_bday_age_max (limit in case of "age" placeholder is used which blocks using RRULE)
* Extension: [sharing/bday conversion]: add STATUS + CLASS fields
* Improve: sanitize item align timezone (add/remove) of EXDATE or RDATE with DTSTART
## 3.7.4
* Fix: sharing: PROPFIND returns now empty owner element in case of a mapped share as clients try PROPFIND on this not accessable href

View File

@@ -174,12 +174,26 @@ def check_and_sanitize_items(
# EXDATE has value DATE even if DTSTART/DTEND is DATE-TIME.
# The RFC is vaguely formulated on the issue.
# To resolve the issue convert EXDATE and RDATE to
# the same type as DTDSTART
# the same type as DTSTART
if hasattr(component, "dtstart"):
ref_date = component.dtstart.value
ref_value_param = component.dtstart.params.get("VALUE")
for dates in chain(component.contents.get("exdate", []),
component.contents.get("rdate", [])):
for i, date in enumerate(dates.value):
if type(ref_date) is datetime.datetime and type(date) is datetime.datetime:
if hasattr(ref_date, 'tzinfo') and ref_date.tzinfo is not None:
logger.trace("ITEM/check_and_sanitize_item: dtstart has tzinfo: '%s'", ref_date)
if hasattr(date, 'tzinfo') and date.tzinfo is None:
# Ensure that datetime.datetime object has timezone set if dtstart has
dates.value[i] = dates.value[i].replace(tzinfo=ref_date.tzinfo)
logger.trace("ITEM/check_and_sanitize_item: overtake missing tzinfo from dtstart: '%s' -> '%s'", date, dates.value[i])
elif (hasattr(ref_date, 'tzinfo') and ref_date.tzinfo is None) or not hasattr(ref_date, 'tzinfo'):
logger.trace("ITEM/check_and_sanitize_item: dtstart has no tzinfo: '%s'", ref_date)
if hasattr(date, 'tzinfo') and date.tzinfo is not None:
# Ensure that datetime.datetime object has no timezone set if dtstart has none
dates.value[i] = dates.value[i].replace(tzinfo=None)
logger.trace("ITEM/check_and_sanitize_item: remove existing tzinfo (dtstart has none): '%s' -> '%s'", date, dates.value[i])
if all(type(d) is type(ref_date) for d in dates.value):
continue
for i, date in enumerate(dates.value):

View File

@@ -0,0 +1,116 @@
BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:W. Europe Standard Time
BEGIN:STANDARD
DTSTART:16010101T030000
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T020000
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20260605T085814Z
LAST-MODIFIED:20260605T100722Z
DTSTAMP:20260605T100722Z
UID:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
SUMMARY:Billings-task-setting follow-up series
PRIORITY:5
STATUS:CONFIRMED
ORGANIZER;CN=XXXXXXXX XXXXX:mailto:XXXXXXX@XXXXXXXXXXXXXX.XXX
ATTENDEE;RSVP=TRUE;CN=XX;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPANT:mailto:XX@XXXXXXX.XXX
ATTENDEE;RSVP=TRUE;CN=XXXXXXXX XXXXXXX;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT:mailto:XXXXXXXXX@XXXXXXXXXXXXXX.XXX
ATTENDEE;RSVP=TRUE;CN=XXXXXX XXXXXXXX;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT:mailto:XXXXXXXXXX@XXXXXXXXXXXXXX.XXX
EXDATE:20260403T110000
RRULE:FREQ=WEEKLY;BYDAY=FR;UNTIL=20260731T090000Z
X-MOZ-LASTACK:20260605T100722Z
DTSTART;TZID=W. Europe Standard Time:20260123T110000
DTEND;TZID=W. Europe Standard Time:20260123T120000
CLASS:PUBLIC
DESCRIPTION;LANGUAGE=de-DE:Serie war letzte Woche ausgelaufen\n\nMicrosoft Teams meeting details redacted.\n
LOCATION;LANGUAGE=de-DE:Microsoft Teams-Besprechung
SEQUENCE:0
TRANSP:OPAQUE
X-MICROSOFT-CDO-ALLDAYEVENT:FALSE
X-MICROSOFT-CDO-APPT-SEQUENCE:0
X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE
X-MICROSOFT-CDO-IMPORTANCE:1
X-MICROSOFT-CDO-INSTTYPE:1
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
X-MICROSOFT-CDO-OWNERAPPTID:XXXXXXXXXX
X-MICROSOFT-DISALLOW-COUNTER:FALSE
X-MICROSOFT-DONOTFORWARDMEETING:FALSE
X-MICROSOFT-ISRESPONSEREQUESTED:TRUE
X-MICROSOFT-LOCATIONDISPLAYNAME:Microsoft Teams-Besprechung
X-MICROSOFT-LOCATIONS:[{"DisplayName":"Microsoft Teams-Besprechung"\,"LocationAnnotation":""\,"LocationUri":""\,"LocationStreet":""\,"LocationCity":""\,"LocationState":""\,"LocationCountry":""\,"LocationPostalCode":""\,"LocationFullAddress":""}]
X-MICROSOFT-LOCATIONSOURCE:None
X-MICROSOFT-ONLINEMEETINGINFORMATION:{"OnlineMeetingChannelId":null\,"OnlineMeetingProvider":3}
X-MICROSOFT-REQUESTEDATTENDANCEMODE:DEFAULT
X-MICROSOFT-SCHEDULINGSERVICEUPDATEURL:REDACTED
X-MICROSOFT-SKYPETEAMSMEETINGURL:REDACTED
X-MICROSOFT-SKYPETEAMSPROPERTIES:REDACTED
X-MOZ-GENERATION:17
X-MOZ-INVITED-ATTENDEE:mailto:XX@XXXXXXX.XXX
X-MOZ-RECEIVED-DTSTAMP:20260123T100333Z
X-MOZ-RECEIVED-SEQUENCE:0
BEGIN:VALARM
ACTION:DISPLAY
TRIGGER:-PT5M
DESCRIPTION:Mozilla Standardbeschreibung
END:VALARM
END:VEVENT
BEGIN:VEVENT
CREATED:20260605T085814Z
LAST-MODIFIED:20260130T093740Z
DTSTAMP:20260130T093740Z
UID:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
SUMMARY:Billings-task-setting follow-up series
PRIORITY:5
STATUS:CONFIRMED
RECURRENCE-ID;TZID=W. Europe Standard Time:20260130T110000
ORGANIZER;CN=XXXXXXXX XXXXX:mailto:XXXXXXX@XXXXXXXXXXXXXX.XXX
ATTENDEE;RSVP=TRUE;CN=XXXXXXXX XXXXXXX;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT:mailto:XXXXXXXXX@XXXXXXXXXXXXXX.XXX
ATTENDEE;RSVP=TRUE;CN=XXXXXX XXXXXXXX;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT:mailto:XXXXXXXXXX@XXXXXXXXXXXXXX.XXX
ATTENDEE;RSVP=TRUE;CN=XX;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPANT:mailto:XX@XXXXXXX.XXX
DTSTART;TZID=W. Europe Standard Time:20260130T103000
DTEND;TZID=W. Europe Standard Time:20260130T113000
CLASS:PUBLIC
DESCRIPTION:Serie war letzte Woche ausgelaufen\n\nMicrosoft Teams meeting details redacted.\n
LOCATION:Microsoft Teams-Besprechung
SEQUENCE:1
TRANSP:OPAQUE
X-MICROSOFT-CDO-ALLDAYEVENT:FALSE
X-MICROSOFT-CDO-APPT-SEQUENCE:1
X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE
X-MICROSOFT-CDO-IMPORTANCE:1
X-MICROSOFT-CDO-INSTTYPE:3
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
X-MICROSOFT-CDO-OWNERAPPTID:XXXXXXXXXX
X-MICROSOFT-DISALLOW-COUNTER:FALSE
X-MICROSOFT-DONOTFORWARDMEETING:FALSE
X-MICROSOFT-ISRESPONSEREQUESTED:TRUE
X-MICROSOFT-LOCATIONDISPLAYNAME:Microsoft Teams-Besprechung
X-MICROSOFT-LOCATIONS:[{"DisplayName":"Microsoft Teams-Besprechung"\,"LocationAnnotation":""\,"LocationUri":""\,"LocationStreet":""\,"LocationCity":""\,"LocationState":""\,"LocationCountry":""\,"LocationPostalCode":""\,"LocationFullAddress":""}]
X-MICROSOFT-LOCATIONSOURCE:None
X-MICROSOFT-ONLINEMEETINGINFORMATION:{"OnlineMeetingChannelId":null\,"OnlineMeetingProvider":3}
X-MICROSOFT-REQUESTEDATTENDANCEMODE:DEFAULT
X-MICROSOFT-SCHEDULINGSERVICEUPDATEURL:REDACTED
X-MICROSOFT-SKYPETEAMSMEETINGURL:REDACTED
X-MICROSOFT-SKYPETEAMSPROPERTIES:REDACTED
X-MOZ-GENERATION:0
X-MOZ-RECEIVED-DTSTAMP:20260130T093205Z
X-MOZ-RECEIVED-SEQUENCE:1
BEGIN:VALARM
ACTION:DISPLAY
TRIGGER:-PT5M
DESCRIPTION:Mozilla Standardbeschreibung
END:VALARM
END:VEVENT
END:VCALENDAR

View File

@@ -0,0 +1,33 @@
BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Paris
X-LIC-LOCATION:Europe/Paris
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:20130902T150157Z
LAST-MODIFIED:20130902T150158Z
DTSTAMP:20130902T150158Z
UID:event_mixed_datetime_and_date_exdate
SUMMARY:Event
DTSTART:20130901T180000
DTEND:20130901T190000
RRULE:FREQ=DAILY;COUNT=3
EXDATE:20130902T193000Z
END:VEVENT
END:VCALENDAR

View File

@@ -273,6 +273,18 @@ permissions: RrWw""")
event = get_file_content("event_exdate_without_rrule.ics")
self.put("/calendar.ics/event.ics", event)
def test_add_event_exdate_no_tz(self) -> None:
"""Test event where EXDATE has no tzinfo."""
self.mkcalendar("/calendar.ics/")
event = get_file_content("event_issue2151.ics")
self.put("/calendar.ics/event_issue2151.ics", event)
def test_add_event_dtstart_no_tz_exdate_tz(self) -> None:
"""Test event where DTSTART has no tzinfo but EXDATE."""
self.mkcalendar("/calendar.ics/")
event = get_file_content("event_mixed_datetime_and_date_exdate.ics")
self.put("/calendar.ics/event_mixed_datetime_and_date_exdate.ics", event)
def test_add_todo(self) -> None:
"""Add a todo."""
self.mkcalendar("/calendar.ics/")