Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/dateutil/tz/tz.py: 31%
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# -*- coding: utf-8 -*-
2"""
3This module offers timezone implementations subclassing the abstract
4:py:class:`datetime.tzinfo` type. There are classes to handle tzfile format
5files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`,
6etc), TZ environment string (in all known formats), given ranges (with help
7from relative deltas), local machine timezone, fixed offset timezone, and UTC
8timezone.
9"""
10import datetime
11import struct
12import time
13import sys
14import os
15import bisect
16import weakref
17from collections import OrderedDict
19import six
20from six import string_types
21from six.moves import _thread
22from ._common import tzname_in_python2, _tzinfo
23from ._common import tzrangebase, enfold
24from ._common import _validate_fromutc_inputs
26from ._factories import _TzSingleton, _TzOffsetFactory
27from ._factories import _TzStrFactory
28try:
29 from .win import tzwin, tzwinlocal
30except ImportError:
31 tzwin = tzwinlocal = None
33# For warning about rounding tzinfo
34from warnings import warn
36ZERO = datetime.timedelta(0)
37EPOCH = datetime.datetime(1970, 1, 1, 0, 0)
38EPOCHORDINAL = EPOCH.toordinal()
41@six.add_metaclass(_TzSingleton)
42class tzutc(datetime.tzinfo):
43 """
44 This is a tzinfo object that represents the UTC time zone.
46 **Examples:**
48 .. doctest::
50 >>> from datetime import *
51 >>> from dateutil.tz import *
53 >>> datetime.now()
54 datetime.datetime(2003, 9, 27, 9, 40, 1, 521290)
56 >>> datetime.now(tzutc())
57 datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc())
59 >>> datetime.now(tzutc()).tzname()
60 'UTC'
62 .. versionchanged:: 2.7.0
63 ``tzutc()`` is now a singleton, so the result of ``tzutc()`` will
64 always return the same object.
66 .. doctest::
68 >>> from dateutil.tz import tzutc, UTC
69 >>> tzutc() is tzutc()
70 True
71 >>> tzutc() is UTC
72 True
73 """
74 def utcoffset(self, dt):
75 return ZERO
77 def dst(self, dt):
78 return ZERO
80 @tzname_in_python2
81 def tzname(self, dt):
82 return "UTC"
84 def is_ambiguous(self, dt):
85 """
86 Whether or not the "wall time" of a given datetime is ambiguous in this
87 zone.
89 :param dt:
90 A :py:class:`datetime.datetime`, naive or time zone aware.
93 :return:
94 Returns ``True`` if ambiguous, ``False`` otherwise.
96 .. versionadded:: 2.6.0
97 """
98 return False
100 @_validate_fromutc_inputs
101 def fromutc(self, dt):
102 """
103 Fast track version of fromutc() returns the original ``dt`` object for
104 any valid :py:class:`datetime.datetime` object.
105 """
106 return dt
108 def __eq__(self, other):
109 if not isinstance(other, (tzutc, tzoffset)):
110 return NotImplemented
112 return (isinstance(other, tzutc) or
113 (isinstance(other, tzoffset) and other._offset == ZERO))
115 __hash__ = None
117 def __ne__(self, other):
118 return not (self == other)
120 def __repr__(self):
121 return "%s()" % self.__class__.__name__
123 __reduce__ = object.__reduce__
126#: Convenience constant providing a :class:`tzutc()` instance
127#:
128#: .. versionadded:: 2.7.0
129UTC = tzutc()
132@six.add_metaclass(_TzOffsetFactory)
133class tzoffset(datetime.tzinfo):
134 """
135 A simple class for representing a fixed offset from UTC.
137 :param name:
138 The timezone name, to be returned when ``tzname()`` is called.
139 :param offset:
140 The time zone offset in seconds, or (since version 2.6.0, represented
141 as a :py:class:`datetime.timedelta` object).
142 """
143 def __init__(self, name, offset):
144 self._name = name
146 try:
147 # Allow a timedelta
148 offset = offset.total_seconds()
149 except (TypeError, AttributeError):
150 pass
152 self._offset = datetime.timedelta(seconds=_get_supported_offset(offset))
154 def utcoffset(self, dt):
155 return self._offset
157 def dst(self, dt):
158 return ZERO
160 @tzname_in_python2
161 def tzname(self, dt):
162 return self._name
164 @_validate_fromutc_inputs
165 def fromutc(self, dt):
166 return dt + self._offset
168 def is_ambiguous(self, dt):
169 """
170 Whether or not the "wall time" of a given datetime is ambiguous in this
171 zone.
173 :param dt:
174 A :py:class:`datetime.datetime`, naive or time zone aware.
175 :return:
176 Returns ``True`` if ambiguous, ``False`` otherwise.
178 .. versionadded:: 2.6.0
179 """
180 return False
182 def __eq__(self, other):
183 if not isinstance(other, tzoffset):
184 return NotImplemented
186 return self._offset == other._offset
188 __hash__ = None
190 def __ne__(self, other):
191 return not (self == other)
193 def __repr__(self):
194 return "%s(%s, %s)" % (self.__class__.__name__,
195 repr(self._name),
196 int(self._offset.total_seconds()))
198 __reduce__ = object.__reduce__
201class tzlocal(_tzinfo):
202 """
203 A :class:`tzinfo` subclass built around the ``time`` timezone functions.
204 """
205 def __init__(self):
206 super(tzlocal, self).__init__()
208 self._std_offset = datetime.timedelta(seconds=-time.timezone)
209 if time.daylight:
210 self._dst_offset = datetime.timedelta(seconds=-time.altzone)
211 else:
212 self._dst_offset = self._std_offset
214 self._dst_saved = self._dst_offset - self._std_offset
215 self._hasdst = bool(self._dst_saved)
216 self._tznames = tuple(time.tzname)
218 def utcoffset(self, dt):
219 if dt is None and self._hasdst:
220 return None
222 if self._isdst(dt):
223 return self._dst_offset
224 else:
225 return self._std_offset
227 def dst(self, dt):
228 if dt is None and self._hasdst:
229 return None
231 if self._isdst(dt):
232 return self._dst_offset - self._std_offset
233 else:
234 return ZERO
236 @tzname_in_python2
237 def tzname(self, dt):
238 return self._tznames[self._isdst(dt)]
240 def is_ambiguous(self, dt):
241 """
242 Whether or not the "wall time" of a given datetime is ambiguous in this
243 zone.
245 :param dt:
246 A :py:class:`datetime.datetime`, naive or time zone aware.
249 :return:
250 Returns ``True`` if ambiguous, ``False`` otherwise.
252 .. versionadded:: 2.6.0
253 """
254 naive_dst = self._naive_is_dst(dt)
255 return (not naive_dst and
256 (naive_dst != self._naive_is_dst(dt - self._dst_saved)))
258 def _naive_is_dst(self, dt):
259 timestamp = _datetime_to_timestamp(dt)
260 return time.localtime(timestamp + time.timezone).tm_isdst
262 def _isdst(self, dt, fold_naive=True):
263 # We can't use mktime here. It is unstable when deciding if
264 # the hour near to a change is DST or not.
265 #
266 # timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour,
267 # dt.minute, dt.second, dt.weekday(), 0, -1))
268 # return time.localtime(timestamp).tm_isdst
269 #
270 # The code above yields the following result:
271 #
272 # >>> import tz, datetime
273 # >>> t = tz.tzlocal()
274 # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
275 # 'BRDT'
276 # >>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()
277 # 'BRST'
278 # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
279 # 'BRST'
280 # >>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()
281 # 'BRDT'
282 # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
283 # 'BRDT'
284 #
285 # Here is a more stable implementation:
286 #
287 if not self._hasdst:
288 return False
290 # Check for ambiguous times:
291 dstval = self._naive_is_dst(dt)
292 fold = getattr(dt, 'fold', None)
294 if self.is_ambiguous(dt):
295 if fold is not None:
296 return not self._fold(dt)
297 else:
298 return True
300 return dstval
302 def __eq__(self, other):
303 if isinstance(other, tzlocal):
304 return (self._std_offset == other._std_offset and
305 self._dst_offset == other._dst_offset)
306 elif isinstance(other, tzutc):
307 return (not self._hasdst and
308 self._tznames[0] in {'UTC', 'GMT'} and
309 self._std_offset == ZERO)
310 elif isinstance(other, tzoffset):
311 return (not self._hasdst and
312 self._tznames[0] == other._name and
313 self._std_offset == other._offset)
314 else:
315 return NotImplemented
317 __hash__ = None
319 def __ne__(self, other):
320 return not (self == other)
322 def __repr__(self):
323 return "%s()" % self.__class__.__name__
325 __reduce__ = object.__reduce__
328class _ttinfo(object):
329 __slots__ = ["offset", "delta", "isdst", "abbr",
330 "isstd", "isgmt", "dstoffset"]
332 def __init__(self):
333 for attr in self.__slots__:
334 setattr(self, attr, None)
336 def __repr__(self):
337 l = []
338 for attr in self.__slots__:
339 value = getattr(self, attr)
340 if value is not None:
341 l.append("%s=%s" % (attr, repr(value)))
342 return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
344 def __eq__(self, other):
345 if not isinstance(other, _ttinfo):
346 return NotImplemented
348 return (self.offset == other.offset and
349 self.delta == other.delta and
350 self.isdst == other.isdst and
351 self.abbr == other.abbr and
352 self.isstd == other.isstd and
353 self.isgmt == other.isgmt and
354 self.dstoffset == other.dstoffset)
356 __hash__ = None
358 def __ne__(self, other):
359 return not (self == other)
361 def __getstate__(self):
362 state = {}
363 for name in self.__slots__:
364 state[name] = getattr(self, name, None)
365 return state
367 def __setstate__(self, state):
368 for name in self.__slots__:
369 if name in state:
370 setattr(self, name, state[name])
373class _tzfile(object):
374 """
375 Lightweight class for holding the relevant transition and time zone
376 information read from binary tzfiles.
377 """
378 attrs = ['trans_list', 'trans_list_utc', 'trans_idx', 'ttinfo_list',
379 'ttinfo_std', 'ttinfo_dst', 'ttinfo_before', 'ttinfo_first']
381 def __init__(self, **kwargs):
382 for attr in self.attrs:
383 setattr(self, attr, kwargs.get(attr, None))
386class tzfile(_tzinfo):
387 """
388 This is a ``tzinfo`` subclass that allows one to use the ``tzfile(5)``
389 format timezone files to extract current and historical zone information.
391 :param fileobj:
392 This can be an opened file stream or a file name that the time zone
393 information can be read from.
395 :param filename:
396 This is an optional parameter specifying the source of the time zone
397 information in the event that ``fileobj`` is a file object. If omitted
398 and ``fileobj`` is a file stream, this parameter will be set either to
399 ``fileobj``'s ``name`` attribute or to ``repr(fileobj)``.
401 See `Sources for Time Zone and Daylight Saving Time Data
402 <https://data.iana.org/time-zones/tz-link.html>`_ for more information.
403 Time zone files can be compiled from the `IANA Time Zone database files
404 <https://www.iana.org/time-zones>`_ with the `zic time zone compiler
405 <https://www.freebsd.org/cgi/man.cgi?query=zic&sektion=8>`_
407 .. note::
409 Only construct a ``tzfile`` directly if you have a specific timezone
410 file on disk that you want to read into a Python ``tzinfo`` object.
411 If you want to get a ``tzfile`` representing a specific IANA zone,
412 (e.g. ``'America/New_York'``), you should call
413 :func:`dateutil.tz.gettz` with the zone identifier.
416 **Examples:**
418 Using the US Eastern time zone as an example, we can see that a ``tzfile``
419 provides time zone information for the standard Daylight Saving offsets:
421 .. testsetup:: tzfile
423 from dateutil.tz import gettz
424 from datetime import datetime
426 .. doctest:: tzfile
428 >>> NYC = gettz('America/New_York')
429 >>> NYC
430 tzfile('/usr/share/zoneinfo/America/New_York')
432 >>> print(datetime(2016, 1, 3, tzinfo=NYC)) # EST
433 2016-01-03 00:00:00-05:00
435 >>> print(datetime(2016, 7, 7, tzinfo=NYC)) # EDT
436 2016-07-07 00:00:00-04:00
439 The ``tzfile`` structure contains a fully history of the time zone,
440 so historical dates will also have the right offsets. For example, before
441 the adoption of the UTC standards, New York used local solar mean time:
443 .. doctest:: tzfile
445 >>> print(datetime(1901, 4, 12, tzinfo=NYC)) # LMT
446 1901-04-12 00:00:00-04:56
448 And during World War II, New York was on "Eastern War Time", which was a
449 state of permanent daylight saving time:
451 .. doctest:: tzfile
453 >>> print(datetime(1944, 2, 7, tzinfo=NYC)) # EWT
454 1944-02-07 00:00:00-04:00
456 """
458 def __init__(self, fileobj, filename=None):
459 super(tzfile, self).__init__()
461 file_opened_here = False
462 if isinstance(fileobj, string_types):
463 self._filename = fileobj
464 fileobj = open(fileobj, 'rb')
465 file_opened_here = True
466 elif filename is not None:
467 self._filename = filename
468 elif hasattr(fileobj, "name"):
469 self._filename = fileobj.name
470 else:
471 self._filename = repr(fileobj)
473 if fileobj is not None:
474 if not file_opened_here:
475 fileobj = _nullcontext(fileobj)
477 with fileobj as file_stream:
478 tzobj = self._read_tzfile(file_stream)
480 self._set_tzdata(tzobj)
482 def _set_tzdata(self, tzobj):
483 """ Set the time zone data of this object from a _tzfile object """
484 # Copy the relevant attributes over as private attributes
485 for attr in _tzfile.attrs:
486 setattr(self, '_' + attr, getattr(tzobj, attr))
488 def _read_tzfile(self, fileobj):
489 out = _tzfile()
491 # From tzfile(5):
492 #
493 # The time zone information files used by tzset(3)
494 # begin with the magic characters "TZif" to identify
495 # them as time zone information files, followed by
496 # sixteen bytes reserved for future use, followed by
497 # six four-byte values of type long, written in a
498 # ``standard'' byte order (the high-order byte
499 # of the value is written first).
500 if fileobj.read(4).decode() != "TZif":
501 raise ValueError("magic not found")
503 fileobj.read(16)
505 (
506 # The number of UTC/local indicators stored in the file.
507 ttisgmtcnt,
509 # The number of standard/wall indicators stored in the file.
510 ttisstdcnt,
512 # The number of leap seconds for which data is
513 # stored in the file.
514 leapcnt,
516 # The number of "transition times" for which data
517 # is stored in the file.
518 timecnt,
520 # The number of "local time types" for which data
521 # is stored in the file (must not be zero).
522 typecnt,
524 # The number of characters of "time zone
525 # abbreviation strings" stored in the file.
526 charcnt,
528 ) = struct.unpack(">6l", fileobj.read(24))
530 # The above header is followed by tzh_timecnt four-byte
531 # values of type long, sorted in ascending order.
532 # These values are written in ``standard'' byte order.
533 # Each is used as a transition time (as returned by
534 # time(2)) at which the rules for computing local time
535 # change.
537 if timecnt:
538 out.trans_list_utc = list(struct.unpack(">%dl" % timecnt,
539 fileobj.read(timecnt*4)))
540 else:
541 out.trans_list_utc = []
543 # Next come tzh_timecnt one-byte values of type unsigned
544 # char; each one tells which of the different types of
545 # ``local time'' types described in the file is associated
546 # with the same-indexed transition time. These values
547 # serve as indices into an array of ttinfo structures that
548 # appears next in the file.
550 if timecnt:
551 out.trans_idx = struct.unpack(">%dB" % timecnt,
552 fileobj.read(timecnt))
553 else:
554 out.trans_idx = []
556 # Each ttinfo structure is written as a four-byte value
557 # for tt_gmtoff of type long, in a standard byte
558 # order, followed by a one-byte value for tt_isdst
559 # and a one-byte value for tt_abbrind. In each
560 # structure, tt_gmtoff gives the number of
561 # seconds to be added to UTC, tt_isdst tells whether
562 # tm_isdst should be set by localtime(3), and
563 # tt_abbrind serves as an index into the array of
564 # time zone abbreviation characters that follow the
565 # ttinfo structure(s) in the file.
567 ttinfo = []
569 for i in range(typecnt):
570 ttinfo.append(struct.unpack(">lbb", fileobj.read(6)))
572 abbr = fileobj.read(charcnt).decode()
574 # Then there are tzh_leapcnt pairs of four-byte
575 # values, written in standard byte order; the
576 # first value of each pair gives the time (as
577 # returned by time(2)) at which a leap second
578 # occurs; the second gives the total number of
579 # leap seconds to be applied after the given time.
580 # The pairs of values are sorted in ascending order
581 # by time.
583 # Not used, for now (but seek for correct file position)
584 if leapcnt:
585 fileobj.seek(leapcnt * 8, os.SEEK_CUR)
587 # Then there are tzh_ttisstdcnt standard/wall
588 # indicators, each stored as a one-byte value;
589 # they tell whether the transition times associated
590 # with local time types were specified as standard
591 # time or wall clock time, and are used when
592 # a time zone file is used in handling POSIX-style
593 # time zone environment variables.
595 if ttisstdcnt:
596 isstd = struct.unpack(">%db" % ttisstdcnt,
597 fileobj.read(ttisstdcnt))
599 # Finally, there are tzh_ttisgmtcnt UTC/local
600 # indicators, each stored as a one-byte value;
601 # they tell whether the transition times associated
602 # with local time types were specified as UTC or
603 # local time, and are used when a time zone file
604 # is used in handling POSIX-style time zone envi-
605 # ronment variables.
607 if ttisgmtcnt:
608 isgmt = struct.unpack(">%db" % ttisgmtcnt,
609 fileobj.read(ttisgmtcnt))
611 # Build ttinfo list
612 out.ttinfo_list = []
613 for i in range(typecnt):
614 gmtoff, isdst, abbrind = ttinfo[i]
615 gmtoff = _get_supported_offset(gmtoff)
616 tti = _ttinfo()
617 tti.offset = gmtoff
618 tti.dstoffset = datetime.timedelta(0)
619 tti.delta = datetime.timedelta(seconds=gmtoff)
620 tti.isdst = isdst
621 tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)]
622 tti.isstd = (ttisstdcnt > i and isstd[i] != 0)
623 tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0)
624 out.ttinfo_list.append(tti)
626 # Replace ttinfo indexes for ttinfo objects.
627 out.trans_idx = [out.ttinfo_list[idx] for idx in out.trans_idx]
629 # Set standard, dst, and before ttinfos. before will be
630 # used when a given time is before any transitions,
631 # and will be set to the first non-dst ttinfo, or to
632 # the first dst, if all of them are dst.
633 out.ttinfo_std = None
634 out.ttinfo_dst = None
635 out.ttinfo_before = None
636 if out.ttinfo_list:
637 if not out.trans_list_utc:
638 out.ttinfo_std = out.ttinfo_first = out.ttinfo_list[0]
639 else:
640 for i in range(timecnt-1, -1, -1):
641 tti = out.trans_idx[i]
642 if not out.ttinfo_std and not tti.isdst:
643 out.ttinfo_std = tti
644 elif not out.ttinfo_dst and tti.isdst:
645 out.ttinfo_dst = tti
647 if out.ttinfo_std and out.ttinfo_dst:
648 break
649 else:
650 if out.ttinfo_dst and not out.ttinfo_std:
651 out.ttinfo_std = out.ttinfo_dst
653 for tti in out.ttinfo_list:
654 if not tti.isdst:
655 out.ttinfo_before = tti
656 break
657 else:
658 out.ttinfo_before = out.ttinfo_list[0]
660 # Now fix transition times to become relative to wall time.
661 #
662 # I'm not sure about this. In my tests, the tz source file
663 # is setup to wall time, and in the binary file isstd and
664 # isgmt are off, so it should be in wall time. OTOH, it's
665 # always in gmt time. Let me know if you have comments
666 # about this.
667 lastdst = None
668 lastoffset = None
669 lastdstoffset = None
670 lastbaseoffset = None
671 out.trans_list = []
673 for i, tti in enumerate(out.trans_idx):
674 offset = tti.offset
675 dstoffset = 0
677 if lastdst is not None:
678 if tti.isdst:
679 if not lastdst:
680 dstoffset = offset - lastoffset
682 if not dstoffset and lastdstoffset:
683 dstoffset = lastdstoffset
685 tti.dstoffset = datetime.timedelta(seconds=dstoffset)
686 lastdstoffset = dstoffset
688 # If a time zone changes its base offset during a DST transition,
689 # then you need to adjust by the previous base offset to get the
690 # transition time in local time. Otherwise you use the current
691 # base offset. Ideally, I would have some mathematical proof of
692 # why this is true, but I haven't really thought about it enough.
693 baseoffset = offset - dstoffset
694 adjustment = baseoffset
695 if (lastbaseoffset is not None and baseoffset != lastbaseoffset
696 and tti.isdst != lastdst):
697 # The base DST has changed
698 adjustment = lastbaseoffset
700 lastdst = tti.isdst
701 lastoffset = offset
702 lastbaseoffset = baseoffset
704 out.trans_list.append(out.trans_list_utc[i] + adjustment)
706 out.trans_idx = tuple(out.trans_idx)
707 out.trans_list = tuple(out.trans_list)
708 out.trans_list_utc = tuple(out.trans_list_utc)
710 return out
712 def _find_last_transition(self, dt, in_utc=False):
713 # If there's no list, there are no transitions to find
714 if not self._trans_list:
715 return None
717 timestamp = _datetime_to_timestamp(dt)
719 # Find where the timestamp fits in the transition list - if the
720 # timestamp is a transition time, it's part of the "after" period.
721 trans_list = self._trans_list_utc if in_utc else self._trans_list
722 idx = bisect.bisect_right(trans_list, timestamp)
724 # We want to know when the previous transition was, so subtract off 1
725 return idx - 1
727 def _get_ttinfo(self, idx):
728 # For no list or after the last transition, default to _ttinfo_std
729 if idx is None or (idx + 1) >= len(self._trans_list):
730 return self._ttinfo_std
732 # If there is a list and the time is before it, return _ttinfo_before
733 if idx < 0:
734 return self._ttinfo_before
736 return self._trans_idx[idx]
738 def _find_ttinfo(self, dt):
739 idx = self._resolve_ambiguous_time(dt)
741 return self._get_ttinfo(idx)
743 def fromutc(self, dt):
744 """
745 The ``tzfile`` implementation of :py:func:`datetime.tzinfo.fromutc`.
747 :param dt:
748 A :py:class:`datetime.datetime` object.
750 :raises TypeError:
751 Raised if ``dt`` is not a :py:class:`datetime.datetime` object.
753 :raises ValueError:
754 Raised if this is called with a ``dt`` which does not have this
755 ``tzinfo`` attached.
757 :return:
758 Returns a :py:class:`datetime.datetime` object representing the
759 wall time in ``self``'s time zone.
760 """
761 # These isinstance checks are in datetime.tzinfo, so we'll preserve
762 # them, even if we don't care about duck typing.
763 if not isinstance(dt, datetime.datetime):
764 raise TypeError("fromutc() requires a datetime argument")
766 if dt.tzinfo is not self:
767 raise ValueError("dt.tzinfo is not self")
769 # First treat UTC as wall time and get the transition we're in.
770 idx = self._find_last_transition(dt, in_utc=True)
771 tti = self._get_ttinfo(idx)
773 dt_out = dt + datetime.timedelta(seconds=tti.offset)
775 fold = self.is_ambiguous(dt_out, idx=idx)
777 return enfold(dt_out, fold=int(fold))
779 def is_ambiguous(self, dt, idx=None):
780 """
781 Whether or not the "wall time" of a given datetime is ambiguous in this
782 zone.
784 :param dt:
785 A :py:class:`datetime.datetime`, naive or time zone aware.
788 :return:
789 Returns ``True`` if ambiguous, ``False`` otherwise.
791 .. versionadded:: 2.6.0
792 """
793 if idx is None:
794 idx = self._find_last_transition(dt)
796 # Calculate the difference in offsets from current to previous
797 timestamp = _datetime_to_timestamp(dt)
798 tti = self._get_ttinfo(idx)
800 if idx is None or idx <= 0:
801 return False
803 od = self._get_ttinfo(idx - 1).offset - tti.offset
804 tt = self._trans_list[idx] # Transition time
806 return timestamp < tt + od
808 def _resolve_ambiguous_time(self, dt):
809 idx = self._find_last_transition(dt)
811 # If we have no transitions, return the index
812 _fold = self._fold(dt)
813 if idx is None or idx == 0:
814 return idx
816 # If it's ambiguous and we're in a fold, shift to a different index.
817 idx_offset = int(not _fold and self.is_ambiguous(dt, idx))
819 return idx - idx_offset
821 def utcoffset(self, dt):
822 if dt is None:
823 return None
825 if not self._ttinfo_std:
826 return ZERO
828 return self._find_ttinfo(dt).delta
830 def dst(self, dt):
831 if dt is None:
832 return None
834 if not self._ttinfo_dst:
835 return ZERO
837 tti = self._find_ttinfo(dt)
839 if not tti.isdst:
840 return ZERO
842 # The documentation says that utcoffset()-dst() must
843 # be constant for every dt.
844 return tti.dstoffset
846 @tzname_in_python2
847 def tzname(self, dt):
848 if not self._ttinfo_std or dt is None:
849 return None
850 return self._find_ttinfo(dt).abbr
852 def __eq__(self, other):
853 if not isinstance(other, tzfile):
854 return NotImplemented
855 return (self._trans_list == other._trans_list and
856 self._trans_idx == other._trans_idx and
857 self._ttinfo_list == other._ttinfo_list)
859 __hash__ = None
861 def __ne__(self, other):
862 return not (self == other)
864 def __repr__(self):
865 return "%s(%s)" % (self.__class__.__name__, repr(self._filename))
867 def __reduce__(self):
868 return self.__reduce_ex__(None)
870 def __reduce_ex__(self, protocol):
871 return (self.__class__, (None, self._filename), self.__dict__)
874class tzrange(tzrangebase):
875 """
876 The ``tzrange`` object is a time zone specified by a set of offsets and
877 abbreviations, equivalent to the way the ``TZ`` variable can be specified
878 in POSIX-like systems, but using Python delta objects to specify DST
879 start, end and offsets.
881 :param stdabbr:
882 The abbreviation for standard time (e.g. ``'EST'``).
884 :param stdoffset:
885 An integer or :class:`datetime.timedelta` object or equivalent
886 specifying the base offset from UTC.
888 If unspecified, +00:00 is used.
890 :param dstabbr:
891 The abbreviation for DST / "Summer" time (e.g. ``'EDT'``).
893 If specified, with no other DST information, DST is assumed to occur
894 and the default behavior or ``dstoffset``, ``start`` and ``end`` is
895 used. If unspecified and no other DST information is specified, it
896 is assumed that this zone has no DST.
898 If this is unspecified and other DST information is *is* specified,
899 DST occurs in the zone but the time zone abbreviation is left
900 unchanged.
902 :param dstoffset:
903 A an integer or :class:`datetime.timedelta` object or equivalent
904 specifying the UTC offset during DST. If unspecified and any other DST
905 information is specified, it is assumed to be the STD offset +1 hour.
907 :param start:
908 A :class:`relativedelta.relativedelta` object or equivalent specifying
909 the time and time of year that daylight savings time starts. To
910 specify, for example, that DST starts at 2AM on the 2nd Sunday in
911 March, pass:
913 ``relativedelta(hours=2, month=3, day=1, weekday=SU(+2))``
915 If unspecified and any other DST information is specified, the default
916 value is 2 AM on the first Sunday in April.
918 :param end:
919 A :class:`relativedelta.relativedelta` object or equivalent
920 representing the time and time of year that daylight savings time
921 ends, with the same specification method as in ``start``. One note is
922 that this should point to the first time in the *standard* zone, so if
923 a transition occurs at 2AM in the DST zone and the clocks are set back
924 1 hour to 1AM, set the ``hours`` parameter to +1.
927 **Examples:**
929 .. testsetup:: tzrange
931 from dateutil.tz import tzrange, tzstr
933 .. doctest:: tzrange
935 >>> tzstr('EST5EDT') == tzrange("EST", -18000, "EDT")
936 True
938 >>> from dateutil.relativedelta import *
939 >>> range1 = tzrange("EST", -18000, "EDT")
940 >>> range2 = tzrange("EST", -18000, "EDT", -14400,
941 ... relativedelta(hours=+2, month=4, day=1,
942 ... weekday=SU(+1)),
943 ... relativedelta(hours=+1, month=10, day=31,
944 ... weekday=SU(-1)))
945 >>> tzstr('EST5EDT') == range1 == range2
946 True
948 """
949 def __init__(self, stdabbr, stdoffset=None,
950 dstabbr=None, dstoffset=None,
951 start=None, end=None):
953 global relativedelta
954 from dateutil import relativedelta
956 self._std_abbr = stdabbr
957 self._dst_abbr = dstabbr
959 try:
960 stdoffset = stdoffset.total_seconds()
961 except (TypeError, AttributeError):
962 pass
964 try:
965 dstoffset = dstoffset.total_seconds()
966 except (TypeError, AttributeError):
967 pass
969 if stdoffset is not None:
970 self._std_offset = datetime.timedelta(seconds=stdoffset)
971 else:
972 self._std_offset = ZERO
974 if dstoffset is not None:
975 self._dst_offset = datetime.timedelta(seconds=dstoffset)
976 elif dstabbr and stdoffset is not None:
977 self._dst_offset = self._std_offset + datetime.timedelta(hours=+1)
978 else:
979 self._dst_offset = ZERO
981 if dstabbr and start is None:
982 self._start_delta = relativedelta.relativedelta(
983 hours=+2, month=4, day=1, weekday=relativedelta.SU(+1))
984 else:
985 self._start_delta = start
987 if dstabbr and end is None:
988 self._end_delta = relativedelta.relativedelta(
989 hours=+1, month=10, day=31, weekday=relativedelta.SU(-1))
990 else:
991 self._end_delta = end
993 self._dst_base_offset_ = self._dst_offset - self._std_offset
994 self.hasdst = bool(self._start_delta)
996 def transitions(self, year):
997 """
998 For a given year, get the DST on and off transition times, expressed
999 always on the standard time side. For zones with no transitions, this
1000 function returns ``None``.
1002 :param year:
1003 The year whose transitions you would like to query.
1005 :return:
1006 Returns a :class:`tuple` of :class:`datetime.datetime` objects,
1007 ``(dston, dstoff)`` for zones with an annual DST transition, or
1008 ``None`` for fixed offset zones.
1009 """
1010 if not self.hasdst:
1011 return None
1013 base_year = datetime.datetime(year, 1, 1)
1015 start = base_year + self._start_delta
1016 end = base_year + self._end_delta
1018 return (start, end)
1020 def __eq__(self, other):
1021 if not isinstance(other, tzrange):
1022 return NotImplemented
1024 return (self._std_abbr == other._std_abbr and
1025 self._dst_abbr == other._dst_abbr and
1026 self._std_offset == other._std_offset and
1027 self._dst_offset == other._dst_offset and
1028 self._start_delta == other._start_delta and
1029 self._end_delta == other._end_delta)
1031 @property
1032 def _dst_base_offset(self):
1033 return self._dst_base_offset_
1036@six.add_metaclass(_TzStrFactory)
1037class tzstr(tzrange):
1038 """
1039 ``tzstr`` objects are time zone objects specified by a time-zone string as
1040 it would be passed to a ``TZ`` variable on POSIX-style systems (see
1041 the `GNU C Library: TZ Variable`_ for more details).
1043 There is one notable exception, which is that POSIX-style time zones use an
1044 inverted offset format, so normally ``GMT+3`` would be parsed as an offset
1045 3 hours *behind* GMT. The ``tzstr`` time zone object will parse this as an
1046 offset 3 hours *ahead* of GMT. If you would like to maintain the POSIX
1047 behavior, pass a ``True`` value to ``posix_offset``.
1049 The :class:`tzrange` object provides the same functionality, but is
1050 specified using :class:`relativedelta.relativedelta` objects. rather than
1051 strings.
1053 :param s:
1054 A time zone string in ``TZ`` variable format. This can be a
1055 :class:`bytes` (2.x: :class:`str`), :class:`str` (2.x:
1056 :class:`unicode`) or a stream emitting unicode characters
1057 (e.g. :class:`StringIO`).
1059 :param posix_offset:
1060 Optional. If set to ``True``, interpret strings such as ``GMT+3`` or
1061 ``UTC+3`` as being 3 hours *behind* UTC rather than ahead, per the
1062 POSIX standard.
1064 .. caution::
1066 Prior to version 2.7.0, this function also supported time zones
1067 in the format:
1069 * ``EST5EDT,4,0,6,7200,10,0,26,7200,3600``
1070 * ``EST5EDT,4,1,0,7200,10,-1,0,7200,3600``
1072 This format is non-standard and has been deprecated; this function
1073 will raise a :class:`DeprecatedTZFormatWarning` until
1074 support is removed in a future version.
1076 .. _`GNU C Library: TZ Variable`:
1077 https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
1078 """
1079 def __init__(self, s, posix_offset=False):
1080 global parser
1081 from dateutil.parser import _parser as parser
1083 self._s = s
1085 res = parser._parsetz(s)
1086 if res is None or res.any_unused_tokens:
1087 raise ValueError("unknown string format")
1089 # Here we break the compatibility with the TZ variable handling.
1090 # GMT-3 actually *means* the timezone -3.
1091 if res.stdabbr in ("GMT", "UTC") and not posix_offset:
1092 res.stdoffset *= -1
1094 # We must initialize it first, since _delta() needs
1095 # _std_offset and _dst_offset set. Use False in start/end
1096 # to avoid building it two times.
1097 tzrange.__init__(self, res.stdabbr, res.stdoffset,
1098 res.dstabbr, res.dstoffset,
1099 start=False, end=False)
1101 if not res.dstabbr:
1102 self._start_delta = None
1103 self._end_delta = None
1104 else:
1105 self._start_delta = self._delta(res.start)
1106 if self._start_delta:
1107 self._end_delta = self._delta(res.end, isend=1)
1109 self.hasdst = bool(self._start_delta)
1111 def _delta(self, x, isend=0):
1112 from dateutil import relativedelta
1113 kwargs = {}
1114 if x.month is not None:
1115 kwargs["month"] = x.month
1116 if x.weekday is not None:
1117 kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week)
1118 if x.week > 0:
1119 kwargs["day"] = 1
1120 else:
1121 kwargs["day"] = 31
1122 elif x.day:
1123 kwargs["day"] = x.day
1124 elif x.yday is not None:
1125 kwargs["yearday"] = x.yday
1126 elif x.jyday is not None:
1127 kwargs["nlyearday"] = x.jyday
1128 if not kwargs:
1129 # Default is to start on first sunday of april, and end
1130 # on last sunday of october.
1131 if not isend:
1132 kwargs["month"] = 4
1133 kwargs["day"] = 1
1134 kwargs["weekday"] = relativedelta.SU(+1)
1135 else:
1136 kwargs["month"] = 10
1137 kwargs["day"] = 31
1138 kwargs["weekday"] = relativedelta.SU(-1)
1139 if x.time is not None:
1140 kwargs["seconds"] = x.time
1141 else:
1142 # Default is 2AM.
1143 kwargs["seconds"] = 7200
1144 if isend:
1145 # Convert to standard time, to follow the documented way
1146 # of working with the extra hour. See the documentation
1147 # of the tzinfo class.
1148 delta = self._dst_offset - self._std_offset
1149 kwargs["seconds"] -= delta.seconds + delta.days * 86400
1150 return relativedelta.relativedelta(**kwargs)
1152 def __repr__(self):
1153 return "%s(%s)" % (self.__class__.__name__, repr(self._s))
1156class _tzicalvtzcomp(object):
1157 def __init__(self, tzoffsetfrom, tzoffsetto, isdst,
1158 tzname=None, rrule=None):
1159 self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom)
1160 self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto)
1161 self.tzoffsetdiff = self.tzoffsetto - self.tzoffsetfrom
1162 self.isdst = isdst
1163 self.tzname = tzname
1164 self.rrule = rrule
1167class _tzicalvtz(_tzinfo):
1168 def __init__(self, tzid, comps=[]):
1169 super(_tzicalvtz, self).__init__()
1171 self._tzid = tzid
1172 self._comps = comps
1173 self._cachedate = []
1174 self._cachecomp = []
1175 self._cache_lock = _thread.allocate_lock()
1177 def _find_comp(self, dt):
1178 if len(self._comps) == 1:
1179 return self._comps[0]
1181 dt = dt.replace(tzinfo=None)
1183 try:
1184 with self._cache_lock:
1185 return self._cachecomp[self._cachedate.index(
1186 (dt, self._fold(dt)))]
1187 except ValueError:
1188 pass
1190 lastcompdt = None
1191 lastcomp = None
1193 for comp in self._comps:
1194 compdt = self._find_compdt(comp, dt)
1196 if compdt and (not lastcompdt or lastcompdt < compdt):
1197 lastcompdt = compdt
1198 lastcomp = comp
1200 if not lastcomp:
1201 # RFC says nothing about what to do when a given
1202 # time is before the first onset date. We'll look for the
1203 # first standard component, or the first component, if
1204 # none is found.
1205 for comp in self._comps:
1206 if not comp.isdst:
1207 lastcomp = comp
1208 break
1209 else:
1210 lastcomp = comp[0]
1212 with self._cache_lock:
1213 self._cachedate.insert(0, (dt, self._fold(dt)))
1214 self._cachecomp.insert(0, lastcomp)
1216 if len(self._cachedate) > 10:
1217 self._cachedate.pop()
1218 self._cachecomp.pop()
1220 return lastcomp
1222 def _find_compdt(self, comp, dt):
1223 if comp.tzoffsetdiff < ZERO and self._fold(dt):
1224 dt -= comp.tzoffsetdiff
1226 compdt = comp.rrule.before(dt, inc=True)
1228 return compdt
1230 def utcoffset(self, dt):
1231 if dt is None:
1232 return None
1234 return self._find_comp(dt).tzoffsetto
1236 def dst(self, dt):
1237 comp = self._find_comp(dt)
1238 if comp.isdst:
1239 return comp.tzoffsetdiff
1240 else:
1241 return ZERO
1243 @tzname_in_python2
1244 def tzname(self, dt):
1245 return self._find_comp(dt).tzname
1247 def __repr__(self):
1248 return "<tzicalvtz %s>" % repr(self._tzid)
1250 __reduce__ = object.__reduce__
1253class tzical(object):
1254 """
1255 This object is designed to parse an iCalendar-style ``VTIMEZONE`` structure
1256 as set out in `RFC 5545`_ Section 4.6.5 into one or more `tzinfo` objects.
1258 :param `fileobj`:
1259 A file or stream in iCalendar format, which should be UTF-8 encoded
1260 with CRLF endings.
1262 .. _`RFC 5545`: https://tools.ietf.org/html/rfc5545
1263 """
1264 def __init__(self, fileobj):
1265 global rrule
1266 from dateutil import rrule
1268 if isinstance(fileobj, string_types):
1269 self._s = fileobj
1270 # ical should be encoded in UTF-8 with CRLF
1271 fileobj = open(fileobj, 'r')
1272 else:
1273 self._s = getattr(fileobj, 'name', repr(fileobj))
1274 fileobj = _nullcontext(fileobj)
1276 self._vtz = {}
1278 with fileobj as fobj:
1279 self._parse_rfc(fobj.read())
1281 def keys(self):
1282 """
1283 Retrieves the available time zones as a list.
1284 """
1285 return list(self._vtz.keys())
1287 def get(self, tzid=None):
1288 """
1289 Retrieve a :py:class:`datetime.tzinfo` object by its ``tzid``.
1291 :param tzid:
1292 If there is exactly one time zone available, omitting ``tzid``
1293 or passing :py:const:`None` value returns it. Otherwise a valid
1294 key (which can be retrieved from :func:`keys`) is required.
1296 :raises ValueError:
1297 Raised if ``tzid`` is not specified but there are either more
1298 or fewer than 1 zone defined.
1300 :returns:
1301 Returns either a :py:class:`datetime.tzinfo` object representing
1302 the relevant time zone or :py:const:`None` if the ``tzid`` was
1303 not found.
1304 """
1305 if tzid is None:
1306 if len(self._vtz) == 0:
1307 raise ValueError("no timezones defined")
1308 elif len(self._vtz) > 1:
1309 raise ValueError("more than one timezone available")
1310 tzid = next(iter(self._vtz))
1312 return self._vtz.get(tzid)
1314 def _parse_offset(self, s):
1315 s = s.strip()
1316 if not s:
1317 raise ValueError("empty offset")
1318 if s[0] in ('+', '-'):
1319 signal = (-1, +1)[s[0] == '+']
1320 s = s[1:]
1321 else:
1322 signal = +1
1323 if len(s) == 4:
1324 return (int(s[:2]) * 3600 + int(s[2:]) * 60) * signal
1325 elif len(s) == 6:
1326 return (int(s[:2]) * 3600 + int(s[2:4]) * 60 + int(s[4:])) * signal
1327 else:
1328 raise ValueError("invalid offset: " + s)
1330 def _parse_rfc(self, s):
1331 lines = s.splitlines()
1332 if not lines:
1333 raise ValueError("empty string")
1335 # Unfold
1336 i = 0
1337 while i < len(lines):
1338 line = lines[i].rstrip()
1339 if not line:
1340 del lines[i]
1341 elif i > 0 and line[0] == " ":
1342 lines[i-1] += line[1:]
1343 del lines[i]
1344 else:
1345 i += 1
1347 tzid = None
1348 comps = []
1349 invtz = False
1350 comptype = None
1351 for line in lines:
1352 if not line:
1353 continue
1354 name, value = line.split(':', 1)
1355 parms = name.split(';')
1356 if not parms:
1357 raise ValueError("empty property name")
1358 name = parms[0].upper()
1359 parms = parms[1:]
1360 if invtz:
1361 if name == "BEGIN":
1362 if value in ("STANDARD", "DAYLIGHT"):
1363 # Process component
1364 pass
1365 else:
1366 raise ValueError("unknown component: "+value)
1367 comptype = value
1368 founddtstart = False
1369 tzoffsetfrom = None
1370 tzoffsetto = None
1371 rrulelines = []
1372 tzname = None
1373 elif name == "END":
1374 if value == "VTIMEZONE":
1375 if comptype:
1376 raise ValueError("component not closed: "+comptype)
1377 if not tzid:
1378 raise ValueError("mandatory TZID not found")
1379 if not comps:
1380 raise ValueError(
1381 "at least one component is needed")
1382 # Process vtimezone
1383 self._vtz[tzid] = _tzicalvtz(tzid, comps)
1384 invtz = False
1385 elif value == comptype:
1386 if not founddtstart:
1387 raise ValueError("mandatory DTSTART not found")
1388 if tzoffsetfrom is None:
1389 raise ValueError(
1390 "mandatory TZOFFSETFROM not found")
1391 if tzoffsetto is None:
1392 raise ValueError(
1393 "mandatory TZOFFSETFROM not found")
1394 # Process component
1395 rr = None
1396 if rrulelines:
1397 rr = rrule.rrulestr("\n".join(rrulelines),
1398 compatible=True,
1399 ignoretz=True,
1400 cache=True)
1401 comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto,
1402 (comptype == "DAYLIGHT"),
1403 tzname, rr)
1404 comps.append(comp)
1405 comptype = None
1406 else:
1407 raise ValueError("invalid component end: "+value)
1408 elif comptype:
1409 if name == "DTSTART":
1410 # DTSTART in VTIMEZONE takes a subset of valid RRULE
1411 # values under RFC 5545.
1412 for parm in parms:
1413 if parm != 'VALUE=DATE-TIME':
1414 msg = ('Unsupported DTSTART param in ' +
1415 'VTIMEZONE: ' + parm)
1416 raise ValueError(msg)
1417 rrulelines.append(line)
1418 founddtstart = True
1419 elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"):
1420 rrulelines.append(line)
1421 elif name == "TZOFFSETFROM":
1422 if parms:
1423 raise ValueError(
1424 "unsupported %s parm: %s " % (name, parms[0]))
1425 tzoffsetfrom = self._parse_offset(value)
1426 elif name == "TZOFFSETTO":
1427 if parms:
1428 raise ValueError(
1429 "unsupported TZOFFSETTO parm: "+parms[0])
1430 tzoffsetto = self._parse_offset(value)
1431 elif name == "TZNAME":
1432 if parms:
1433 raise ValueError(
1434 "unsupported TZNAME parm: "+parms[0])
1435 tzname = value
1436 elif name == "COMMENT":
1437 pass
1438 else:
1439 raise ValueError("unsupported property: "+name)
1440 else:
1441 if name == "TZID":
1442 if parms:
1443 raise ValueError(
1444 "unsupported TZID parm: "+parms[0])
1445 tzid = value
1446 elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"):
1447 pass
1448 else:
1449 raise ValueError("unsupported property: "+name)
1450 elif name == "BEGIN" and value == "VTIMEZONE":
1451 tzid = None
1452 comps = []
1453 invtz = True
1455 def __repr__(self):
1456 return "%s(%s)" % (self.__class__.__name__, repr(self._s))
1459if sys.platform != "win32":
1460 TZFILES = ["/etc/localtime", "localtime"]
1461 TZPATHS = ["/usr/share/zoneinfo",
1462 "/usr/lib/zoneinfo",
1463 "/usr/share/lib/zoneinfo",
1464 "/etc/zoneinfo"]
1465else:
1466 TZFILES = []
1467 TZPATHS = []
1470def __get_gettz():
1471 tzlocal_classes = (tzlocal,)
1472 if tzwinlocal is not None:
1473 tzlocal_classes += (tzwinlocal,)
1475 class GettzFunc(object):
1476 """
1477 Retrieve a time zone object from a string representation
1479 This function is intended to retrieve the :py:class:`tzinfo` subclass
1480 that best represents the time zone that would be used if a POSIX
1481 `TZ variable`_ were set to the same value.
1483 If no argument or an empty string is passed to ``gettz``, local time
1484 is returned:
1486 .. code-block:: python3
1488 >>> gettz()
1489 tzfile('/etc/localtime')
1491 This function is also the preferred way to map IANA tz database keys
1492 to :class:`tzfile` objects:
1494 .. code-block:: python3
1496 >>> gettz('Pacific/Kiritimati')
1497 tzfile('/usr/share/zoneinfo/Pacific/Kiritimati')
1499 On Windows, the standard is extended to include the Windows-specific
1500 zone names provided by the operating system:
1502 .. code-block:: python3
1504 >>> gettz('Egypt Standard Time')
1505 tzwin('Egypt Standard Time')
1507 Passing a GNU ``TZ`` style string time zone specification returns a
1508 :class:`tzstr` object:
1510 .. code-block:: python3
1512 >>> gettz('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3')
1513 tzstr('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3')
1515 :param name:
1516 A time zone name (IANA, or, on Windows, Windows keys), location of
1517 a ``tzfile(5)`` zoneinfo file or ``TZ`` variable style time zone
1518 specifier. An empty string, no argument or ``None`` is interpreted
1519 as local time.
1521 :return:
1522 Returns an instance of one of ``dateutil``'s :py:class:`tzinfo`
1523 subclasses.
1525 .. versionchanged:: 2.7.0
1527 After version 2.7.0, any two calls to ``gettz`` using the same
1528 input strings will return the same object:
1530 .. code-block:: python3
1532 >>> tz.gettz('America/Chicago') is tz.gettz('America/Chicago')
1533 True
1535 In addition to improving performance, this ensures that
1536 `"same zone" semantics`_ are used for datetimes in the same zone.
1539 .. _`TZ variable`:
1540 https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
1542 .. _`"same zone" semantics`:
1543 https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html
1544 """
1545 def __init__(self):
1547 self.__instances = weakref.WeakValueDictionary()
1548 self.__strong_cache_size = 8
1549 self.__strong_cache = OrderedDict()
1550 self._cache_lock = _thread.allocate_lock()
1552 def __call__(self, name=None):
1553 with self._cache_lock:
1554 rv = self.__instances.get(name, None)
1556 if rv is None:
1557 rv = self.nocache(name=name)
1558 if not (name is None
1559 or isinstance(rv, tzlocal_classes)
1560 or rv is None):
1561 # tzlocal is slightly more complicated than the other
1562 # time zone providers because it depends on environment
1563 # at construction time, so don't cache that.
1564 #
1565 # We also cannot store weak references to None, so we
1566 # will also not store that.
1567 self.__instances[name] = rv
1568 else:
1569 # No need for strong caching, return immediately
1570 return rv
1572 self.__strong_cache[name] = self.__strong_cache.pop(name, rv)
1574 if len(self.__strong_cache) > self.__strong_cache_size:
1575 self.__strong_cache.popitem(last=False)
1577 return rv
1579 def set_cache_size(self, size):
1580 with self._cache_lock:
1581 self.__strong_cache_size = size
1582 while len(self.__strong_cache) > size:
1583 self.__strong_cache.popitem(last=False)
1585 def cache_clear(self):
1586 with self._cache_lock:
1587 self.__instances = weakref.WeakValueDictionary()
1588 self.__strong_cache.clear()
1590 @staticmethod
1591 def nocache(name=None):
1592 """A non-cached version of gettz"""
1593 tz = None
1594 if not name:
1595 try:
1596 name = os.environ["TZ"]
1597 except KeyError:
1598 pass
1599 if name is None or name in ("", ":"):
1600 for filepath in TZFILES:
1601 if not os.path.isabs(filepath):
1602 filename = filepath
1603 for path in TZPATHS:
1604 filepath = os.path.join(path, filename)
1605 if os.path.isfile(filepath):
1606 break
1607 else:
1608 continue
1609 if os.path.isfile(filepath):
1610 try:
1611 tz = tzfile(filepath)
1612 break
1613 except (IOError, OSError, ValueError):
1614 pass
1615 else:
1616 tz = tzlocal()
1617 else:
1618 try:
1619 if name.startswith(":"):
1620 name = name[1:]
1621 except TypeError as e:
1622 if isinstance(name, bytes):
1623 new_msg = "gettz argument should be str, not bytes"
1624 six.raise_from(TypeError(new_msg), e)
1625 else:
1626 raise
1627 if os.path.isabs(name):
1628 if os.path.isfile(name):
1629 tz = tzfile(name)
1630 else:
1631 tz = None
1632 else:
1633 for path in TZPATHS:
1634 filepath = os.path.join(path, name)
1635 if not os.path.isfile(filepath):
1636 filepath = filepath.replace(' ', '_')
1637 if not os.path.isfile(filepath):
1638 continue
1639 try:
1640 tz = tzfile(filepath)
1641 break
1642 except (IOError, OSError, ValueError):
1643 pass
1644 else:
1645 tz = None
1646 if tzwin is not None:
1647 try:
1648 tz = tzwin(name)
1649 except (WindowsError, UnicodeEncodeError):
1650 # UnicodeEncodeError is for Python 2.7 compat
1651 tz = None
1653 if not tz:
1654 from dateutil.zoneinfo import get_zonefile_instance
1655 tz = get_zonefile_instance().get(name)
1657 if not tz:
1658 for c in name:
1659 # name is not a tzstr unless it has at least
1660 # one offset. For short values of "name", an
1661 # explicit for loop seems to be the fastest way
1662 # To determine if a string contains a digit
1663 if c in "0123456789":
1664 try:
1665 tz = tzstr(name)
1666 except ValueError:
1667 pass
1668 break
1669 else:
1670 if name in ("GMT", "UTC"):
1671 tz = UTC
1672 elif name in time.tzname:
1673 tz = tzlocal()
1674 return tz
1676 return GettzFunc()
1679gettz = __get_gettz()
1680del __get_gettz
1683def datetime_exists(dt, tz=None):
1684 """
1685 Given a datetime and a time zone, determine whether or not a given datetime
1686 would fall in a gap.
1688 :param dt:
1689 A :class:`datetime.datetime` (whose time zone will be ignored if ``tz``
1690 is provided.)
1692 :param tz:
1693 A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If
1694 ``None`` or not provided, the datetime's own time zone will be used.
1696 :return:
1697 Returns a boolean value whether or not the "wall time" exists in
1698 ``tz``.
1700 .. versionadded:: 2.7.0
1701 """
1702 if tz is None:
1703 if dt.tzinfo is None:
1704 raise ValueError('Datetime is naive and no time zone provided.')
1705 tz = dt.tzinfo
1707 dt = dt.replace(tzinfo=None)
1709 # This is essentially a test of whether or not the datetime can survive
1710 # a round trip to UTC.
1711 dt_rt = dt.replace(tzinfo=tz).astimezone(UTC).astimezone(tz)
1712 dt_rt = dt_rt.replace(tzinfo=None)
1714 return dt == dt_rt
1717def datetime_ambiguous(dt, tz=None):
1718 """
1719 Given a datetime and a time zone, determine whether or not a given datetime
1720 is ambiguous (i.e if there are two times differentiated only by their DST
1721 status).
1723 :param dt:
1724 A :class:`datetime.datetime` (whose time zone will be ignored if ``tz``
1725 is provided.)
1727 :param tz:
1728 A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If
1729 ``None`` or not provided, the datetime's own time zone will be used.
1731 :return:
1732 Returns a boolean value whether or not the "wall time" is ambiguous in
1733 ``tz``.
1735 .. versionadded:: 2.6.0
1736 """
1737 if tz is None:
1738 if dt.tzinfo is None:
1739 raise ValueError('Datetime is naive and no time zone provided.')
1741 tz = dt.tzinfo
1743 # If a time zone defines its own "is_ambiguous" function, we'll use that.
1744 is_ambiguous_fn = getattr(tz, 'is_ambiguous', None)
1745 if is_ambiguous_fn is not None:
1746 try:
1747 return tz.is_ambiguous(dt)
1748 except Exception:
1749 pass
1751 # If it doesn't come out and tell us it's ambiguous, we'll just check if
1752 # the fold attribute has any effect on this particular date and time.
1753 dt = dt.replace(tzinfo=tz)
1754 wall_0 = enfold(dt, fold=0)
1755 wall_1 = enfold(dt, fold=1)
1757 same_offset = wall_0.utcoffset() == wall_1.utcoffset()
1758 same_dst = wall_0.dst() == wall_1.dst()
1760 return not (same_offset and same_dst)
1763def resolve_imaginary(dt):
1764 """
1765 Given a datetime that may be imaginary, return an existing datetime.
1767 This function assumes that an imaginary datetime represents what the
1768 wall time would be in a zone had the offset transition not occurred, so
1769 it will always fall forward by the transition's change in offset.
1771 .. doctest::
1773 >>> from dateutil import tz
1774 >>> from datetime import datetime
1775 >>> NYC = tz.gettz('America/New_York')
1776 >>> print(tz.resolve_imaginary(datetime(2017, 3, 12, 2, 30, tzinfo=NYC)))
1777 2017-03-12 03:30:00-04:00
1779 >>> KIR = tz.gettz('Pacific/Kiritimati')
1780 >>> print(tz.resolve_imaginary(datetime(1995, 1, 1, 12, 30, tzinfo=KIR)))
1781 1995-01-02 12:30:00+14:00
1783 As a note, :func:`datetime.astimezone` is guaranteed to produce a valid,
1784 existing datetime, so a round-trip to and from UTC is sufficient to get
1785 an extant datetime, however, this generally "falls back" to an earlier time
1786 rather than falling forward to the STD side (though no guarantees are made
1787 about this behavior).
1789 :param dt:
1790 A :class:`datetime.datetime` which may or may not exist.
1792 :return:
1793 Returns an existing :class:`datetime.datetime`. If ``dt`` was not
1794 imaginary, the datetime returned is guaranteed to be the same object
1795 passed to the function.
1797 .. versionadded:: 2.7.0
1798 """
1799 if dt.tzinfo is not None and not datetime_exists(dt):
1801 curr_offset = (dt + datetime.timedelta(hours=24)).utcoffset()
1802 old_offset = (dt - datetime.timedelta(hours=24)).utcoffset()
1804 dt += curr_offset - old_offset
1806 return dt
1809def _datetime_to_timestamp(dt):
1810 """
1811 Convert a :class:`datetime.datetime` object to an epoch timestamp in
1812 seconds since January 1, 1970, ignoring the time zone.
1813 """
1814 return (dt.replace(tzinfo=None) - EPOCH).total_seconds()
1817if sys.version_info >= (3, 6):
1818 def _get_supported_offset(second_offset):
1819 return second_offset
1820else:
1821 def _get_supported_offset(second_offset):
1822 # For python pre-3.6, round to full-minutes if that's not the case.
1823 # Python's datetime doesn't accept sub-minute timezones. Check
1824 # http://python.org/sf/1447945 or https://bugs.python.org/issue5288
1825 # for some information.
1826 old_offset = second_offset
1827 calculated_offset = 60 * ((second_offset + 30) // 60)
1828 return calculated_offset
1831try:
1832 # Python 3.7 feature
1833 from contextlib import nullcontext as _nullcontext
1834except ImportError:
1835 class _nullcontext(object):
1836 """
1837 Class for wrapping contexts so that they are passed through in a
1838 with statement.
1839 """
1840 def __init__(self, context):
1841 self.context = context
1843 def __enter__(self):
1844 return self.context
1846 def __exit__(*args, **kwargs):
1847 pass
1849# vim:ts=4:sw=4:et