From 4c055a621310ad55b3ea52620d348a7a347c8464 Mon Sep 17 00:00:00 2001 From: Nicco Kunzmann Date: Tue, 19 Sep 2023 15:05:34 +0100 Subject: [PATCH] Add timezone to period value types The time zone was missing from PERIOD values. See https://github.com/niccokunzmann/python-recurring-ical-events/issues/113 --- CHANGES.rst | 2 +- src/icalendar/prop.py | 8 ++--- .../tests/calendars/period_with_timezone.ics | 32 +++++++++++++++++++ src/icalendar/tests/test_period.py | 17 +++++++++- 4 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 src/icalendar/tests/calendars/period_with_timezone.ics diff --git a/CHANGES.rst b/CHANGES.rst index 0ba76a60..8f74c244 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -18,7 +18,7 @@ New features: Bug fixes: -- ... +- PERIOD values now set the timezone of their start and end. #556 5.0.8 (2023-09-18) ------------------ diff --git a/src/icalendar/prop.py b/src/icalendar/prop.py index 22314165..b4448c6c 100644 --- a/src/icalendar/prop.py +++ b/src/icalendar/prop.py @@ -331,7 +331,7 @@ def from_ical(cls, ical, timezone=None): if u.startswith(('P', '-P', '+P')): return vDuration.from_ical(ical) if '/' in u: - return vPeriod.from_ical(ical) + return vPeriod.from_ical(ical, timezone=timezone) if len(ical) in (15, 16): return vDatetime.from_ical(ical, timezone=timezone) @@ -548,11 +548,11 @@ def to_ical(self): + vDatetime(self.end).to_ical()) @staticmethod - def from_ical(ical): + def from_ical(ical, timezone=None): try: start, end_or_duration = ical.split('/') - start = vDDDTypes.from_ical(start) - end_or_duration = vDDDTypes.from_ical(end_or_duration) + start = vDDDTypes.from_ical(start, timezone=timezone) + end_or_duration = vDDDTypes.from_ical(end_or_duration, timezone=timezone) return (start, end_or_duration) except Exception: raise ValueError(f'Expected period format, got: {ical}') diff --git a/src/icalendar/tests/calendars/period_with_timezone.ics b/src/icalendar/tests/calendars/period_with_timezone.ics new file mode 100644 index 00000000..e82c10f9 --- /dev/null +++ b/src/icalendar/tests/calendars/period_with_timezone.ics @@ -0,0 +1,32 @@ +BEGIN:VCALENDAR +VERSION:2.0 +X-WR-CALNAME;VALUE=TEXT:Test RDATE +BEGIN:VTIMEZONE +TZID:America/Vancouver +BEGIN:STANDARD +DTSTART:20221106T020000 +TZOFFSETFROM:-0700 +TZOFFSETTO:-0800 +RDATE:20231105T020000 +TZNAME:PST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:20230312T020000 +TZOFFSETFROM:-0800 +TZOFFSETTO:-0700 +RDATE:20240310T020000 +TZNAME:PDT +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +UID:1 +DESCRIPTION:Test RDATE +DTSTART;TZID=America/Vancouver:20230920T120000 +DTEND;TZID=America/Vancouver:20230920T140000 +EXDATE;TZID=America/Vancouver:20231220T120000 +RDATE;VALUE=PERIOD;TZID=America/Vancouver:20231213T120000/20231213T150000 +RRULE:FREQ=MONTHLY;COUNT=9;INTERVAL=1;BYDAY=+3WE;BYMONTH=1,2,3,4,5,9,10,11, + 12;WKST=MO +SUMMARY:Test RDATE +END:VEVENT +END:VCALENDAR diff --git a/src/icalendar/tests/test_period.py b/src/icalendar/tests/test_period.py index ff64a788..03df873f 100644 --- a/src/icalendar/tests/test_period.py +++ b/src/icalendar/tests/test_period.py @@ -7,6 +7,7 @@ import pytest import pytz from icalendar.prop import vDDDTypes +import datetime @pytest.mark.parametrize("calname,tzname,index,period_string", [ @@ -31,7 +32,7 @@ def test_issue_156_period_list_in_rdate(calendars, calname, tzname, index, perio calendar = calendars[calname] rdate = calendar.walk("vevent")[0]["rdate"] period = rdate.dts[index] - assert period.dt == vDDDTypes.from_ical(period_string, timezone=pytz.timezone(tzname)) + assert period.dt == vDDDTypes.from_ical(period_string, timezone=tzname) def test_duration_properly_parsed(events): @@ -46,3 +47,17 @@ def test_duration_properly_parsed(events): assert period[1].days == 0 assert period[1].seconds == (5 * 60 + 30) * 60 assert period[1] == duration + + +def test_tzid_is_part_of_the_parameters(calendars): + """The TZID should be mentioned in the parameters.""" + event = list(calendars.period_with_timezone.walk("VEVENT"))[0] + assert event["RDATE"].params["TZID"] == "America/Vancouver" + + +def test_tzid_is_part_of_the_period_values(calendars): + """The TZID should be set in the datetime.""" + event = list(calendars.period_with_timezone.walk("VEVENT"))[0] + start, end = event["RDATE"].dts[0].dt + assert start == pytz.timezone("America/Vancouver").localize(datetime.datetime(2023, 12, 13, 12)) + assert end == pytz.timezone("America/Vancouver").localize(datetime.datetime(2023, 12, 13, 15))