Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pendulum/tz/timezone.py: 58%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# mypy: no-warn-redundant-casts
2from __future__ import annotations
4import datetime as _datetime
5import zoneinfo
7from abc import ABC
8from abc import abstractmethod
9from typing import TYPE_CHECKING
10from typing import TypeVar
11from typing import cast
13from pendulum.tz.exceptions import AmbiguousTime
14from pendulum.tz.exceptions import InvalidTimezone
15from pendulum.tz.exceptions import NonExistingTime
18if TYPE_CHECKING:
19 from typing_extensions import Self
21POST_TRANSITION = "post"
22PRE_TRANSITION = "pre"
23TRANSITION_ERROR = "error"
26_DT = TypeVar("_DT", bound=_datetime.datetime)
29class PendulumTimezone(ABC):
30 @property
31 @abstractmethod
32 def name(self) -> str:
33 raise NotImplementedError
35 @abstractmethod
36 def convert(self, dt: _DT, raise_on_unknown_times: bool = False) -> _DT:
37 raise NotImplementedError
39 @abstractmethod
40 def datetime(
41 self,
42 year: int,
43 month: int,
44 day: int,
45 hour: int = 0,
46 minute: int = 0,
47 second: int = 0,
48 microsecond: int = 0,
49 ) -> _datetime.datetime:
50 raise NotImplementedError
53class Timezone(zoneinfo.ZoneInfo, PendulumTimezone):
54 """
55 Represents a named timezone.
57 The accepted names are those provided by the IANA time zone database.
59 >>> from pendulum.tz.timezone import Timezone
60 >>> tz = Timezone('Europe/Paris')
61 """
63 def __new__(cls, key: str) -> Self:
64 try:
65 return super().__new__(cls, key) # type: ignore[call-arg]
66 except zoneinfo.ZoneInfoNotFoundError:
67 raise InvalidTimezone(key)
69 def __eq__(self, other: object) -> bool:
70 return isinstance(other, Timezone) and self.key == other.key
72 @property
73 def name(self) -> str:
74 return self.key
76 def convert(self, dt: _DT, raise_on_unknown_times: bool = False) -> _DT:
77 """
78 Converts a datetime in the current timezone.
80 If the datetime is naive, it will be normalized.
82 >>> from datetime import datetime
83 >>> from pendulum import timezone
84 >>> paris = timezone('Europe/Paris')
85 >>> dt = datetime(2013, 3, 31, 2, 30, fold=1)
86 >>> in_paris = paris.convert(dt)
87 >>> in_paris.isoformat()
88 '2013-03-31T03:30:00+02:00'
90 If the datetime is aware, it will be properly converted.
92 >>> new_york = timezone('America/New_York')
93 >>> in_new_york = new_york.convert(in_paris)
94 >>> in_new_york.isoformat()
95 '2013-03-30T21:30:00-04:00'
96 """
98 if dt.tzinfo is None:
99 # Technically, utcoffset() can return None, but none of the zone information
100 # in tzdata sets _tti_before to None. This can be checked with the following
101 # code:
102 #
103 # >>> import zoneinfo
104 # >>> from zoneinfo._zoneinfo import ZoneInfo
105 #
106 # >>> for tzname in zoneinfo.available_timezones():
107 # >>> if ZoneInfo(tzname)._tti_before is None:
108 # >>> print(tzname)
110 offset_before = cast(
111 "_datetime.timedelta",
112 (self.utcoffset(dt.replace(fold=0)) if dt.fold else self.utcoffset(dt)),
113 )
114 offset_after = cast(
115 "_datetime.timedelta",
116 (self.utcoffset(dt) if dt.fold else self.utcoffset(dt.replace(fold=1))),
117 )
119 if offset_after > offset_before:
120 # Skipped time
121 if raise_on_unknown_times:
122 raise NonExistingTime(dt)
124 dt = cast(
125 "_DT",
126 dt
127 + (
128 (offset_after - offset_before)
129 if dt.fold
130 else (offset_before - offset_after)
131 ),
132 )
133 elif offset_before > offset_after and raise_on_unknown_times:
134 # Repeated time
135 raise AmbiguousTime(dt)
137 return dt.replace(tzinfo=self)
139 return cast("_DT", dt.astimezone(self))
141 def datetime(
142 self,
143 year: int,
144 month: int,
145 day: int,
146 hour: int = 0,
147 minute: int = 0,
148 second: int = 0,
149 microsecond: int = 0,
150 ) -> _datetime.datetime:
151 """
152 Return a normalized datetime for the current timezone.
153 """
154 return self.convert(
155 _datetime.datetime(
156 year, month, day, hour, minute, second, microsecond, fold=1
157 )
158 )
160 def __repr__(self) -> str:
161 return f"{self.__class__.__name__}('{self.name}')"
164class FixedTimezone(_datetime.tzinfo, PendulumTimezone):
165 def __init__(self, offset: int, name: str | None = None) -> None:
166 sign = "-" if offset < 0 else "+"
168 minutes = offset / 60
169 hour, minute = divmod(abs(int(minutes)), 60)
171 if not name:
172 name = f"{sign}{hour:02d}:{minute:02d}"
174 self._name = name
175 self._offset = offset
176 self._utcoffset = _datetime.timedelta(seconds=offset)
178 def __eq__(self, other: object) -> bool:
179 return isinstance(other, FixedTimezone) and self._offset == other._offset
181 @property
182 def name(self) -> str:
183 return self._name
185 def convert(self, dt: _DT, raise_on_unknown_times: bool = False) -> _DT:
186 if dt.tzinfo is None:
187 return dt.__class__(
188 dt.year,
189 dt.month,
190 dt.day,
191 dt.hour,
192 dt.minute,
193 dt.second,
194 dt.microsecond,
195 tzinfo=self,
196 fold=0,
197 )
199 return cast("_DT", dt.astimezone(self))
201 def datetime(
202 self,
203 year: int,
204 month: int,
205 day: int,
206 hour: int = 0,
207 minute: int = 0,
208 second: int = 0,
209 microsecond: int = 0,
210 ) -> _datetime.datetime:
211 return self.convert(
212 _datetime.datetime(
213 year, month, day, hour, minute, second, microsecond, fold=1
214 )
215 )
217 @property
218 def offset(self) -> int:
219 return self._offset
221 def utcoffset(self, dt: _datetime.datetime | None) -> _datetime.timedelta:
222 return self._utcoffset
224 def dst(self, dt: _datetime.datetime | None) -> _datetime.timedelta:
225 return _datetime.timedelta()
227 def fromutc(self, dt: _datetime.datetime) -> _datetime.datetime:
228 # Use the stdlib datetime's add method to avoid infinite recursion
229 return (_datetime.datetime.__add__(dt, self._utcoffset)).replace(tzinfo=self)
231 def tzname(self, dt: _datetime.datetime | None) -> str | None:
232 return self._name
234 def __getinitargs__(self) -> tuple[int, str]:
235 return self._offset, self._name
237 def __repr__(self) -> str:
238 name = ""
239 if self._name:
240 name = f', name="{self._name}"'
242 return f"{self.__class__.__name__}({self._offset}{name})"
245UTC = Timezone("UTC")