Fix for TZID forward references to VTIMEZONE

pull/792/head
David Martin 2025-03-22 13:21:38 -04:00
rodzic d22fb38c3f
commit 88b8d62df5
4 zmienionych plików z 108 dodań i 11 usunięć

Wyświetl plik

@ -18,6 +18,7 @@ New features:
Bug fixes:
- Fix to permit TZID forward references to VTIMEZONEs
- ...
6.1.2 (2025-03-19)
@ -137,9 +138,7 @@ New features:
Bug fixes:
- Fix link to stable release of tox in documentation.
- Fix a bad ``bytes`` replace in ``unescape_char``.
- Handle ``ValueError`` in ``vBinary.from_ical``.
- Ignore the BOM character in incorrectly encoded ics files.
- Fix a bad bytes replace in unescape_char.
6.0.0a0 (2024-07-03)
--------------------

Wyświetl plik

@ -730,7 +730,7 @@ class Event(Component):
'RSTATUS', 'RELATED', 'RESOURCES', 'RDATE', 'RRULE'
)
ignore_exceptions = True
@property
def alarms(self) -> Alarms:
"""Compute the alarm times for this component.
@ -1007,7 +1007,7 @@ class Journal(Component):
@property
def start(self) -> date:
"""The start of the Journal.
The "DTSTART"
property is used to specify the calendar date with which the
journal entry is associated.
@ -1016,14 +1016,14 @@ class Journal(Component):
if start is None:
raise IncompleteComponent("No DTSTART given.")
return start
@start.setter
def start(self, value: datetime|date) -> None:
"""Set the start of the journal."""
self.DTSTART = value
end = start
@property
def duration(self) -> timedelta:
"""The journal has no duration: timedelta(0)."""
@ -1565,20 +1565,20 @@ class Alarm(Component):
if trigger is None:
raise ValueError("You must set a TRIGGER before setting the RELATED parameter.")
trigger.params["RELATED"] = value
class Triggers(NamedTuple):
"""The computed times of alarm triggers.
start - triggers relative to the start of the Event or Todo (timedelta)
end - triggers relative to the end of the Event or Todo (timedelta)
absolute - triggers at a datetime in UTC
"""
start: tuple[timedelta]
end: tuple[timedelta]
absolute: tuple[datetime]
@property
def triggers(self):
"""The computed triggers of an Alarm.
@ -1638,6 +1638,38 @@ class Calendar(Component):
"""Return the calendar example with the given name."""
return cls.from_ical(get_example("calendars", name))
@classmethod
def from_ical(cls, st, multiple=False):
comps = Component.from_ical(st, multiple=True)
all_timezones_so_far = True
for comp in comps:
for component in comp.walk():
if type(component) == Timezone:
if all_timezones_so_far:
pass
else:
# If a preceding component refers to a VTIMEZONE defined later in the source st
# (forward references are allowed by RFC 5545), then the earlier component may have
# the wrong timezone attached.
# However, during computation of comps, all VTIMEZONEs observed do end up in
# the timezone cache. So simply re-running from_ical will rely on the cache
# for those forward references to produce the correct result.
# See test_create_america_new_york_forward_reference.
return Component.from_ical(st, multiple)
else:
all_timezones_so_far = False
# No potentially forward VTIMEZONEs to worry about
if multiple:
return comps
if len(comps) > 1:
raise ValueError(cls._format_error(
'Found multiple components where only one is allowed', st))
if len(comps) < 1:
raise ValueError(cls._format_error(
'Found no components where exactly one is required', st))
return comps[0]
@property
def events(self) -> list[Event]:
"""All event components in the calendar.

Wyświetl plik

@ -0,0 +1,61 @@
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:noend123
DTSTART;TZID=custom_America/New_York_Forward_reference;VALUE=DATE-TIME:20140829T080000
DTSTART;TZID=custom_America/New_York_Forward_reference;VALUE=DATE-TIME:20140829T100000
SUMMARY:an event with a custom tz name
END:VEVENT
BEGIN:VTIMEZONE
TZID:custom_America/New_York_Forward_reference
LAST-MODIFIED:20050809T050000Z
BEGIN:DAYLIGHT
DTSTART:19670430T020000
RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19730429T070000Z
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
TZNAME:EDT
END:DAYLIGHT
BEGIN:STANDARD
DTSTART:19671029T020000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=20061029T060000Z
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
TZNAME:EST
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19740106T020000
RDATE:19750223T020000
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
TZNAME:EDT
END:DAYLIGHT
BEGIN:DAYLIGHT
DTSTART:19760425T020000
RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19860427T070000Z
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
TZNAME:EDT
END:DAYLIGHT
BEGIN:DAYLIGHT
DTSTART:19870405T020000
RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU;UNTIL=20060402T070000Z
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
TZNAME:EDT
END:DAYLIGHT
BEGIN:DAYLIGHT
DTSTART:20070311T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
TZNAME:EDT
END:DAYLIGHT
BEGIN:STANDARD
DTSTART:20071104T020000
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
TZNAME:EST
END:STANDARD
END:VTIMEZONE
END:VCALENDAR

Wyświetl plik

@ -138,6 +138,11 @@ def test_create_america_new_york(calendars, tzp):
dt = cal.events[0].start
assert tzid_from_dt(dt) in ("custom_America/New_York", "EDT")
def test_create_america_new_york_forward_reference(calendars, tzp):
"""testing America/New_York variant with VTIMEZONE as a forward reference"""
cal = calendars.america_new_york_forward_reference
dt = cal.walk('VEVENT')[0]['DTSTART'][0].dt
assert tzid_from_dt(dt) in ('custom_America/New_York_Forward_reference', "EDT")
def test_america_new_york_with_pytz(calendars, tzp, pytz_only):
"""Create a custom timezone with pytz and test the transition times."""