Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pendulum/tz/timezone.py: 33%
210 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
1from datetime import datetime
2from datetime import timedelta
3from datetime import tzinfo
4from typing import Optional
5from typing import TypeVar
6from typing import overload
8import pendulum
10from pendulum.helpers import local_time
11from pendulum.helpers import timestamp
12from pendulum.utils._compat import _HAS_FOLD
14from .exceptions import AmbiguousTime
15from .exceptions import NonExistingTime
16from .zoneinfo import read
17from .zoneinfo import read_file
18from .zoneinfo.transition import Transition
21POST_TRANSITION = "post"
22PRE_TRANSITION = "pre"
23TRANSITION_ERROR = "error"
25_datetime = datetime
26_D = TypeVar("_D", bound=datetime)
29class Timezone(tzinfo):
30 """
31 Represents a named timezone.
33 The accepted names are those provided by the IANA time zone database.
35 >>> from pendulum.tz.timezone import Timezone
36 >>> tz = Timezone('Europe/Paris')
37 """
39 def __init__(self, name, extended=True): # type: (str, bool) -> None
40 tz = read(name, extend=extended)
42 self._name = name
43 self._transitions = tz.transitions
44 self._hint = {True: None, False: None}
46 @property
47 def name(self): # type: () -> str
48 return self._name
50 def convert(self, dt, dst_rule=None): # type: (_D, Optional[str]) -> _D
51 """
52 Converts a datetime in the current timezone.
54 If the datetime is naive, it will be normalized.
56 >>> from datetime import datetime
57 >>> from pendulum import timezone
58 >>> paris = timezone('Europe/Paris')
59 >>> dt = datetime(2013, 3, 31, 2, 30, fold=1)
60 >>> in_paris = paris.convert(dt)
61 >>> in_paris.isoformat()
62 '2013-03-31T03:30:00+02:00'
64 If the datetime is aware, it will be properly converted.
66 >>> new_york = timezone('America/New_York')
67 >>> in_new_york = new_york.convert(in_paris)
68 >>> in_new_york.isoformat()
69 '2013-03-30T21:30:00-04:00'
70 """
71 if dt.tzinfo is None:
72 return self._normalize(dt, dst_rule=dst_rule)
74 return self._convert(dt)
76 def datetime(
77 self, year, month, day, hour=0, minute=0, second=0, microsecond=0
78 ): # type: (int, int, int, int, int, int, int) -> _datetime
79 """
80 Return a normalized datetime for the current timezone.
81 """
82 if _HAS_FOLD:
83 return self.convert(
84 datetime(year, month, day, hour, minute, second, microsecond, fold=1)
85 )
87 return self.convert(
88 datetime(year, month, day, hour, minute, second, microsecond),
89 dst_rule=POST_TRANSITION,
90 )
92 def _normalize(self, dt, dst_rule=None): # type: (_D, Optional[str]) -> _D
93 sec = timestamp(dt)
94 fold = 0
95 transition = self._lookup_transition(sec)
97 if not _HAS_FOLD and dst_rule is None:
98 dst_rule = POST_TRANSITION
100 if dst_rule is None:
101 dst_rule = PRE_TRANSITION
102 if dt.fold == 1:
103 dst_rule = POST_TRANSITION
105 if sec < transition.local:
106 if transition.is_ambiguous(sec):
107 # Ambiguous time
108 if dst_rule == TRANSITION_ERROR:
109 raise AmbiguousTime(dt)
111 # We set the fold attribute for later
112 if dst_rule == POST_TRANSITION:
113 fold = 1
114 elif transition.previous is not None:
115 transition = transition.previous
117 if transition:
118 if transition.is_ambiguous(sec):
119 # Ambiguous time
120 if dst_rule == TRANSITION_ERROR:
121 raise AmbiguousTime(dt)
123 # We set the fold attribute for later
124 if dst_rule == POST_TRANSITION:
125 fold = 1
126 elif transition.is_missing(sec):
127 # Skipped time
128 if dst_rule == TRANSITION_ERROR:
129 raise NonExistingTime(dt)
131 # We adjust accordingly
132 if dst_rule == POST_TRANSITION:
133 sec += transition.fix
134 fold = 1
135 else:
136 sec -= transition.fix
138 kwargs = {"tzinfo": self}
139 if _HAS_FOLD or isinstance(dt, pendulum.DateTime):
140 kwargs["fold"] = fold
142 return dt.__class__(*local_time(sec, 0, dt.microsecond), **kwargs)
144 def _convert(self, dt): # type: (_D) -> _D
145 if dt.tzinfo is self:
146 return self._normalize(dt, dst_rule=POST_TRANSITION)
148 if not isinstance(dt.tzinfo, Timezone):
149 return dt.astimezone(self)
151 stamp = timestamp(dt)
153 if isinstance(dt.tzinfo, FixedTimezone):
154 offset = dt.tzinfo.offset
155 else:
156 transition = dt.tzinfo._lookup_transition(stamp)
157 offset = transition.ttype.offset
159 if stamp < transition.local and transition.previous is not None:
160 if (
161 transition.previous.is_ambiguous(stamp)
162 and getattr(dt, "fold", 1) == 0
163 ):
164 pass
165 else:
166 offset = transition.previous.ttype.offset
168 stamp -= offset
170 transition = self._lookup_transition(stamp, is_utc=True)
171 if stamp < transition.at and transition.previous is not None:
172 transition = transition.previous
174 offset = transition.ttype.offset
175 stamp += offset
176 fold = int(not transition.ttype.is_dst())
178 kwargs = {"tzinfo": self}
180 if _HAS_FOLD or isinstance(dt, pendulum.DateTime):
181 kwargs["fold"] = fold
183 return dt.__class__(*local_time(stamp, 0, dt.microsecond), **kwargs)
185 def _lookup_transition(
186 self, stamp, is_utc=False
187 ): # type: (int, bool) -> Transition
188 lo, hi = 0, len(self._transitions)
189 hint = self._hint[is_utc]
190 if hint:
191 if stamp == hint[0]:
192 return self._transitions[hint[1]]
193 elif stamp < hint[0]:
194 hi = hint[1]
195 else:
196 lo = hint[1]
198 if not is_utc:
199 while lo < hi:
200 mid = (lo + hi) // 2
201 if stamp < self._transitions[mid].to:
202 hi = mid
203 else:
204 lo = mid + 1
205 else:
206 while lo < hi:
207 mid = (lo + hi) // 2
208 if stamp < self._transitions[mid].at:
209 hi = mid
210 else:
211 lo = mid + 1
213 if lo >= len(self._transitions):
214 # Beyond last transition
215 lo = len(self._transitions) - 1
217 self._hint[is_utc] = (stamp, lo)
219 return self._transitions[lo]
221 @overload
222 def utcoffset(self, dt): # type: (None) -> None
223 pass
225 @overload
226 def utcoffset(self, dt): # type: (_datetime) -> timedelta
227 pass
229 def utcoffset(self, dt):
230 if dt is None:
231 return
233 transition = self._get_transition(dt)
235 return transition.utcoffset()
237 def dst(
238 self, dt # type: Optional[_datetime]
239 ): # type: (...) -> Optional[timedelta]
240 if dt is None:
241 return
243 transition = self._get_transition(dt)
245 if not transition.ttype.is_dst():
246 return timedelta()
248 return timedelta(seconds=transition.fix)
250 def tzname(self, dt): # type: (Optional[_datetime]) -> Optional[str]
251 if dt is None:
252 return
254 transition = self._get_transition(dt)
256 return transition.ttype.abbreviation
258 def _get_transition(self, dt): # type: (_datetime) -> Transition
259 if dt.tzinfo is not None and dt.tzinfo is not self:
260 dt = dt - dt.utcoffset()
262 stamp = timestamp(dt)
264 transition = self._lookup_transition(stamp, is_utc=True)
265 else:
266 stamp = timestamp(dt)
268 transition = self._lookup_transition(stamp)
270 if stamp < transition.local and transition.previous is not None:
271 fold = getattr(dt, "fold", 1)
272 if transition.is_ambiguous(stamp):
273 if fold == 0:
274 transition = transition.previous
275 elif transition.previous.is_ambiguous(stamp) and fold == 0:
276 pass
277 else:
278 transition = transition.previous
280 return transition
282 def fromutc(self, dt): # type: (_D) -> _D
283 stamp = timestamp(dt)
285 transition = self._lookup_transition(stamp, is_utc=True)
286 if stamp < transition.at and transition.previous is not None:
287 transition = transition.previous
289 stamp += transition.ttype.offset
291 return dt.__class__(*local_time(stamp, 0, dt.microsecond), tzinfo=self)
293 def __repr__(self): # type: () -> str
294 return "Timezone('{}')".format(self._name)
296 def __getinitargs__(self): # type: () -> tuple
297 return (self._name,)
300class FixedTimezone(Timezone):
301 def __init__(self, offset, name=None):
302 sign = "-" if offset < 0 else "+"
304 minutes = offset / 60
305 hour, minute = divmod(abs(int(minutes)), 60)
307 if not name:
308 name = "{0}{1:02d}:{2:02d}".format(sign, hour, minute)
310 self._name = name
311 self._offset = offset
312 self._utcoffset = timedelta(seconds=offset)
314 @property
315 def offset(self): # type: () -> int
316 return self._offset
318 def _normalize(self, dt, dst_rule=None): # type: (_D, Optional[str]) -> _D
319 if _HAS_FOLD:
320 dt = dt.__class__(
321 dt.year,
322 dt.month,
323 dt.day,
324 dt.hour,
325 dt.minute,
326 dt.second,
327 dt.microsecond,
328 tzinfo=self,
329 fold=0,
330 )
331 else:
332 dt = dt.__class__(
333 dt.year,
334 dt.month,
335 dt.day,
336 dt.hour,
337 dt.minute,
338 dt.second,
339 dt.microsecond,
340 tzinfo=self,
341 )
343 return dt
345 def _convert(self, dt): # type: (_D) -> _D
346 if dt.tzinfo is not self:
347 return dt.astimezone(self)
349 return dt
351 def utcoffset(self, dt): # type: (Optional[_datetime]) -> timedelta
352 return self._utcoffset
354 def dst(self, dt): # type: (Optional[_datetime]) -> timedelta
355 return timedelta()
357 def fromutc(self, dt): # type: (_D) -> _D
358 # Use the stdlib datetime's add method to avoid infinite recursion
359 return (datetime.__add__(dt, self._utcoffset)).replace(tzinfo=self)
361 def tzname(self, dt): # type: (Optional[_datetime]) -> Optional[str]
362 return self._name
364 def __getinitargs__(self): # type: () -> tuple
365 return self._offset, self._name
368class TimezoneFile(Timezone):
369 def __init__(self, path):
370 tz = read_file(path)
372 self._name = ""
373 self._transitions = tz.transitions
374 self._hint = {True: None, False: None}
377UTC = FixedTimezone(0, "UTC")