Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/dateutil/tz/_common.py: 30%
161 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1from six import PY2
3from functools import wraps
5from datetime import datetime, timedelta, tzinfo
8ZERO = timedelta(0)
10__all__ = ['tzname_in_python2', 'enfold']
13def tzname_in_python2(namefunc):
14 """Change unicode output into bytestrings in Python 2
16 tzname() API changed in Python 3. It used to return bytes, but was changed
17 to unicode strings
18 """
19 if PY2: 19 ↛ 20line 19 didn't jump to line 20, because the condition on line 19 was never true
20 @wraps(namefunc)
21 def adjust_encoding(*args, **kwargs):
22 name = namefunc(*args, **kwargs)
23 if name is not None:
24 name = name.encode()
26 return name
28 return adjust_encoding
29 else:
30 return namefunc
33# The following is adapted from Alexander Belopolsky's tz library
34# https://github.com/abalkin/tz
35if hasattr(datetime, 'fold'): 35 ↛ 58line 35 didn't jump to line 58, because the condition on line 35 was never false
36 # This is the pre-python 3.6 fold situation
37 def enfold(dt, fold=1):
38 """
39 Provides a unified interface for assigning the ``fold`` attribute to
40 datetimes both before and after the implementation of PEP-495.
42 :param fold:
43 The value for the ``fold`` attribute in the returned datetime. This
44 should be either 0 or 1.
46 :return:
47 Returns an object for which ``getattr(dt, 'fold', 0)`` returns
48 ``fold`` for all versions of Python. In versions prior to
49 Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
50 subclass of :py:class:`datetime.datetime` with the ``fold``
51 attribute added, if ``fold`` is 1.
53 .. versionadded:: 2.6.0
54 """
55 return dt.replace(fold=fold)
57else:
58 class _DatetimeWithFold(datetime):
59 """
60 This is a class designed to provide a PEP 495-compliant interface for
61 Python versions before 3.6. It is used only for dates in a fold, so
62 the ``fold`` attribute is fixed at ``1``.
64 .. versionadded:: 2.6.0
65 """
66 __slots__ = ()
68 def replace(self, *args, **kwargs):
69 """
70 Return a datetime with the same attributes, except for those
71 attributes given new values by whichever keyword arguments are
72 specified. Note that tzinfo=None can be specified to create a naive
73 datetime from an aware datetime with no conversion of date and time
74 data.
76 This is reimplemented in ``_DatetimeWithFold`` because pypy3 will
77 return a ``datetime.datetime`` even if ``fold`` is unchanged.
78 """
79 argnames = (
80 'year', 'month', 'day', 'hour', 'minute', 'second',
81 'microsecond', 'tzinfo'
82 )
84 for arg, argname in zip(args, argnames):
85 if argname in kwargs:
86 raise TypeError('Duplicate argument: {}'.format(argname))
88 kwargs[argname] = arg
90 for argname in argnames:
91 if argname not in kwargs:
92 kwargs[argname] = getattr(self, argname)
94 dt_class = self.__class__ if kwargs.get('fold', 1) else datetime
96 return dt_class(**kwargs)
98 @property
99 def fold(self):
100 return 1
102 def enfold(dt, fold=1):
103 """
104 Provides a unified interface for assigning the ``fold`` attribute to
105 datetimes both before and after the implementation of PEP-495.
107 :param fold:
108 The value for the ``fold`` attribute in the returned datetime. This
109 should be either 0 or 1.
111 :return:
112 Returns an object for which ``getattr(dt, 'fold', 0)`` returns
113 ``fold`` for all versions of Python. In versions prior to
114 Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
115 subclass of :py:class:`datetime.datetime` with the ``fold``
116 attribute added, if ``fold`` is 1.
118 .. versionadded:: 2.6.0
119 """
120 if getattr(dt, 'fold', 0) == fold:
121 return dt
123 args = dt.timetuple()[:6]
124 args += (dt.microsecond, dt.tzinfo)
126 if fold:
127 return _DatetimeWithFold(*args)
128 else:
129 return datetime(*args)
132def _validate_fromutc_inputs(f):
133 """
134 The CPython version of ``fromutc`` checks that the input is a ``datetime``
135 object and that ``self`` is attached as its ``tzinfo``.
136 """
137 @wraps(f)
138 def fromutc(self, dt):
139 if not isinstance(dt, datetime): 139 ↛ 140line 139 didn't jump to line 140, because the condition on line 139 was never true
140 raise TypeError("fromutc() requires a datetime argument")
141 if dt.tzinfo is not self: 141 ↛ 142line 141 didn't jump to line 142, because the condition on line 141 was never true
142 raise ValueError("dt.tzinfo is not self")
144 return f(self, dt)
146 return fromutc
149class _tzinfo(tzinfo):
150 """
151 Base class for all ``dateutil`` ``tzinfo`` objects.
152 """
154 def is_ambiguous(self, dt):
155 """
156 Whether or not the "wall time" of a given datetime is ambiguous in this
157 zone.
159 :param dt:
160 A :py:class:`datetime.datetime`, naive or time zone aware.
163 :return:
164 Returns ``True`` if ambiguous, ``False`` otherwise.
166 .. versionadded:: 2.6.0
167 """
169 dt = dt.replace(tzinfo=self)
171 wall_0 = enfold(dt, fold=0)
172 wall_1 = enfold(dt, fold=1)
174 same_offset = wall_0.utcoffset() == wall_1.utcoffset()
175 same_dt = wall_0.replace(tzinfo=None) == wall_1.replace(tzinfo=None)
177 return same_dt and not same_offset
179 def _fold_status(self, dt_utc, dt_wall):
180 """
181 Determine the fold status of a "wall" datetime, given a representation
182 of the same datetime as a (naive) UTC datetime. This is calculated based
183 on the assumption that ``dt.utcoffset() - dt.dst()`` is constant for all
184 datetimes, and that this offset is the actual number of hours separating
185 ``dt_utc`` and ``dt_wall``.
187 :param dt_utc:
188 Representation of the datetime as UTC
190 :param dt_wall:
191 Representation of the datetime as "wall time". This parameter must
192 either have a `fold` attribute or have a fold-naive
193 :class:`datetime.tzinfo` attached, otherwise the calculation may
194 fail.
195 """
196 if self.is_ambiguous(dt_wall): 196 ↛ 197line 196 didn't jump to line 197, because the condition on line 196 was never true
197 delta_wall = dt_wall - dt_utc
198 _fold = int(delta_wall == (dt_utc.utcoffset() - dt_utc.dst()))
199 else:
200 _fold = 0
202 return _fold
204 def _fold(self, dt):
205 return getattr(dt, 'fold', 0)
207 def _fromutc(self, dt):
208 """
209 Given a timezone-aware datetime in a given timezone, calculates a
210 timezone-aware datetime in a new timezone.
212 Since this is the one time that we *know* we have an unambiguous
213 datetime object, we take this opportunity to determine whether the
214 datetime is ambiguous and in a "fold" state (e.g. if it's the first
215 occurrence, chronologically, of the ambiguous datetime).
217 :param dt:
218 A timezone-aware :class:`datetime.datetime` object.
219 """
221 # Re-implement the algorithm from Python's datetime.py
222 dtoff = dt.utcoffset()
223 if dtoff is None: 223 ↛ 224line 223 didn't jump to line 224, because the condition on line 223 was never true
224 raise ValueError("fromutc() requires a non-None utcoffset() "
225 "result")
227 # The original datetime.py code assumes that `dst()` defaults to
228 # zero during ambiguous times. PEP 495 inverts this presumption, so
229 # for pre-PEP 495 versions of python, we need to tweak the algorithm.
230 dtdst = dt.dst()
231 if dtdst is None: 231 ↛ 232line 231 didn't jump to line 232, because the condition on line 231 was never true
232 raise ValueError("fromutc() requires a non-None dst() result")
233 delta = dtoff - dtdst
235 dt += delta
236 # Set fold=1 so we can default to being in the fold for
237 # ambiguous dates.
238 dtdst = enfold(dt, fold=1).dst()
239 if dtdst is None: 239 ↛ 240line 239 didn't jump to line 240, because the condition on line 239 was never true
240 raise ValueError("fromutc(): dt.dst gave inconsistent "
241 "results; cannot convert")
242 return dt + dtdst
244 @_validate_fromutc_inputs
245 def fromutc(self, dt):
246 """
247 Given a timezone-aware datetime in a given timezone, calculates a
248 timezone-aware datetime in a new timezone.
250 Since this is the one time that we *know* we have an unambiguous
251 datetime object, we take this opportunity to determine whether the
252 datetime is ambiguous and in a "fold" state (e.g. if it's the first
253 occurrence, chronologically, of the ambiguous datetime).
255 :param dt:
256 A timezone-aware :class:`datetime.datetime` object.
257 """
258 dt_wall = self._fromutc(dt)
260 # Calculate the fold status given the two datetimes.
261 _fold = self._fold_status(dt, dt_wall)
263 # Set the default fold value for ambiguous dates
264 return enfold(dt_wall, fold=_fold)
267class tzrangebase(_tzinfo):
268 """
269 This is an abstract base class for time zones represented by an annual
270 transition into and out of DST. Child classes should implement the following
271 methods:
273 * ``__init__(self, *args, **kwargs)``
274 * ``transitions(self, year)`` - this is expected to return a tuple of
275 datetimes representing the DST on and off transitions in standard
276 time.
278 A fully initialized ``tzrangebase`` subclass should also provide the
279 following attributes:
280 * ``hasdst``: Boolean whether or not the zone uses DST.
281 * ``_dst_offset`` / ``_std_offset``: :class:`datetime.timedelta` objects
282 representing the respective UTC offsets.
283 * ``_dst_abbr`` / ``_std_abbr``: Strings representing the timezone short
284 abbreviations in DST and STD, respectively.
285 * ``_hasdst``: Whether or not the zone has DST.
287 .. versionadded:: 2.6.0
288 """
289 def __init__(self):
290 raise NotImplementedError('tzrangebase is an abstract base class')
292 def utcoffset(self, dt):
293 isdst = self._isdst(dt)
295 if isdst is None:
296 return None
297 elif isdst:
298 return self._dst_offset
299 else:
300 return self._std_offset
302 def dst(self, dt):
303 isdst = self._isdst(dt)
305 if isdst is None:
306 return None
307 elif isdst:
308 return self._dst_base_offset
309 else:
310 return ZERO
312 @tzname_in_python2
313 def tzname(self, dt):
314 if self._isdst(dt):
315 return self._dst_abbr
316 else:
317 return self._std_abbr
319 def fromutc(self, dt):
320 """ Given a datetime in UTC, return local time """
321 if not isinstance(dt, datetime):
322 raise TypeError("fromutc() requires a datetime argument")
324 if dt.tzinfo is not self:
325 raise ValueError("dt.tzinfo is not self")
327 # Get transitions - if there are none, fixed offset
328 transitions = self.transitions(dt.year)
329 if transitions is None:
330 return dt + self.utcoffset(dt)
332 # Get the transition times in UTC
333 dston, dstoff = transitions
335 dston -= self._std_offset
336 dstoff -= self._std_offset
338 utc_transitions = (dston, dstoff)
339 dt_utc = dt.replace(tzinfo=None)
341 isdst = self._naive_isdst(dt_utc, utc_transitions)
343 if isdst:
344 dt_wall = dt + self._dst_offset
345 else:
346 dt_wall = dt + self._std_offset
348 _fold = int(not isdst and self.is_ambiguous(dt_wall))
350 return enfold(dt_wall, fold=_fold)
352 def is_ambiguous(self, dt):
353 """
354 Whether or not the "wall time" of a given datetime is ambiguous in this
355 zone.
357 :param dt:
358 A :py:class:`datetime.datetime`, naive or time zone aware.
361 :return:
362 Returns ``True`` if ambiguous, ``False`` otherwise.
364 .. versionadded:: 2.6.0
365 """
366 if not self.hasdst:
367 return False
369 start, end = self.transitions(dt.year)
371 dt = dt.replace(tzinfo=None)
372 return (end <= dt < end + self._dst_base_offset)
374 def _isdst(self, dt):
375 if not self.hasdst:
376 return False
377 elif dt is None:
378 return None
380 transitions = self.transitions(dt.year)
382 if transitions is None:
383 return False
385 dt = dt.replace(tzinfo=None)
387 isdst = self._naive_isdst(dt, transitions)
389 # Handle ambiguous dates
390 if not isdst and self.is_ambiguous(dt):
391 return not self._fold(dt)
392 else:
393 return isdst
395 def _naive_isdst(self, dt, transitions):
396 dston, dstoff = transitions
398 dt = dt.replace(tzinfo=None)
400 if dston < dstoff:
401 isdst = dston <= dt < dstoff
402 else:
403 isdst = not dstoff <= dt < dston
405 return isdst
407 @property
408 def _dst_base_offset(self):
409 return self._dst_offset - self._std_offset
411 __hash__ = None
413 def __ne__(self, other):
414 return not (self == other)
416 def __repr__(self):
417 return "%s(...)" % self.__class__.__name__
419 __reduce__ = object.__reduce__