From 56c6d74a523b539d99fe2ce8ccb9ee6fc49ab411 Mon Sep 17 00:00:00 2001 From: Nicco Kunzmann Date: Thu, 21 Nov 2024 18:15:51 +0000 Subject: [PATCH] Handle special cases for timezone identification --- src/icalendar/cal.py | 1 - .../test_issue_722_generate_vtimezone.py | 7 +++-- .../tests/test_timezone_identification.py | 15 +++++----- src/icalendar/tests/test_timezoned.py | 4 +-- src/icalendar/timezone/tzid.py | 30 ++++++++++++------- src/icalendar/timezone/zoneinfo.py | 24 ++++++++------- 6 files changed, 47 insertions(+), 34 deletions(-) diff --git a/src/icalendar/cal.py b/src/icalendar/cal.py index cf989d38..84fcea59 100644 --- a/src/icalendar/cal.py +++ b/src/icalendar/cal.py @@ -1392,7 +1392,6 @@ def get_used_tzids(self) -> set[str]: """ result = set() for name, value in self.property_items(sorted=False): - print(name, value) if hasattr(value, "params"): result.add(value.params.get("TZID")) return result - {None} diff --git a/src/icalendar/tests/test_issue_722_generate_vtimezone.py b/src/icalendar/tests/test_issue_722_generate_vtimezone.py index 5fb6d500..7bbebdb2 100644 --- a/src/icalendar/tests/test_issue_722_generate_vtimezone.py +++ b/src/icalendar/tests/test_issue_722_generate_vtimezone.py @@ -11,10 +11,10 @@ from datetime import date, datetime, timedelta from re import findall -from zoneinfo import available_timezones import pytest from dateutil.tz import gettz +from zoneinfo import available_timezones from icalendar import Calendar, Component, Event, Timezone from icalendar.timezone import tzid_from_tzinfo, tzids_from_tzinfo @@ -327,7 +327,7 @@ def test_dateutil_timezone_is_not_found_with_tzname(calendars, no_pytz): assert "dateutil" in repr(cal.events[0].start.tzinfo.__class__) -@pytest.mark.parametrize("tzname", ["America/New_York", "Europe/Berlin"]) +@pytest.mark.parametrize("tzname", ["America/New_York", "Arctic/Longyearbyen"]) # @pytest.mark.parametrize("component", ["STANDARD", "DAYLIGHT"]) def test_dateutil_timezone_is_matched_with_tzname(tzname): """dateutil is an example of a timezone that has no tzid. @@ -338,6 +338,7 @@ def test_dateutil_timezone_is_matched_with_tzname(tzname): cal = Calendar() event = Event() event.start = datetime(2024, 11, 12, tzinfo=gettz(tzname)) + print(dir(event.start.tzinfo)) cal.add_component(event) assert cal.get_missing_tzids() == {tzname} cal.add_missing_timezones() @@ -399,7 +400,7 @@ def test_dates_before_and_after_are_considered(): pytest.skip("todo") -@pytest.mark.parametrize("tzid", available_timezones()) +@pytest.mark.parametrize("tzid", available_timezones() - {"Factory", "localtime"}) def test_we_can_identify_dateutil_timezones(tzid): """dateutil and others were badly supported. diff --git a/src/icalendar/tests/test_timezone_identification.py b/src/icalendar/tests/test_timezone_identification.py index ac93d6a2..c1137602 100644 --- a/src/icalendar/tests/test_timezone_identification.py +++ b/src/icalendar/tests/test_timezone_identification.py @@ -3,15 +3,9 @@ import pytest from zoneinfo import ZoneInfo, available_timezones -from icalendar.timezone import tzids_from_tzinfo - -tzids = available_timezones() -try: - tzids.remove("Factory") - tzids.remove("localtime") -except ValueError: - pass +from icalendar.timezone import tzids_from_tzinfo, tzid_from_tzinfo +tzids = available_timezones() - {"Factory", "localtime"} with_tzid = pytest.mark.parametrize("tzid", tzids) @with_tzid @@ -30,3 +24,8 @@ def test_can_identify_dateutil(tzid): """Check that all those dateutil timezones can be identified.""" from dateutil.tz import gettz assert tzid in tzids_from_tzinfo(gettz(tzid)) + +def test_utc_is_identified(utc): + """Test UTC because it is handled in a special way.""" + assert "UTC" in tzids_from_tzinfo(utc) + assert tzid_from_tzinfo(utc) == "UTC" diff --git a/src/icalendar/tests/test_timezoned.py b/src/icalendar/tests/test_timezoned.py index 68f2971b..44591a26 100644 --- a/src/icalendar/tests/test_timezoned.py +++ b/src/icalendar/tests/test_timezoned.py @@ -135,7 +135,7 @@ def test_tzinfo_dateutil(): def test_create_america_new_york(calendars, tzp): """testing America/New_York, the most complex example from the RFC""" cal = calendars.america_new_york - dt = cal.walk("VEVENT")[0]["DTSTART"][0].dt + dt = cal.events[0].start assert tzid_from_dt(dt) in ("custom_America/New_York", "EDT") @@ -143,7 +143,7 @@ def test_america_new_york_with_pytz(calendars, tzp, pytz_only): """Create a custom timezone with pytz and test the transition times.""" print(tzp) cal = calendars.america_new_york - dt = cal.walk("VEVENT")[0]["DTSTART"][0].dt + dt = cal.events[0].start tz = dt.tzinfo tz_new_york = tzp.timezone("America/New_York") # for reasons (tm) the locally installed version of the timezone diff --git a/src/icalendar/timezone/tzid.py b/src/icalendar/timezone/tzid.py index fc2f17f8..b39485dd 100644 --- a/src/icalendar/timezone/tzid.py +++ b/src/icalendar/timezone/tzid.py @@ -7,6 +7,7 @@ from __future__ import annotations from typing import TYPE_CHECKING, Optional +import dateutil.tz.tz as tz if TYPE_CHECKING: from datetime import datetime, tzinfo @@ -20,16 +21,20 @@ def tzids_from_tzinfo(tzinfo: Optional[tzinfo]) -> tuple[str]: >>> tzids_from_tzinfo(zoneinfo.ZoneInfo("Africa/Accra")) ('Africa/Accra',) >>> from dateutil.tz import gettz - >>> tzids_from_tzinfo(gettz("Africa/Accra")) - ('Africa/Abidjan', 'Africa/Accra', 'Africa/Bamako', 'Africa/Banjul', 'Africa/Conakry', 'Africa/Dakar') + >>> tzids_from_tzinfo(gettz("Europe/Berlin")) + ('Arctic/Longyearbyen', 'Atlantic/Jan_Mayen', 'Europe/Berlin', 'Europe/Budapest', 'Europe/Copenhagen', 'Europe/Oslo', 'Europe/Stockholm', 'Europe/Vienna') - """ + """ # The example might need to change if you recreate the lookup tree if tzinfo is None: return () if hasattr(tzinfo, 'zone'): return (tzinfo.zone,) # pytz implementation if hasattr(tzinfo, 'key'): - return (tzinfo.key,) # dateutil implementation, tzinfo.key # ZoneInfo implementation + return (tzinfo.key,) # ZoneInfo implementation + if isinstance(tzinfo, tz._tzicalvtz): + return (tzinfo._tzid,) + if isinstance(tzinfo, tz.tzstr): + return (tzinfo._s,) return tuple(sorted(tzinfo2tzids(tzinfo))) @@ -39,8 +44,12 @@ def tzid_from_tzinfo(tzinfo: Optional[tzinfo]) -> Optional[str]: Some timezones are equivalent. Thus, we might return one ID that is equivelant to others. """ - return (tzids_from_tzinfo(tzinfo) + (None,))[0] - + tzids = tzids_from_tzinfo(tzinfo) + if "UTC" in tzids: + return "UTC" + if not tzids: + return None + return tzids[0] def tzid_from_dt(dt: datetime) -> Optional[str]: """Retrieve the timezone id from the datetime object.""" @@ -67,10 +76,11 @@ def tzinfo2tzids(tzinfo: Optional[tzinfo]) -> set[str]: is equivalent to many others. >>> import zoneinfo - >>> from icalendar.timezone.equivalent_timezone_ids import tzinfo2tzids - >>> tzinfo2tzids(zoneinfo.ZoneInfo("Africa/Accra")) - ('Africa/Abidjan', 'Africa/Accra', 'Africa/Bamako', 'Africa/Banjul', 'Africa/Conakry', 'Africa/Dakar') - """ + >>> from icalendar.timezone.tzid import tzinfo2tzids + >>> "Europe/Berlin" in tzinfo2tzids(zoneinfo.ZoneInfo("Europe/Berlin")) + True + + """ # The example might need to change if you recreate the lookup tree if tzinfo is None: return set() from icalendar.timezone.equivalent_timezone_ids_result import lookup diff --git a/src/icalendar/timezone/zoneinfo.py b/src/icalendar/timezone/zoneinfo.py index e3665c30..bc5c7aaf 100644 --- a/src/icalendar/timezone/zoneinfo.py +++ b/src/icalendar/timezone/zoneinfo.py @@ -1,21 +1,25 @@ """Use zoneinfo timezones""" from __future__ import annotations + try: import zoneinfo -except: - from backports import zoneinfo -from icalendar import prop -from dateutil.rrule import rrule, rruleset +except ImportError: + from backports import zoneinfo # type: ignore # noqa: PGH003 +import copy +import copyreg +import functools from datetime import datetime, tzinfo -from typing import Optional -from .provider import TZProvider -from .. import cal from io import StringIO +from typing import TYPE_CHECKING, Optional + +from dateutil.rrule import rrule, rruleset from dateutil.tz import tzical from dateutil.tz.tz import _tzicalvtz -import copyreg -import functools -import copy + +from .provider import TZProvider + +if TYPE_CHECKING: + from icalendar import cal, prop class ZONEINFO(TZProvider):