Skip to content

Commit

Permalink
Handle special cases for timezone identification
Browse files Browse the repository at this point in the history
  • Loading branch information
niccokunzmann committed Nov 21, 2024
1 parent 368ae94 commit 56c6d74
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 34 deletions.
1 change: 0 additions & 1 deletion src/icalendar/cal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
7 changes: 4 additions & 3 deletions src/icalendar/tests/test_issue_722_generate_vtimezone.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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()
Expand Down Expand Up @@ -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.
Expand Down
15 changes: 7 additions & 8 deletions src/icalendar/tests/test_timezone_identification.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
4 changes: 2 additions & 2 deletions src/icalendar/tests/test_timezoned.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,15 @@ 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")


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
Expand Down
30 changes: 20 additions & 10 deletions src/icalendar/timezone/tzid.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)))


Expand All @@ -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."""
Expand All @@ -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
Expand Down
24 changes: 14 additions & 10 deletions src/icalendar/timezone/zoneinfo.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down

0 comments on commit 56c6d74

Please sign in to comment.