Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pendulum/tz/timezone.py: 76%
91 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-30 06:11 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-30 06:11 +0000
1# mypy: no-warn-redundant-casts
2from __future__ import annotations
4import datetime as _datetime
6from abc import ABC
7from abc import abstractmethod
8from typing import TYPE_CHECKING
9from typing import TypeVar
10from typing import cast
12from pendulum.tz.exceptions import AmbiguousTime
13from pendulum.tz.exceptions import InvalidTimezone
14from pendulum.tz.exceptions import NonExistingTime
15from pendulum.utils._compat import zoneinfo
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 @property
70 def name(self) -> str:
71 return self.key
73 def convert(self, dt: _DT, raise_on_unknown_times: bool = False) -> _DT:
74 """
75 Converts a datetime in the current timezone.
77 If the datetime is naive, it will be normalized.
79 >>> from datetime import datetime
80 >>> from pendulum import timezone
81 >>> paris = timezone('Europe/Paris')
82 >>> dt = datetime(2013, 3, 31, 2, 30, fold=1)
83 >>> in_paris = paris.convert(dt)
84 >>> in_paris.isoformat()
85 '2013-03-31T03:30:00+02:00'
87 If the datetime is aware, it will be properly converted.
89 >>> new_york = timezone('America/New_York')
90 >>> in_new_york = new_york.convert(in_paris)
91 >>> in_new_york.isoformat()
92 '2013-03-30T21:30:00-04:00'
93 """
95 if dt.tzinfo is None:
96 # Technically, utcoffset() can return None, but none of the zone information
97 # in tzdata sets _tti_before to None. This can be checked with the following
98 # code:
99 #
100 # >>> import zoneinfo
101 # >>> from zoneinfo._zoneinfo import ZoneInfo
102 #
103 # >>> for tzname in zoneinfo.available_timezones():
104 # >>> if ZoneInfo(tzname)._tti_before is None:
105 # >>> print(tzname)
107 offset_before = cast(
108 _datetime.timedelta,
109 (self.utcoffset(dt.replace(fold=0)) if dt.fold else self.utcoffset(dt)),
110 )
111 offset_after = cast(
112 _datetime.timedelta,
113 (self.utcoffset(dt) if dt.fold else self.utcoffset(dt.replace(fold=1))),
114 )
116 if offset_after > offset_before:
117 # Skipped time
118 if raise_on_unknown_times:
119 raise NonExistingTime(dt)
121 dt = cast(
122 _DT,
123 dt
124 + (
125 (offset_after - offset_before)
126 if dt.fold
127 else (offset_before - offset_after)
128 ),
129 )
130 elif offset_before > offset_after and raise_on_unknown_times:
131 # Repeated time
132 raise AmbiguousTime(dt)
134 return dt.replace(tzinfo=self)
136 return cast(_DT, dt.astimezone(self))
138 def datetime(
139 self,
140 year: int,
141 month: int,
142 day: int,
143 hour: int = 0,
144 minute: int = 0,
145 second: int = 0,
146 microsecond: int = 0,
147 ) -> _datetime.datetime:
148 """
149 Return a normalized datetime for the current timezone.
150 """
151 return self.convert(
152 _datetime.datetime(
153 year, month, day, hour, minute, second, microsecond, fold=1
154 )
155 )
157 def __repr__(self) -> str:
158 return f"{self.__class__.__name__}('{self.name}')"
161class FixedTimezone(_datetime.tzinfo, PendulumTimezone):
162 def __init__(self, offset: int, name: str | None = None) -> None:
163 sign = "-" if offset < 0 else "+"
165 minutes = offset / 60
166 hour, minute = divmod(abs(int(minutes)), 60)
168 if not name:
169 name = f"{sign}{hour:02d}:{minute:02d}"
171 self._name = name
172 self._offset = offset
173 self._utcoffset = _datetime.timedelta(seconds=offset)
175 @property
176 def name(self) -> str:
177 return self._name
179 def convert(self, dt: _DT, raise_on_unknown_times: bool = False) -> _DT:
180 if dt.tzinfo is None:
181 return dt.__class__(
182 dt.year,
183 dt.month,
184 dt.day,
185 dt.hour,
186 dt.minute,
187 dt.second,
188 dt.microsecond,
189 tzinfo=self,
190 fold=0,
191 )
193 return cast(_DT, dt.astimezone(self))
195 def datetime(
196 self,
197 year: int,
198 month: int,
199 day: int,
200 hour: int = 0,
201 minute: int = 0,
202 second: int = 0,
203 microsecond: int = 0,
204 ) -> _datetime.datetime:
205 return self.convert(
206 _datetime.datetime(
207 year, month, day, hour, minute, second, microsecond, fold=1
208 )
209 )
211 @property
212 def offset(self) -> int:
213 return self._offset
215 def utcoffset(self, dt: _datetime.datetime | None) -> _datetime.timedelta:
216 return self._utcoffset
218 def dst(self, dt: _datetime.datetime | None) -> _datetime.timedelta:
219 return _datetime.timedelta()
221 def fromutc(self, dt: _datetime.datetime) -> _datetime.datetime:
222 # Use the stdlib datetime's add method to avoid infinite recursion
223 return (_datetime.datetime.__add__(dt, self._utcoffset)).replace(tzinfo=self)
225 def tzname(self, dt: _datetime.datetime | None) -> str | None:
226 return self._name
228 def __getinitargs__(self) -> tuple[int, str]:
229 return self._offset, self._name
231 def __repr__(self) -> str:
232 name = ""
233 if self._name:
234 name = f', name="{self._name}"'
236 return f"{self.__class__.__name__}({self._offset}{name})"
239UTC = Timezone("UTC")