fix: 포트 충돌 회피 — note_bridge 8098, intent_service 8099
Jellyfin(8096), OrbStack(8097) 포트 충돌으로 변경. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
"""This package contains all functionality for timezones."""
|
||||
|
||||
from .tzid import tzid_from_dt, tzid_from_tzinfo, tzids_from_tzinfo
|
||||
from .tzp import TZP
|
||||
|
||||
tzp = TZP()
|
||||
|
||||
|
||||
def use_pytz():
|
||||
"""Use pytz as the implementation that looks up and creates timezones."""
|
||||
tzp.use_pytz()
|
||||
|
||||
|
||||
def use_zoneinfo():
|
||||
"""Use zoneinfo as the implementation that looks up and creates timezones."""
|
||||
tzp.use_zoneinfo()
|
||||
|
||||
|
||||
__all__ = [
|
||||
"TZP",
|
||||
"tzp",
|
||||
"use_pytz",
|
||||
"use_zoneinfo",
|
||||
"tzid_from_tzinfo",
|
||||
"tzid_from_dt",
|
||||
"tzids_from_tzinfo",
|
||||
]
|
||||
@@ -0,0 +1,148 @@
|
||||
"""This module helps identifying the timezone ids and where they differ.
|
||||
|
||||
The algorithm: We use the tzname and the utcoffset for each hour from
|
||||
1970 - 2030.
|
||||
We make a big map.
|
||||
If they are equivalent, they are equivalent within the time that is mostly used.
|
||||
|
||||
You can regenerate the information from this module.
|
||||
|
||||
See also:
|
||||
- https://stackoverflow.com/questions/79185519/which-timezones-are-equivalent
|
||||
|
||||
Run this module:
|
||||
|
||||
python -m icalendar.timezone.equivalent_timezone_ids
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta, tzinfo
|
||||
from pathlib import Path
|
||||
from pprint import pprint
|
||||
from typing import Callable, NamedTuple, Optional
|
||||
|
||||
from pytz import AmbiguousTimeError, NonExistentTimeError
|
||||
from zoneinfo import ZoneInfo, available_timezones
|
||||
|
||||
START = datetime(1970, 1, 1) # noqa: DTZ001
|
||||
END = datetime(2020, 1, 1) # noqa: DTZ001
|
||||
DISTANCE_FROM_TIMEZONE_CHANGE = timedelta(hours=12)
|
||||
|
||||
DTS = []
|
||||
dt = START
|
||||
while dt <= END:
|
||||
DTS.append(dt)
|
||||
dt += timedelta(
|
||||
hours=25
|
||||
) # This must be big enough to be fast and small enough to identify the timeszones before it is the present year
|
||||
del dt
|
||||
|
||||
|
||||
def main(
|
||||
create_timezones: list[Callable[[str], tzinfo]],
|
||||
name: str,
|
||||
):
|
||||
"""Generate a lookup table for timezone information if unknown timezones.
|
||||
|
||||
We cannot create one lookup for all because they seem to be all equivalent
|
||||
if we mix timezone implementations.
|
||||
"""
|
||||
print(create_timezones, name)
|
||||
unsorted_tzids = available_timezones()
|
||||
unsorted_tzids.remove("localtime")
|
||||
unsorted_tzids.remove("Factory")
|
||||
|
||||
class TZ(NamedTuple):
|
||||
tz: tzinfo
|
||||
id: str
|
||||
|
||||
tzs = [
|
||||
TZ(create_timezone(tzid), tzid)
|
||||
for create_timezone in create_timezones
|
||||
for tzid in unsorted_tzids
|
||||
]
|
||||
|
||||
def generate_tree(
|
||||
tzs: list[TZ],
|
||||
step: timedelta = timedelta(hours=1),
|
||||
start: datetime = START,
|
||||
end: datetime = END,
|
||||
todo: Optional[set[str]] = None,
|
||||
) -> tuple[datetime, dict[timedelta, set[str]]] | set[str]: # should be recursive
|
||||
"""Generate a lookup tree."""
|
||||
if todo is None:
|
||||
todo = [tz.id for tz in tzs]
|
||||
print(f"{len(todo)} left to compute")
|
||||
print(len(tzs))
|
||||
if len(tzs) == 0:
|
||||
raise ValueError("tzs cannot be empty")
|
||||
if len(tzs) == 1:
|
||||
todo.remove(tzs[0].id)
|
||||
return {tzs[0].id}
|
||||
while start < end:
|
||||
offsets: dict[timedelta, list[TZ]] = defaultdict(list)
|
||||
try:
|
||||
# if we are around a timezone change, we must move on
|
||||
# see https://github.com/collective/icalendar/issues/776
|
||||
around_tz_change = not all(
|
||||
tz.tz.utcoffset(start)
|
||||
== tz.tz.utcoffset(start - DISTANCE_FROM_TIMEZONE_CHANGE)
|
||||
== tz.tz.utcoffset(start + DISTANCE_FROM_TIMEZONE_CHANGE)
|
||||
for tz in tzs
|
||||
)
|
||||
except (NonExistentTimeError, AmbiguousTimeError):
|
||||
around_tz_change = True
|
||||
if around_tz_change:
|
||||
start += DISTANCE_FROM_TIMEZONE_CHANGE
|
||||
continue
|
||||
for tz in tzs:
|
||||
offsets[tz.tz.utcoffset(start)].append(tz)
|
||||
if len(offsets) == 1:
|
||||
start += step
|
||||
continue
|
||||
lookup = {}
|
||||
for offset, tzs in offsets.items():
|
||||
lookup[offset] = generate_tree(
|
||||
tzs=tzs, step=step, start=start + step, end=end, todo=todo
|
||||
)
|
||||
return start, lookup
|
||||
print(f"reached end with {len(tzs)} timezones - assuming they are equivalent.")
|
||||
result = set()
|
||||
for tz in tzs:
|
||||
result.add(tz.id)
|
||||
todo.remove(tz.id)
|
||||
return result
|
||||
|
||||
lookup = generate_tree(tzs, step=timedelta(hours=33))
|
||||
|
||||
file = Path(__file__).parent / f"equivalent_timezone_ids_{name}.py"
|
||||
print(f"The result is written to {file}.")
|
||||
print("lookup = ", end="")
|
||||
pprint(lookup)
|
||||
with file.open("w") as f:
|
||||
f.write(
|
||||
f"'''This file is automatically generated by {Path(__file__).name}'''\n"
|
||||
)
|
||||
f.write("import datetime\n\n")
|
||||
f.write("\nlookup = ")
|
||||
pprint(lookup, stream=f)
|
||||
f.write("\n\n__all__ = ['lookup']\n")
|
||||
|
||||
return lookup
|
||||
|
||||
|
||||
__all__ = ["main"]
|
||||
|
||||
if __name__ == "__main__":
|
||||
from dateutil.tz import gettz
|
||||
from pytz import timezone
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
# add more timezone implementations if you like
|
||||
main(
|
||||
[ZoneInfo, timezone, gettz],
|
||||
"result",
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
||||
"""The interface for timezone implementations."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from datetime import datetime, tzinfo
|
||||
|
||||
from dateutil.rrule import rrule
|
||||
|
||||
from icalendar import prop
|
||||
|
||||
|
||||
class TZProvider(ABC):
|
||||
"""Interface for timezone implementations."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> str:
|
||||
"""The name of the implementation."""
|
||||
|
||||
@abstractmethod
|
||||
def localize_utc(self, dt: datetime) -> datetime:
|
||||
"""Return the datetime in UTC."""
|
||||
|
||||
@abstractmethod
|
||||
def localize(self, dt: datetime, tz: tzinfo) -> datetime:
|
||||
"""Localize a datetime to a timezone."""
|
||||
|
||||
@abstractmethod
|
||||
def knows_timezone_id(self, id: str) -> bool:
|
||||
"""Whether the timezone is already cached by the implementation."""
|
||||
|
||||
@abstractmethod
|
||||
def fix_rrule_until(self, rrule: rrule, ical_rrule: prop.vRecur) -> None:
|
||||
"""Make sure the until value works for the rrule generated from the ical_rrule."""
|
||||
|
||||
@abstractmethod
|
||||
def create_timezone(self, name: str, transition_times, transition_info) -> tzinfo:
|
||||
"""Create a pytz timezone file given information."""
|
||||
|
||||
@abstractmethod
|
||||
def timezone(self, name: str) -> Optional[tzinfo]:
|
||||
"""Return a timezone with a name or None if we cannot find it."""
|
||||
|
||||
@abstractmethod
|
||||
def uses_pytz(self) -> bool:
|
||||
"""Whether we use pytz."""
|
||||
|
||||
@abstractmethod
|
||||
def uses_zoneinfo(self) -> bool:
|
||||
"""Whether we use zoneinfo."""
|
||||
|
||||
|
||||
__all__ = ["TZProvider"]
|
||||
72
.venv/lib/python3.9/site-packages/icalendar/timezone/pytz.py
Normal file
72
.venv/lib/python3.9/site-packages/icalendar/timezone/pytz.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""Use pytz timezones."""
|
||||
|
||||
from __future__ import annotations
|
||||
import pytz
|
||||
from .. import cal
|
||||
from datetime import datetime, tzinfo
|
||||
from pytz.tzinfo import DstTzInfo
|
||||
from typing import Optional
|
||||
from .provider import TZProvider
|
||||
from icalendar import prop
|
||||
from dateutil.rrule import rrule
|
||||
|
||||
|
||||
class PYTZ(TZProvider):
|
||||
"""Provide icalendar with timezones from pytz."""
|
||||
|
||||
name = "pytz"
|
||||
|
||||
def localize_utc(self, dt: datetime) -> datetime:
|
||||
"""Return the datetime in UTC."""
|
||||
if getattr(dt, "tzinfo", False) and dt.tzinfo is not None:
|
||||
return dt.astimezone(pytz.utc)
|
||||
# assume UTC for naive datetime instances
|
||||
return pytz.utc.localize(dt)
|
||||
|
||||
def localize(self, dt: datetime, tz: tzinfo) -> datetime:
|
||||
"""Localize a datetime to a timezone."""
|
||||
return tz.localize(dt)
|
||||
|
||||
def knows_timezone_id(self, id: str) -> bool:
|
||||
"""Whether the timezone is already cached by the implementation."""
|
||||
return id in pytz.all_timezones
|
||||
|
||||
def fix_rrule_until(self, rrule: rrule, ical_rrule: prop.vRecur) -> None:
|
||||
"""Make sure the until value works for the rrule generated from the ical_rrule."""
|
||||
if not {"UNTIL", "COUNT"}.intersection(ical_rrule.keys()):
|
||||
# pytz.timezones don't know any transition dates after 2038
|
||||
# either
|
||||
rrule._until = datetime(2038, 12, 31, tzinfo=pytz.UTC)
|
||||
|
||||
def create_timezone(self, tz: cal.Timezone) -> tzinfo:
|
||||
"""Create a pytz timezone from the given information."""
|
||||
transition_times, transition_info = tz.get_transitions()
|
||||
name = tz.tz_name
|
||||
cls = type(
|
||||
name,
|
||||
(DstTzInfo,),
|
||||
{
|
||||
"zone": name,
|
||||
"_utc_transition_times": transition_times,
|
||||
"_transition_info": transition_info,
|
||||
},
|
||||
)
|
||||
return cls()
|
||||
|
||||
def timezone(self, name: str) -> Optional[tzinfo]:
|
||||
"""Return a timezone with a name or None if we cannot find it."""
|
||||
try:
|
||||
return pytz.timezone(name)
|
||||
except pytz.UnknownTimeZoneError:
|
||||
pass
|
||||
|
||||
def uses_pytz(self) -> bool:
|
||||
"""Whether we use pytz."""
|
||||
return True
|
||||
|
||||
def uses_zoneinfo(self) -> bool:
|
||||
"""Whether we use zoneinfo."""
|
||||
return False
|
||||
|
||||
|
||||
__all__ = ["PYTZ"]
|
||||
112
.venv/lib/python3.9/site-packages/icalendar/timezone/tzid.py
Normal file
112
.venv/lib/python3.9/site-packages/icalendar/timezone/tzid.py
Normal file
@@ -0,0 +1,112 @@
|
||||
"""This module identifies timezones.
|
||||
|
||||
Normally, timezones have ids.
|
||||
This is a way to access the ids if you have a
|
||||
datetime.tzinfo object.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from dateutil.tz import tz
|
||||
|
||||
from icalendar.timezone import equivalent_timezone_ids_result
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from datetime import datetime, tzinfo
|
||||
|
||||
DATEUTIL_UTC = tz.gettz("UTC")
|
||||
DATEUTIL_UTC_PATH : Optional[str] = getattr(DATEUTIL_UTC, "_filename", None)
|
||||
DATEUTIL_ZONEINFO_PATH = (
|
||||
None if DATEUTIL_UTC_PATH is None else Path(DATEUTIL_UTC_PATH).parent
|
||||
)
|
||||
|
||||
def tzids_from_tzinfo(tzinfo: Optional[tzinfo]) -> tuple[str]:
|
||||
"""Get several timezone ids if we can identify the timezone.
|
||||
|
||||
>>> import zoneinfo
|
||||
>>> from icalendar.timezone.tzid import tzids_from_tzinfo
|
||||
>>> tzids_from_tzinfo(zoneinfo.ZoneInfo("Arctic/Longyearbyen"))
|
||||
('Arctic/Longyearbyen', 'Atlantic/Jan_Mayen', 'Europe/Berlin', 'Europe/Budapest', 'Europe/Copenhagen', 'Europe/Oslo', 'Europe/Stockholm', 'Europe/Vienna')
|
||||
>>> from dateutil.tz import gettz
|
||||
>>> tzids_from_tzinfo(gettz("Europe/Berlin"))
|
||||
('Europe/Berlin', 'Arctic/Longyearbyen', 'Atlantic/Jan_Mayen', '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 get_equivalent_tzids(tzinfo.zone) # pytz implementation
|
||||
if hasattr(tzinfo, "key"):
|
||||
return get_equivalent_tzids(tzinfo.key) # ZoneInfo implementation
|
||||
if isinstance(tzinfo, tz._tzicalvtz): # noqa: SLF001
|
||||
return get_equivalent_tzids(tzinfo._tzid) # noqa: SLF001
|
||||
if isinstance(tzinfo, tz.tzstr):
|
||||
return get_equivalent_tzids(tzinfo._s) # noqa: SLF001
|
||||
if hasattr(tzinfo, "_filename"): # dateutil.tz.tzfile # noqa: SIM102
|
||||
if DATEUTIL_ZONEINFO_PATH is not None:
|
||||
# tzfile('/usr/share/zoneinfo/Europe/Berlin')
|
||||
path = tzinfo._filename # noqa: SLF001
|
||||
if path.startswith(str(DATEUTIL_ZONEINFO_PATH)):
|
||||
tzid = str(Path(path).relative_to(DATEUTIL_ZONEINFO_PATH))
|
||||
return get_equivalent_tzids(tzid)
|
||||
return get_equivalent_tzids(path)
|
||||
if isinstance(tzinfo, tz.tzutc):
|
||||
return get_equivalent_tzids("UTC")
|
||||
return ()
|
||||
|
||||
|
||||
def tzid_from_tzinfo(tzinfo: Optional[tzinfo]) -> Optional[str]:
|
||||
"""Retrieve the timezone id from the tzinfo object.
|
||||
|
||||
Some timezones are equivalent.
|
||||
Thus, we might return one ID that is equivelant to others.
|
||||
"""
|
||||
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."""
|
||||
tzid = tzid_from_tzinfo(dt.tzinfo)
|
||||
if tzid is None:
|
||||
return dt.tzname()
|
||||
return tzid
|
||||
|
||||
|
||||
_EQUIVALENT_IDS : dict[str, set[str]] = defaultdict(set)
|
||||
|
||||
def _add_equivalent_ids(value:tuple|dict|set):
|
||||
"""This adds equivalent ids/
|
||||
|
||||
As soon as one timezone implementation used claims their equivalence,
|
||||
they are considered equivalent.
|
||||
Have a look at icalendar.timezone.equivalent_timezone_ids.
|
||||
"""
|
||||
if isinstance(value, set):
|
||||
for tzid in value:
|
||||
_EQUIVALENT_IDS[tzid].update(value)
|
||||
elif isinstance(value, tuple):
|
||||
_add_equivalent_ids(value[1])
|
||||
elif isinstance(value, dict):
|
||||
for value in value.values():
|
||||
_add_equivalent_ids(value)
|
||||
else:
|
||||
raise TypeError(f"Expected tuple, dict or set, not {value.__class__.__name__}: {value!r}")
|
||||
|
||||
_add_equivalent_ids(equivalent_timezone_ids_result.lookup)
|
||||
|
||||
def get_equivalent_tzids(tzid: str) -> tuple[str]:
|
||||
"""This returns the tzids which are equivalent to this one."""
|
||||
ids = _EQUIVALENT_IDS.get(tzid, set())
|
||||
return (tzid,) + tuple(sorted(ids - {tzid}))
|
||||
|
||||
|
||||
__all__ = ["tzid_from_tzinfo", "tzid_from_dt", "tzids_from_tzinfo"]
|
||||
146
.venv/lib/python3.9/site-packages/icalendar/timezone/tzp.py
Normal file
146
.venv/lib/python3.9/site-packages/icalendar/timezone/tzp.py
Normal file
@@ -0,0 +1,146 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Optional, Union
|
||||
|
||||
from icalendar.tools import to_datetime
|
||||
|
||||
from .windows_to_olson import WINDOWS_TO_OLSON
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import datetime
|
||||
|
||||
from dateutil.rrule import rrule
|
||||
|
||||
from icalendar import cal, prop
|
||||
|
||||
from .provider import TZProvider
|
||||
|
||||
DEFAULT_TIMEZONE_PROVIDER = "zoneinfo"
|
||||
|
||||
|
||||
class TZP:
|
||||
"""This is the timezone provider proxy.
|
||||
|
||||
If you would like to have another timezone implementation,
|
||||
you can create a new one and pass it to this proxy.
|
||||
All of icalendar will then use this timezone implementation.
|
||||
"""
|
||||
|
||||
def __init__(self, provider: Union[str, TZProvider] = DEFAULT_TIMEZONE_PROVIDER):
|
||||
"""Create a new timezone implementation proxy."""
|
||||
self.use(provider)
|
||||
|
||||
def use_pytz(self) -> None:
|
||||
"""Use pytz as the timezone provider."""
|
||||
from .pytz import PYTZ
|
||||
|
||||
self._use(PYTZ())
|
||||
|
||||
def use_zoneinfo(self) -> None:
|
||||
"""Use zoneinfo as the timezone provider."""
|
||||
from .zoneinfo import ZONEINFO
|
||||
|
||||
self._use(ZONEINFO())
|
||||
|
||||
def _use(self, provider: TZProvider) -> None:
|
||||
"""Use a timezone implementation."""
|
||||
self.__tz_cache = {}
|
||||
self.__provider = provider
|
||||
|
||||
def use(self, provider: Union[str, TZProvider]):
|
||||
"""Switch to a different timezone provider."""
|
||||
if isinstance(provider, str):
|
||||
use_provider = getattr(self, f"use_{provider}", None)
|
||||
if use_provider is None:
|
||||
raise ValueError(
|
||||
f"Unknown provider {provider}. Use 'pytz' or 'zoneinfo'."
|
||||
)
|
||||
use_provider()
|
||||
else:
|
||||
self._use(provider)
|
||||
|
||||
def use_default(self):
|
||||
"""Use the default timezone provider."""
|
||||
self.use(DEFAULT_TIMEZONE_PROVIDER)
|
||||
|
||||
def localize_utc(self, dt: datetime.date) -> datetime.datetime:
|
||||
"""Return the datetime in UTC.
|
||||
|
||||
If the datetime has no timezone, set UTC as its timezone.
|
||||
"""
|
||||
return self.__provider.localize_utc(to_datetime(dt))
|
||||
|
||||
def localize(
|
||||
self, dt: datetime.date, tz: Union[datetime.tzinfo, str, None]
|
||||
) -> datetime.datetime:
|
||||
"""Localize a datetime to a timezone."""
|
||||
if isinstance(tz, str):
|
||||
tz = self.timezone(tz)
|
||||
if tz is None:
|
||||
return dt.replace(tzinfo=None)
|
||||
return self.__provider.localize(to_datetime(dt), tz)
|
||||
|
||||
def cache_timezone_component(self, timezone_component: cal.Timezone) -> None:
|
||||
"""Cache the timezone that is created from a timezone component
|
||||
if it is not already known.
|
||||
|
||||
This can influence the result from timezone(): Once cached, the
|
||||
custom timezone is returned from timezone().
|
||||
"""
|
||||
_unclean_id = timezone_component["TZID"]
|
||||
_id = self.clean_timezone_id(_unclean_id)
|
||||
if (
|
||||
not self.__provider.knows_timezone_id(_id)
|
||||
and not self.__provider.knows_timezone_id(_unclean_id)
|
||||
and _id not in self.__tz_cache
|
||||
):
|
||||
self.__tz_cache[_id] = timezone_component.to_tz(self, lookup_tzid=False)
|
||||
|
||||
def fix_rrule_until(self, rrule: rrule, ical_rrule: prop.vRecur) -> None:
|
||||
"""Make sure the until value works."""
|
||||
self.__provider.fix_rrule_until(rrule, ical_rrule)
|
||||
|
||||
def create_timezone(self, timezone_component: cal.Timezone) -> datetime.tzinfo:
|
||||
"""Create a timezone from a timezone component.
|
||||
|
||||
This component will not be cached.
|
||||
"""
|
||||
return self.__provider.create_timezone(timezone_component)
|
||||
|
||||
def clean_timezone_id(self, tzid: str) -> str:
|
||||
"""Return a clean version of the timezone id.
|
||||
|
||||
Timezone ids can be a bit unclean, starting with a / for example.
|
||||
Internally, we should use this to identify timezones.
|
||||
"""
|
||||
return tzid.strip("/")
|
||||
|
||||
def timezone(self, tz_id: str) -> Optional[datetime.tzinfo]:
|
||||
"""Return a timezone with an id or None if we cannot find it."""
|
||||
_unclean_id = tz_id
|
||||
tz_id = self.clean_timezone_id(tz_id)
|
||||
tz = self.__provider.timezone(tz_id)
|
||||
if tz is not None:
|
||||
return tz
|
||||
if tz_id in WINDOWS_TO_OLSON:
|
||||
tz = self.__provider.timezone(WINDOWS_TO_OLSON[tz_id])
|
||||
return tz or self.__provider.timezone(_unclean_id) or self.__tz_cache.get(tz_id)
|
||||
|
||||
def uses_pytz(self) -> bool:
|
||||
"""Whether we use pytz at all."""
|
||||
return self.__provider.uses_pytz()
|
||||
|
||||
def uses_zoneinfo(self) -> bool:
|
||||
"""Whether we use zoneinfo."""
|
||||
return self.__provider.uses_zoneinfo()
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""The name of the timezone component used."""
|
||||
return self.__provider.name
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}({repr(self.name)})"
|
||||
|
||||
|
||||
__all__ = ["TZP"]
|
||||
@@ -0,0 +1,119 @@
|
||||
"""This module contains mappings from Windows timezone identifiers to
|
||||
Olson timezone identifiers.
|
||||
|
||||
The data is taken from the unicode consortium [0], the proposal and rationale
|
||||
for this mapping is also available at the unicode consortium [1].
|
||||
|
||||
|
||||
[0] https://www.unicode.org/cldr/cldr-aux/charts/29/supplemental/zone_tzid.html
|
||||
[1] https://cldr.unicode.org/development/development-process/design-proposals/extended-windows-olson-zid-mapping # noqa
|
||||
"""
|
||||
|
||||
WINDOWS_TO_OLSON = {
|
||||
"AUS Central Standard Time": "Australia/Darwin",
|
||||
"AUS Eastern Standard Time": "Australia/Sydney",
|
||||
"Afghanistan Standard Time": "Asia/Kabul",
|
||||
"Alaskan Standard Time": "America/Anchorage",
|
||||
"Arab Standard Time": "Asia/Riyadh",
|
||||
"Arabian Standard Time": "Asia/Dubai",
|
||||
"Arabic Standard Time": "Asia/Baghdad",
|
||||
"Argentina Standard Time": "America/Argentina/Buenos_Aires",
|
||||
"Atlantic Standard Time": "America/Halifax",
|
||||
"Azerbaijan Standard Time": "Asia/Baku",
|
||||
"Azores Standard Time": "Atlantic/Azores",
|
||||
"Bahia Standard Time": "America/Bahia",
|
||||
"Bangladesh Standard Time": "Asia/Dhaka",
|
||||
"Belarus Standard Time": "Europe/Minsk",
|
||||
"Canada Central Standard Time": "America/Regina",
|
||||
"Cape Verde Standard Time": "Atlantic/Cape_Verde",
|
||||
"Caucasus Standard Time": "Asia/Yerevan",
|
||||
"Cen. Australia Standard Time": "Australia/Adelaide",
|
||||
"Central America Standard Time": "America/Guatemala",
|
||||
"Central Asia Standard Time": "Asia/Almaty",
|
||||
"Central Brazilian Standard Time": "America/Cuiaba",
|
||||
"Central Europe Standard Time": "Europe/Budapest",
|
||||
"Central European Standard Time": "Europe/Warsaw",
|
||||
"Central Pacific Standard Time": "Pacific/Guadalcanal",
|
||||
"Central Standard Time": "America/Chicago",
|
||||
"Central Standard Time (Mexico)": "America/Mexico_City",
|
||||
"China Standard Time": "Asia/Shanghai",
|
||||
"Dateline Standard Time": "Etc/GMT+12",
|
||||
"E. Africa Standard Time": "Africa/Nairobi",
|
||||
"E. Australia Standard Time": "Australia/Brisbane",
|
||||
"E. Europe Standard Time": "Europe/Chisinau",
|
||||
"E. South America Standard Time": "America/Sao_Paulo",
|
||||
"Eastern Standard Time": "America/New_York",
|
||||
"Eastern Standard Time (Mexico)": "America/Cancun",
|
||||
"Egypt Standard Time": "Africa/Cairo",
|
||||
"Ekaterinburg Standard Time": "Asia/Yekaterinburg",
|
||||
"FLE Standard Time": "Europe/Kyiv",
|
||||
"Fiji Standard Time": "Pacific/Fiji",
|
||||
"GMT Standard Time": "Europe/London",
|
||||
"GTB Standard Time": "Europe/Bucharest",
|
||||
"Georgian Standard Time": "Asia/Tbilisi",
|
||||
"Greenland Standard Time": "America/Nuuk",
|
||||
"Greenwich Standard Time": "Atlantic/Reykjavik",
|
||||
"Hawaiian Standard Time": "Pacific/Honolulu",
|
||||
"India Standard Time": "Asia/Kolkata",
|
||||
"Iran Standard Time": "Asia/Tehran",
|
||||
"Israel Standard Time": "Asia/Jerusalem",
|
||||
"Jordan Standard Time": "Asia/Amman",
|
||||
"Kaliningrad Standard Time": "Europe/Kaliningrad",
|
||||
"Korea Standard Time": "Asia/Seoul",
|
||||
"Libya Standard Time": "Africa/Tripoli",
|
||||
"Line Islands Standard Time": "Pacific/Kiritimati",
|
||||
"Magadan Standard Time": "Asia/Magadan",
|
||||
"Mauritius Standard Time": "Indian/Mauritius",
|
||||
"Middle East Standard Time": "Asia/Beirut",
|
||||
"Montevideo Standard Time": "America/Montevideo",
|
||||
"Morocco Standard Time": "Africa/Casablanca",
|
||||
"Mountain Standard Time": "America/Denver",
|
||||
"Mountain Standard Time (Mexico)": "America/Chihuahua",
|
||||
"Myanmar Standard Time": "Asia/Yangon",
|
||||
"N. Central Asia Standard Time": "Asia/Novosibirsk",
|
||||
"Namibia Standard Time": "Africa/Windhoek",
|
||||
"Nepal Standard Time": "Asia/Kathmandu",
|
||||
"New Zealand Standard Time": "Pacific/Auckland",
|
||||
"Newfoundland Standard Time": "America/St_Johns",
|
||||
"North Asia East Standard Time": "Asia/Irkutsk",
|
||||
"North Asia Standard Time": "Asia/Krasnoyarsk",
|
||||
"North Korea Standard Time": "Asia/Pyongyang",
|
||||
"Pacific SA Standard Time": "America/Santiago",
|
||||
"Pacific Standard Time": "America/Los_Angeles",
|
||||
"Pakistan Standard Time": "Asia/Karachi",
|
||||
"Paraguay Standard Time": "America/Asuncion",
|
||||
"Romance Standard Time": "Europe/Paris",
|
||||
"Russia Time Zone 10": "Asia/Srednekolymsk",
|
||||
"Russia Time Zone 11": "Asia/Kamchatka",
|
||||
"Russia Time Zone 3": "Europe/Samara",
|
||||
"Russian Standard Time": "Europe/Moscow",
|
||||
"SA Eastern Standard Time": "America/Cayenne",
|
||||
"SA Pacific Standard Time": "America/Bogota",
|
||||
"SA Western Standard Time": "America/La_Paz",
|
||||
"SE Asia Standard Time": "Asia/Bangkok",
|
||||
"Samoa Standard Time": "Pacific/Apia",
|
||||
"Singapore Standard Time": "Asia/Singapore",
|
||||
"South Africa Standard Time": "Africa/Johannesburg",
|
||||
"Sri Lanka Standard Time": "Asia/Colombo",
|
||||
"Syria Standard Time": "Asia/Damascus",
|
||||
"Taipei Standard Time": "Asia/Taipei",
|
||||
"Tasmania Standard Time": "Australia/Hobart",
|
||||
"Tokyo Standard Time": "Asia/Tokyo",
|
||||
"Tonga Standard Time": "Pacific/Tongatapu",
|
||||
"Turkey Standard Time": "Europe/Istanbul",
|
||||
"US Eastern Standard Time": "America/Indiana/Indianapolis",
|
||||
"US Mountain Standard Time": "America/Phoenix",
|
||||
"UTC": "Etc/GMT",
|
||||
"UTC+12": "Etc/GMT-12",
|
||||
"UTC-02": "Etc/GMT+2",
|
||||
"UTC-11": "Etc/GMT+11",
|
||||
"Ulaanbaatar Standard Time": "Asia/Ulaanbaatar",
|
||||
"Venezuela Standard Time": "America/Caracas",
|
||||
"Vladivostok Standard Time": "Asia/Vladivostok",
|
||||
"W. Australia Standard Time": "Australia/Perth",
|
||||
"W. Central Africa Standard Time": "Africa/Lagos",
|
||||
"W. Europe Standard Time": "Europe/Berlin",
|
||||
"West Asia Standard Time": "Asia/Tashkent",
|
||||
"West Pacific Standard Time": "Pacific/Port_Moresby",
|
||||
"Yakutsk Standard Time": "Asia/Yakutsk",
|
||||
}
|
||||
160
.venv/lib/python3.9/site-packages/icalendar/timezone/zoneinfo.py
Normal file
160
.venv/lib/python3.9/site-packages/icalendar/timezone/zoneinfo.py
Normal file
@@ -0,0 +1,160 @@
|
||||
"""Use zoneinfo timezones"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from icalendar.tools import is_date, to_datetime
|
||||
|
||||
try:
|
||||
import zoneinfo
|
||||
except ImportError:
|
||||
from backports import zoneinfo # type: ignore # noqa: PGH003
|
||||
import copy
|
||||
import copyreg
|
||||
import functools
|
||||
from datetime import datetime, tzinfo
|
||||
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
|
||||
|
||||
from .provider import TZProvider
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from icalendar import cal, prop
|
||||
from icalendar.prop import vDDDTypes
|
||||
|
||||
|
||||
class ZONEINFO(TZProvider):
|
||||
"""Provide icalendar with timezones from zoneinfo."""
|
||||
|
||||
name = "zoneinfo"
|
||||
utc = zoneinfo.ZoneInfo("UTC")
|
||||
_available_timezones = zoneinfo.available_timezones()
|
||||
|
||||
def localize(self, dt: datetime, tz: zoneinfo.ZoneInfo) -> datetime:
|
||||
"""Localize a datetime to a timezone."""
|
||||
return dt.replace(tzinfo=tz)
|
||||
|
||||
def localize_utc(self, dt: datetime) -> datetime:
|
||||
"""Return the datetime in UTC."""
|
||||
if getattr(dt, "tzinfo", False) and dt.tzinfo is not None:
|
||||
return dt.astimezone(self.utc)
|
||||
return self.localize(dt, self.utc)
|
||||
|
||||
def timezone(self, name: str) -> Optional[tzinfo]:
|
||||
"""Return a timezone with a name or None if we cannot find it."""
|
||||
try:
|
||||
return zoneinfo.ZoneInfo(name)
|
||||
except zoneinfo.ZoneInfoNotFoundError:
|
||||
pass
|
||||
except ValueError:
|
||||
# ValueError: ZoneInfo keys may not be absolute paths, got: /Europe/CUSTOM
|
||||
pass
|
||||
|
||||
def knows_timezone_id(self, id: str) -> bool:
|
||||
"""Whether the timezone is already cached by the implementation."""
|
||||
return id in self._available_timezones
|
||||
|
||||
def fix_rrule_until(self, rrule: rrule, ical_rrule: prop.vRecur) -> None:
|
||||
"""Make sure the until value works for the rrule generated from the ical_rrule."""
|
||||
if not {"UNTIL", "COUNT"}.intersection(ical_rrule.keys()):
|
||||
# zoninfo does not know any transition dates after 2038
|
||||
rrule._until = datetime(2038, 12, 31, tzinfo=self.utc)
|
||||
|
||||
def create_timezone(self, tz: cal.Timezone) -> tzinfo:
|
||||
"""Create a timezone from the given information."""
|
||||
try:
|
||||
return self._create_timezone(tz)
|
||||
except ValueError:
|
||||
# We might have a custom component in there.
|
||||
# see https://github.com/python/cpython/issues/120217
|
||||
tz = copy.deepcopy(tz)
|
||||
for sub in tz.walk():
|
||||
for attr in list(sub.keys()):
|
||||
if attr.lower().startswith("x-"):
|
||||
sub.pop(attr)
|
||||
for sub in tz.subcomponents:
|
||||
start : vDDDTypes = sub.get("DTSTART")
|
||||
if start and is_date(start.dt):
|
||||
# ValueError: Unsupported DTSTART param in VTIMEZONE: VALUE=DATE
|
||||
sub.DTSTART = to_datetime(start.dt)
|
||||
return self._create_timezone(tz)
|
||||
|
||||
def _create_timezone(self, tz: cal.Timezone) -> tzinfo:
|
||||
"""Create a timezone and maybe fail"""
|
||||
file = StringIO(tz.to_ical().decode("UTF-8", "replace"))
|
||||
return tzical(file).get()
|
||||
|
||||
def uses_pytz(self) -> bool:
|
||||
"""Whether we use pytz."""
|
||||
return False
|
||||
|
||||
def uses_zoneinfo(self) -> bool:
|
||||
"""Whether we use zoneinfo."""
|
||||
return True
|
||||
|
||||
|
||||
def pickle_tzicalvtz(tzicalvtz: tz._tzicalvtz):
|
||||
"""Because we use dateutil.tzical, we need to make it pickle-able."""
|
||||
return _tzicalvtz, (tzicalvtz._tzid, tzicalvtz._comps)
|
||||
|
||||
|
||||
copyreg.pickle(_tzicalvtz, pickle_tzicalvtz)
|
||||
|
||||
|
||||
def pickle_rrule_with_cache(self: rrule):
|
||||
"""Make sure we can also pickle rrules that cache.
|
||||
|
||||
This is mainly copied from rrule.replace.
|
||||
"""
|
||||
new_kwargs = {
|
||||
"interval": self._interval,
|
||||
"count": self._count,
|
||||
"dtstart": self._dtstart,
|
||||
"freq": self._freq,
|
||||
"until": self._until,
|
||||
"wkst": self._wkst,
|
||||
"cache": False if self._cache is None else True,
|
||||
}
|
||||
new_kwargs.update(self._original_rule)
|
||||
# from https://stackoverflow.com/a/64915638/1320237
|
||||
return functools.partial(rrule, new_kwargs.pop("freq"), **new_kwargs), ()
|
||||
|
||||
|
||||
copyreg.pickle(rrule, pickle_rrule_with_cache)
|
||||
|
||||
|
||||
def pickle_rruleset_with_cache(rs: rruleset):
|
||||
"""Pickle an rruleset."""
|
||||
# self._rrule = []
|
||||
# self._rdate = []
|
||||
# self._exrule = []
|
||||
# self._exdate = []
|
||||
return unpickle_rruleset_with_cache, (
|
||||
rs._rrule,
|
||||
rs._rdate,
|
||||
rs._exrule,
|
||||
rs._exdate,
|
||||
False if rs._cache is None else True,
|
||||
)
|
||||
|
||||
|
||||
def unpickle_rruleset_with_cache(rrule, rdate, exrule, exdate, cache):
|
||||
"""unpickling the rruleset."""
|
||||
rs = rruleset(cache)
|
||||
for o in rrule:
|
||||
rs.rrule(o)
|
||||
for o in rdate:
|
||||
rs.rdate(o)
|
||||
for o in exrule:
|
||||
rs.exrule(o)
|
||||
for o in exdate:
|
||||
rs.exdate(o)
|
||||
return rs
|
||||
|
||||
|
||||
copyreg.pickle(rruleset, pickle_rruleset_with_cache)
|
||||
|
||||
__all__ = ["ZONEINFO"]
|
||||
Reference in New Issue
Block a user