Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/dateutil/tz/_common.py: 24%
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
2from functools import wraps
4from datetime import datetime, timedelta, tzinfo
7ZERO = timedelta(0)
9__all__ = ["enfold"]
12# The following is adapted from Alexander Belopolsky's tz library
13# https://github.com/abalkin/tz
14def enfold(dt: datetime, fold: int = 1) -> datetime:
15 """Returns a datetime with the ``fold`` attribute set to ``fold``.
17 :param fold:
18 The value for the ``fold`` attribute, either 0 or 1.
20 :return:
21 A :class:`datetime.datetime` with the requested ``fold`` value.
23 .. versionadded:: 2.6.0
24 """
25 return dt.replace(fold=fold)
28def _validate_fromutc_inputs(f):
29 """
30 The CPython version of ``fromutc`` checks that the input is a ``datetime``
31 object and that ``self`` is attached as its ``tzinfo``.
32 """
33 @wraps(f)
34 def fromutc(self, dt):
35 if not isinstance(dt, datetime):
36 raise TypeError("fromutc() requires a datetime argument")
37 if dt.tzinfo is not self:
38 raise ValueError("dt.tzinfo is not self")
40 return f(self, dt)
42 return fromutc
45class _tzinfo(tzinfo):
46 """
47 Base class for all ``dateutil`` ``tzinfo`` objects.
48 """
50 def is_ambiguous(self, dt):
51 """
52 Whether or not the "wall time" of a given datetime is ambiguous in this
53 zone.
55 :param dt:
56 A :py:class:`datetime.datetime`, naive or time zone aware.
59 :return:
60 Returns ``True`` if ambiguous, ``False`` otherwise.
62 .. versionadded:: 2.6.0
63 """
65 dt = dt.replace(tzinfo=self)
67 wall_0 = enfold(dt, fold=0)
68 wall_1 = enfold(dt, fold=1)
70 same_offset = wall_0.utcoffset() == wall_1.utcoffset()
71 same_dt = wall_0.replace(tzinfo=None) == wall_1.replace(tzinfo=None)
73 return same_dt and not same_offset
75 def _fold_status(self, dt_utc, dt_wall):
76 """
77 Determine the fold status of a "wall" datetime, given a representation
78 of the same datetime as a (naive) UTC datetime. This is calculated based
79 on the assumption that ``dt.utcoffset() - dt.dst()`` is constant for all
80 datetimes, and that this offset is the actual number of hours separating
81 ``dt_utc`` and ``dt_wall``.
83 :param dt_utc:
84 Representation of the datetime as UTC
86 :param dt_wall:
87 Representation of the datetime as "wall time". This parameter must
88 either have a `fold` attribute or have a fold-naive
89 :class:`datetime.tzinfo` attached, otherwise the calculation may
90 fail.
91 """
92 if self.is_ambiguous(dt_wall):
93 delta_wall = dt_wall - dt_utc
94 _fold = int(delta_wall == (dt_utc.utcoffset() - dt_utc.dst()))
95 else:
96 _fold = 0
98 return _fold
100 def _fromutc(self, dt):
101 """
102 Given a timezone-aware datetime in a given timezone, calculates a
103 timezone-aware datetime in a new timezone.
105 Since this is the one time that we *know* we have an unambiguous
106 datetime object, we take this opportunity to determine whether the
107 datetime is ambiguous and in a "fold" state (e.g. if it's the first
108 occurrence, chronologically, of the ambiguous datetime).
110 :param dt:
111 A timezone-aware :class:`datetime.datetime` object.
112 """
114 # Re-implement the algorithm from Python's datetime.py
115 dtoff = dt.utcoffset()
116 if dtoff is None:
117 raise ValueError("fromutc() requires a non-None utcoffset() "
118 "result")
120 # The original datetime.py code assumes that `dst()` defaults to
121 # zero during ambiguous times. PEP 495 inverts this presumption, so
122 # select fold=1 when calculating the DST adjustment below.
123 dtdst = dt.dst()
124 if dtdst is None:
125 raise ValueError("fromutc() requires a non-None dst() result")
126 delta = dtoff - dtdst
128 dt += delta
129 # Set fold=1 so we can default to being in the fold for
130 # ambiguous dates.
131 dtdst = enfold(dt, fold=1).dst()
132 if dtdst is None:
133 raise ValueError("fromutc(): dt.dst gave inconsistent "
134 "results; cannot convert")
135 return dt + dtdst
137 @_validate_fromutc_inputs
138 def fromutc(self, dt):
139 """
140 Given a timezone-aware datetime in a given timezone, calculates a
141 timezone-aware datetime in a new timezone.
143 Since this is the one time that we *know* we have an unambiguous
144 datetime object, we take this opportunity to determine whether the
145 datetime is ambiguous and in a "fold" state (e.g. if it's the first
146 occurrence, chronologically, of the ambiguous datetime).
148 :param dt:
149 A timezone-aware :class:`datetime.datetime` object.
150 """
151 dt_wall = self._fromutc(dt)
153 # Calculate the fold status given the two datetimes.
154 _fold = self._fold_status(dt, dt_wall)
156 # Set the default fold value for ambiguous dates
157 return enfold(dt_wall, fold=_fold)
160class tzrangebase(_tzinfo):
161 """
162 This is an abstract base class for time zones represented by an annual
163 transition into and out of DST. Child classes should implement the following
164 methods:
166 * ``__init__(self, *args, **kwargs)``
167 * ``transitions(self, year)`` - this is expected to return a tuple of
168 datetimes representing the DST on and off transitions in standard
169 time.
171 A fully initialized ``tzrangebase`` subclass should also provide the
172 following attributes:
173 * ``hasdst``: Boolean whether or not the zone uses DST.
174 * ``_dst_offset`` / ``_std_offset``: :class:`datetime.timedelta` objects
175 representing the respective UTC offsets.
176 * ``_dst_abbr`` / ``_std_abbr``: Strings representing the timezone short
177 abbreviations in DST and STD, respectively.
178 * ``_hasdst``: Whether or not the zone has DST.
180 .. versionadded:: 2.6.0
181 """
182 def __init__(self):
183 raise NotImplementedError('tzrangebase is an abstract base class')
185 def utcoffset(self, dt):
186 isdst = self._isdst(dt)
188 if isdst is None:
189 return None
190 elif isdst:
191 return self._dst_offset
192 else:
193 return self._std_offset
195 def dst(self, dt):
196 isdst = self._isdst(dt)
198 if isdst is None:
199 return None
200 elif isdst:
201 return self._dst_base_offset
202 else:
203 return ZERO
205 def tzname(self, dt):
206 if self._isdst(dt):
207 return self._dst_abbr
208 else:
209 return self._std_abbr
211 def fromutc(self, dt):
212 """ Given a datetime in UTC, return local time """
213 if not isinstance(dt, datetime):
214 raise TypeError("fromutc() requires a datetime argument")
216 if dt.tzinfo is not self:
217 raise ValueError("dt.tzinfo is not self")
219 # Get transitions - if there are none, fixed offset
220 transitions = self.transitions(dt.year)
221 if transitions is None:
222 return dt + self.utcoffset(dt)
224 # Get the transition times in UTC
225 dston, dstoff = transitions
227 dston -= self._std_offset
228 dstoff -= self._std_offset
230 utc_transitions = (dston, dstoff)
231 dt_utc = dt.replace(tzinfo=None)
233 isdst = self._naive_isdst(dt_utc, utc_transitions)
235 if isdst:
236 dt_wall = dt + self._dst_offset
237 else:
238 dt_wall = dt + self._std_offset
240 _fold = int(not isdst and self.is_ambiguous(dt_wall))
242 return enfold(dt_wall, fold=_fold)
244 def is_ambiguous(self, dt):
245 """
246 Whether or not the "wall time" of a given datetime is ambiguous in this
247 zone.
249 :param dt:
250 A :py:class:`datetime.datetime`, naive or time zone aware.
253 :return:
254 Returns ``True`` if ambiguous, ``False`` otherwise.
256 .. versionadded:: 2.6.0
257 """
258 if not self.hasdst:
259 return False
261 start, end = self.transitions(dt.year)
263 dt = dt.replace(tzinfo=None)
264 return (end <= dt < end + self._dst_base_offset)
266 def _isdst(self, dt):
267 if not self.hasdst:
268 return False
269 elif dt is None:
270 return None
272 transitions = self.transitions(dt.year)
274 if transitions is None:
275 return False
277 dt = dt.replace(tzinfo=None)
279 isdst = self._naive_isdst(dt, transitions)
281 # Handle ambiguous dates
282 if not isdst and self.is_ambiguous(dt):
283 return not dt.fold
284 else:
285 return isdst
287 def _naive_isdst(self, dt, transitions):
288 dston, dstoff = transitions
290 dt = dt.replace(tzinfo=None)
292 if dston < dstoff:
293 isdst = dston <= dt < dstoff
294 else:
295 isdst = not dstoff <= dt < dston
297 return isdst
299 @property
300 def _dst_base_offset(self):
301 return self._dst_offset - self._std_offset
303 __hash__ = None
305 def __repr__(self):
306 return "%s(...)" % self.__class__.__name__
308 __reduce__ = object.__reduce__