comparison env/lib/python3.7/site-packages/dateutil/tz/tz.py @ 0:26e78fe6e8c4 draft

"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author shellac
date Sat, 02 May 2020 07:14:21 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:26e78fe6e8c4
1 # -*- coding: utf-8 -*-
2 """
3 This module offers timezone implementations subclassing the abstract
4 :py:class:`datetime.tzinfo` type. There are classes to handle tzfile format
5 files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`,
6 etc), TZ environment string (in all known formats), given ranges (with help
7 from relative deltas), local machine timezone, fixed offset timezone, and UTC
8 timezone.
9 """
10 import datetime
11 import struct
12 import time
13 import sys
14 import os
15 import bisect
16 import weakref
17 from collections import OrderedDict
18
19 import six
20 from six import string_types
21 from six.moves import _thread
22 from ._common import tzname_in_python2, _tzinfo
23 from ._common import tzrangebase, enfold
24 from ._common import _validate_fromutc_inputs
25
26 from ._factories import _TzSingleton, _TzOffsetFactory
27 from ._factories import _TzStrFactory
28 try:
29 from .win import tzwin, tzwinlocal
30 except ImportError:
31 tzwin = tzwinlocal = None
32
33 # For warning about rounding tzinfo
34 from warnings import warn
35
36 ZERO = datetime.timedelta(0)
37 EPOCH = datetime.datetime.utcfromtimestamp(0)
38 EPOCHORDINAL = EPOCH.toordinal()
39
40
41 @six.add_metaclass(_TzSingleton)
42 class tzutc(datetime.tzinfo):
43 """
44 This is a tzinfo object that represents the UTC time zone.
45
46 **Examples:**
47
48 .. doctest::
49
50 >>> from datetime import *
51 >>> from dateutil.tz import *
52
53 >>> datetime.now()
54 datetime.datetime(2003, 9, 27, 9, 40, 1, 521290)
55
56 >>> datetime.now(tzutc())
57 datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc())
58
59 >>> datetime.now(tzutc()).tzname()
60 'UTC'
61
62 .. versionchanged:: 2.7.0
63 ``tzutc()`` is now a singleton, so the result of ``tzutc()`` will
64 always return the same object.
65
66 .. doctest::
67
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
76
77 def dst(self, dt):
78 return ZERO
79
80 @tzname_in_python2
81 def tzname(self, dt):
82 return "UTC"
83
84 def is_ambiguous(self, dt):
85 """
86 Whether or not the "wall time" of a given datetime is ambiguous in this
87 zone.
88
89 :param dt:
90 A :py:class:`datetime.datetime`, naive or time zone aware.
91
92
93 :return:
94 Returns ``True`` if ambiguous, ``False`` otherwise.
95
96 .. versionadded:: 2.6.0
97 """
98 return False
99
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
107
108 def __eq__(self, other):
109 if not isinstance(other, (tzutc, tzoffset)):
110 return NotImplemented
111
112 return (isinstance(other, tzutc) or
113 (isinstance(other, tzoffset) and other._offset == ZERO))
114
115 __hash__ = None
116
117 def __ne__(self, other):
118 return not (self == other)
119
120 def __repr__(self):
121 return "%s()" % self.__class__.__name__
122
123 __reduce__ = object.__reduce__
124
125
126 #: Convenience constant providing a :class:`tzutc()` instance
127 #:
128 #: .. versionadded:: 2.7.0
129 UTC = tzutc()
130
131
132 @six.add_metaclass(_TzOffsetFactory)
133 class tzoffset(datetime.tzinfo):
134 """
135 A simple class for representing a fixed offset from UTC.
136
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
145
146 try:
147 # Allow a timedelta
148 offset = offset.total_seconds()
149 except (TypeError, AttributeError):
150 pass
151
152 self._offset = datetime.timedelta(seconds=_get_supported_offset(offset))
153
154 def utcoffset(self, dt):
155 return self._offset
156
157 def dst(self, dt):
158 return ZERO
159
160 @tzname_in_python2
161 def tzname(self, dt):
162 return self._name
163
164 @_validate_fromutc_inputs
165 def fromutc(self, dt):
166 return dt + self._offset
167
168 def is_ambiguous(self, dt):
169 """
170 Whether or not the "wall time" of a given datetime is ambiguous in this
171 zone.
172
173 :param dt:
174 A :py:class:`datetime.datetime`, naive or time zone aware.
175 :return:
176 Returns ``True`` if ambiguous, ``False`` otherwise.
177
178 .. versionadded:: 2.6.0
179 """
180 return False
181
182 def __eq__(self, other):
183 if not isinstance(other, tzoffset):
184 return NotImplemented
185
186 return self._offset == other._offset
187
188 __hash__ = None
189
190 def __ne__(self, other):
191 return not (self == other)
192
193 def __repr__(self):
194 return "%s(%s, %s)" % (self.__class__.__name__,
195 repr(self._name),
196 int(self._offset.total_seconds()))
197
198 __reduce__ = object.__reduce__
199
200
201 class tzlocal(_tzinfo):
202 """
203 A :class:`tzinfo` subclass built around the ``time`` timezone functions.
204 """
205 def __init__(self):
206 super(tzlocal, self).__init__()
207
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
213
214 self._dst_saved = self._dst_offset - self._std_offset
215 self._hasdst = bool(self._dst_saved)
216 self._tznames = tuple(time.tzname)
217
218 def utcoffset(self, dt):
219 if dt is None and self._hasdst:
220 return None
221
222 if self._isdst(dt):
223 return self._dst_offset
224 else:
225 return self._std_offset
226
227 def dst(self, dt):
228 if dt is None and self._hasdst:
229 return None
230
231 if self._isdst(dt):
232 return self._dst_offset - self._std_offset
233 else:
234 return ZERO
235
236 @tzname_in_python2
237 def tzname(self, dt):
238 return self._tznames[self._isdst(dt)]
239
240 def is_ambiguous(self, dt):
241 """
242 Whether or not the "wall time" of a given datetime is ambiguous in this
243 zone.
244
245 :param dt:
246 A :py:class:`datetime.datetime`, naive or time zone aware.
247
248
249 :return:
250 Returns ``True`` if ambiguous, ``False`` otherwise.
251
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)))
257
258 def _naive_is_dst(self, dt):
259 timestamp = _datetime_to_timestamp(dt)
260 return time.localtime(timestamp + time.timezone).tm_isdst
261
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
289
290 # Check for ambiguous times:
291 dstval = self._naive_is_dst(dt)
292 fold = getattr(dt, 'fold', None)
293
294 if self.is_ambiguous(dt):
295 if fold is not None:
296 return not self._fold(dt)
297 else:
298 return True
299
300 return dstval
301
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
316
317 __hash__ = None
318
319 def __ne__(self, other):
320 return not (self == other)
321
322 def __repr__(self):
323 return "%s()" % self.__class__.__name__
324
325 __reduce__ = object.__reduce__
326
327
328 class _ttinfo(object):
329 __slots__ = ["offset", "delta", "isdst", "abbr",
330 "isstd", "isgmt", "dstoffset"]
331
332 def __init__(self):
333 for attr in self.__slots__:
334 setattr(self, attr, None)
335
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))
343
344 def __eq__(self, other):
345 if not isinstance(other, _ttinfo):
346 return NotImplemented
347
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)
355
356 __hash__ = None
357
358 def __ne__(self, other):
359 return not (self == other)
360
361 def __getstate__(self):
362 state = {}
363 for name in self.__slots__:
364 state[name] = getattr(self, name, None)
365 return state
366
367 def __setstate__(self, state):
368 for name in self.__slots__:
369 if name in state:
370 setattr(self, name, state[name])
371
372
373 class _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']
380
381 def __init__(self, **kwargs):
382 for attr in self.attrs:
383 setattr(self, attr, kwargs.get(attr, None))
384
385
386 class 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.
390
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.
394
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)``.
400
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>`_
406
407 .. note::
408
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.
414
415
416 **Examples:**
417
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:
420
421 .. testsetup:: tzfile
422
423 from dateutil.tz import gettz
424 from datetime import datetime
425
426 .. doctest:: tzfile
427
428 >>> NYC = gettz('America/New_York')
429 >>> NYC
430 tzfile('/usr/share/zoneinfo/America/New_York')
431
432 >>> print(datetime(2016, 1, 3, tzinfo=NYC)) # EST
433 2016-01-03 00:00:00-05:00
434
435 >>> print(datetime(2016, 7, 7, tzinfo=NYC)) # EDT
436 2016-07-07 00:00:00-04:00
437
438
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:
442
443 .. doctest:: tzfile
444
445 >>> print(datetime(1901, 4, 12, tzinfo=NYC)) # LMT
446 1901-04-12 00:00:00-04:56
447
448 And during World War II, New York was on "Eastern War Time", which was a
449 state of permanent daylight saving time:
450
451 .. doctest:: tzfile
452
453 >>> print(datetime(1944, 2, 7, tzinfo=NYC)) # EWT
454 1944-02-07 00:00:00-04:00
455
456 """
457
458 def __init__(self, fileobj, filename=None):
459 super(tzfile, self).__init__()
460
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)
472
473 if fileobj is not None:
474 if not file_opened_here:
475 fileobj = _nullcontext(fileobj)
476
477 with fileobj as file_stream:
478 tzobj = self._read_tzfile(file_stream)
479
480 self._set_tzdata(tzobj)
481
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))
487
488 def _read_tzfile(self, fileobj):
489 out = _tzfile()
490
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")
502
503 fileobj.read(16)
504
505 (
506 # The number of UTC/local indicators stored in the file.
507 ttisgmtcnt,
508
509 # The number of standard/wall indicators stored in the file.
510 ttisstdcnt,
511
512 # The number of leap seconds for which data is
513 # stored in the file.
514 leapcnt,
515
516 # The number of "transition times" for which data
517 # is stored in the file.
518 timecnt,
519
520 # The number of "local time types" for which data
521 # is stored in the file (must not be zero).
522 typecnt,
523
524 # The number of characters of "time zone
525 # abbreviation strings" stored in the file.
526 charcnt,
527
528 ) = struct.unpack(">6l", fileobj.read(24))
529
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.
536
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 = []
542
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.
549
550 if timecnt:
551 out.trans_idx = struct.unpack(">%dB" % timecnt,
552 fileobj.read(timecnt))
553 else:
554 out.trans_idx = []
555
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.
566
567 ttinfo = []
568
569 for i in range(typecnt):
570 ttinfo.append(struct.unpack(">lbb", fileobj.read(6)))
571
572 abbr = fileobj.read(charcnt).decode()
573
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.
582
583 # Not used, for now (but seek for correct file position)
584 if leapcnt:
585 fileobj.seek(leapcnt * 8, os.SEEK_CUR)
586
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.
594
595 if ttisstdcnt:
596 isstd = struct.unpack(">%db" % ttisstdcnt,
597 fileobj.read(ttisstdcnt))
598
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.
606
607 if ttisgmtcnt:
608 isgmt = struct.unpack(">%db" % ttisgmtcnt,
609 fileobj.read(ttisgmtcnt))
610
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)
625
626 # Replace ttinfo indexes for ttinfo objects.
627 out.trans_idx = [out.ttinfo_list[idx] for idx in out.trans_idx]
628
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
646
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
652
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]
659
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 = []
672
673 for i, tti in enumerate(out.trans_idx):
674 offset = tti.offset
675 dstoffset = 0
676
677 if lastdst is not None:
678 if tti.isdst:
679 if not lastdst:
680 dstoffset = offset - lastoffset
681
682 if not dstoffset and lastdstoffset:
683 dstoffset = lastdstoffset
684
685 tti.dstoffset = datetime.timedelta(seconds=dstoffset)
686 lastdstoffset = dstoffset
687
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
699
700 lastdst = tti.isdst
701 lastoffset = offset
702 lastbaseoffset = baseoffset
703
704 out.trans_list.append(out.trans_list_utc[i] + adjustment)
705
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)
709
710 return out
711
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
716
717 timestamp = _datetime_to_timestamp(dt)
718
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)
723
724 # We want to know when the previous transition was, so subtract off 1
725 return idx - 1
726
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
731
732 # If there is a list and the time is before it, return _ttinfo_before
733 if idx < 0:
734 return self._ttinfo_before
735
736 return self._trans_idx[idx]
737
738 def _find_ttinfo(self, dt):
739 idx = self._resolve_ambiguous_time(dt)
740
741 return self._get_ttinfo(idx)
742
743 def fromutc(self, dt):
744 """
745 The ``tzfile`` implementation of :py:func:`datetime.tzinfo.fromutc`.
746
747 :param dt:
748 A :py:class:`datetime.datetime` object.
749
750 :raises TypeError:
751 Raised if ``dt`` is not a :py:class:`datetime.datetime` object.
752
753 :raises ValueError:
754 Raised if this is called with a ``dt`` which does not have this
755 ``tzinfo`` attached.
756
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")
765
766 if dt.tzinfo is not self:
767 raise ValueError("dt.tzinfo is not self")
768
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)
772
773 dt_out = dt + datetime.timedelta(seconds=tti.offset)
774
775 fold = self.is_ambiguous(dt_out, idx=idx)
776
777 return enfold(dt_out, fold=int(fold))
778
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.
783
784 :param dt:
785 A :py:class:`datetime.datetime`, naive or time zone aware.
786
787
788 :return:
789 Returns ``True`` if ambiguous, ``False`` otherwise.
790
791 .. versionadded:: 2.6.0
792 """
793 if idx is None:
794 idx = self._find_last_transition(dt)
795
796 # Calculate the difference in offsets from current to previous
797 timestamp = _datetime_to_timestamp(dt)
798 tti = self._get_ttinfo(idx)
799
800 if idx is None or idx <= 0:
801 return False
802
803 od = self._get_ttinfo(idx - 1).offset - tti.offset
804 tt = self._trans_list[idx] # Transition time
805
806 return timestamp < tt + od
807
808 def _resolve_ambiguous_time(self, dt):
809 idx = self._find_last_transition(dt)
810
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
815
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))
818
819 return idx - idx_offset
820
821 def utcoffset(self, dt):
822 if dt is None:
823 return None
824
825 if not self._ttinfo_std:
826 return ZERO
827
828 return self._find_ttinfo(dt).delta
829
830 def dst(self, dt):
831 if dt is None:
832 return None
833
834 if not self._ttinfo_dst:
835 return ZERO
836
837 tti = self._find_ttinfo(dt)
838
839 if not tti.isdst:
840 return ZERO
841
842 # The documentation says that utcoffset()-dst() must
843 # be constant for every dt.
844 return tti.dstoffset
845
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
851
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)
858
859 __hash__ = None
860
861 def __ne__(self, other):
862 return not (self == other)
863
864 def __repr__(self):
865 return "%s(%s)" % (self.__class__.__name__, repr(self._filename))
866
867 def __reduce__(self):
868 return self.__reduce_ex__(None)
869
870 def __reduce_ex__(self, protocol):
871 return (self.__class__, (None, self._filename), self.__dict__)
872
873
874 class 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.
880
881 :param stdabbr:
882 The abbreviation for standard time (e.g. ``'EST'``).
883
884 :param stdoffset:
885 An integer or :class:`datetime.timedelta` object or equivalent
886 specifying the base offset from UTC.
887
888 If unspecified, +00:00 is used.
889
890 :param dstabbr:
891 The abbreviation for DST / "Summer" time (e.g. ``'EDT'``).
892
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.
897
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.
901
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.
906
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:
912
913 ``relativedelta(hours=2, month=3, day=1, weekday=SU(+2))``
914
915 If unspecified and any other DST information is specified, the default
916 value is 2 AM on the first Sunday in April.
917
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.
925
926
927 **Examples:**
928
929 .. testsetup:: tzrange
930
931 from dateutil.tz import tzrange, tzstr
932
933 .. doctest:: tzrange
934
935 >>> tzstr('EST5EDT') == tzrange("EST", -18000, "EDT")
936 True
937
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
947
948 """
949 def __init__(self, stdabbr, stdoffset=None,
950 dstabbr=None, dstoffset=None,
951 start=None, end=None):
952
953 global relativedelta
954 from dateutil import relativedelta
955
956 self._std_abbr = stdabbr
957 self._dst_abbr = dstabbr
958
959 try:
960 stdoffset = stdoffset.total_seconds()
961 except (TypeError, AttributeError):
962 pass
963
964 try:
965 dstoffset = dstoffset.total_seconds()
966 except (TypeError, AttributeError):
967 pass
968
969 if stdoffset is not None:
970 self._std_offset = datetime.timedelta(seconds=stdoffset)
971 else:
972 self._std_offset = ZERO
973
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
980
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
986
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
992
993 self._dst_base_offset_ = self._dst_offset - self._std_offset
994 self.hasdst = bool(self._start_delta)
995
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``.
1001
1002 :param year:
1003 The year whose transitions you would like to query.
1004
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
1012
1013 base_year = datetime.datetime(year, 1, 1)
1014
1015 start = base_year + self._start_delta
1016 end = base_year + self._end_delta
1017
1018 return (start, end)
1019
1020 def __eq__(self, other):
1021 if not isinstance(other, tzrange):
1022 return NotImplemented
1023
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)
1030
1031 @property
1032 def _dst_base_offset(self):
1033 return self._dst_base_offset_
1034
1035
1036 @six.add_metaclass(_TzStrFactory)
1037 class 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).
1042
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``.
1048
1049 The :class:`tzrange` object provides the same functionality, but is
1050 specified using :class:`relativedelta.relativedelta` objects. rather than
1051 strings.
1052
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`).
1058
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.
1063
1064 .. caution::
1065
1066 Prior to version 2.7.0, this function also supported time zones
1067 in the format:
1068
1069 * ``EST5EDT,4,0,6,7200,10,0,26,7200,3600``
1070 * ``EST5EDT,4,1,0,7200,10,-1,0,7200,3600``
1071
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.
1075
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
1082
1083 self._s = s
1084
1085 res = parser._parsetz(s)
1086 if res is None or res.any_unused_tokens:
1087 raise ValueError("unknown string format")
1088
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
1093
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)
1100
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)
1108
1109 self.hasdst = bool(self._start_delta)
1110
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)
1151
1152 def __repr__(self):
1153 return "%s(%s)" % (self.__class__.__name__, repr(self._s))
1154
1155
1156 class _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
1165
1166
1167 class _tzicalvtz(_tzinfo):
1168 def __init__(self, tzid, comps=[]):
1169 super(_tzicalvtz, self).__init__()
1170
1171 self._tzid = tzid
1172 self._comps = comps
1173 self._cachedate = []
1174 self._cachecomp = []
1175 self._cache_lock = _thread.allocate_lock()
1176
1177 def _find_comp(self, dt):
1178 if len(self._comps) == 1:
1179 return self._comps[0]
1180
1181 dt = dt.replace(tzinfo=None)
1182
1183 try:
1184 with self._cache_lock:
1185 return self._cachecomp[self._cachedate.index(
1186 (dt, self._fold(dt)))]
1187 except ValueError:
1188 pass
1189
1190 lastcompdt = None
1191 lastcomp = None
1192
1193 for comp in self._comps:
1194 compdt = self._find_compdt(comp, dt)
1195
1196 if compdt and (not lastcompdt or lastcompdt < compdt):
1197 lastcompdt = compdt
1198 lastcomp = comp
1199
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]
1211
1212 with self._cache_lock:
1213 self._cachedate.insert(0, (dt, self._fold(dt)))
1214 self._cachecomp.insert(0, lastcomp)
1215
1216 if len(self._cachedate) > 10:
1217 self._cachedate.pop()
1218 self._cachecomp.pop()
1219
1220 return lastcomp
1221
1222 def _find_compdt(self, comp, dt):
1223 if comp.tzoffsetdiff < ZERO and self._fold(dt):
1224 dt -= comp.tzoffsetdiff
1225
1226 compdt = comp.rrule.before(dt, inc=True)
1227
1228 return compdt
1229
1230 def utcoffset(self, dt):
1231 if dt is None:
1232 return None
1233
1234 return self._find_comp(dt).tzoffsetto
1235
1236 def dst(self, dt):
1237 comp = self._find_comp(dt)
1238 if comp.isdst:
1239 return comp.tzoffsetdiff
1240 else:
1241 return ZERO
1242
1243 @tzname_in_python2
1244 def tzname(self, dt):
1245 return self._find_comp(dt).tzname
1246
1247 def __repr__(self):
1248 return "<tzicalvtz %s>" % repr(self._tzid)
1249
1250 __reduce__ = object.__reduce__
1251
1252
1253 class 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.
1257
1258 :param `fileobj`:
1259 A file or stream in iCalendar format, which should be UTF-8 encoded
1260 with CRLF endings.
1261
1262 .. _`RFC 5545`: https://tools.ietf.org/html/rfc5545
1263 """
1264 def __init__(self, fileobj):
1265 global rrule
1266 from dateutil import rrule
1267
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)
1275
1276 self._vtz = {}
1277
1278 with fileobj as fobj:
1279 self._parse_rfc(fobj.read())
1280
1281 def keys(self):
1282 """
1283 Retrieves the available time zones as a list.
1284 """
1285 return list(self._vtz.keys())
1286
1287 def get(self, tzid=None):
1288 """
1289 Retrieve a :py:class:`datetime.tzinfo` object by its ``tzid``.
1290
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.
1295
1296 :raises ValueError:
1297 Raised if ``tzid`` is not specified but there are either more
1298 or fewer than 1 zone defined.
1299
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))
1311
1312 return self._vtz.get(tzid)
1313
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)
1329
1330 def _parse_rfc(self, s):
1331 lines = s.splitlines()
1332 if not lines:
1333 raise ValueError("empty string")
1334
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
1346
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
1454
1455 def __repr__(self):
1456 return "%s(%s)" % (self.__class__.__name__, repr(self._s))
1457
1458
1459 if 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"]
1465 else:
1466 TZFILES = []
1467 TZPATHS = []
1468
1469
1470 def __get_gettz():
1471 tzlocal_classes = (tzlocal,)
1472 if tzwinlocal is not None:
1473 tzlocal_classes += (tzwinlocal,)
1474
1475 class GettzFunc(object):
1476 """
1477 Retrieve a time zone object from a string representation
1478
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.
1482
1483 If no argument or an empty string is passed to ``gettz``, local time
1484 is returned:
1485
1486 .. code-block:: python3
1487
1488 >>> gettz()
1489 tzfile('/etc/localtime')
1490
1491 This function is also the preferred way to map IANA tz database keys
1492 to :class:`tzfile` objects:
1493
1494 .. code-block:: python3
1495
1496 >>> gettz('Pacific/Kiritimati')
1497 tzfile('/usr/share/zoneinfo/Pacific/Kiritimati')
1498
1499 On Windows, the standard is extended to include the Windows-specific
1500 zone names provided by the operating system:
1501
1502 .. code-block:: python3
1503
1504 >>> gettz('Egypt Standard Time')
1505 tzwin('Egypt Standard Time')
1506
1507 Passing a GNU ``TZ`` style string time zone specification returns a
1508 :class:`tzstr` object:
1509
1510 .. code-block:: python3
1511
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')
1514
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.
1520
1521 :return:
1522 Returns an instance of one of ``dateutil``'s :py:class:`tzinfo`
1523 subclasses.
1524
1525 .. versionchanged:: 2.7.0
1526
1527 After version 2.7.0, any two calls to ``gettz`` using the same
1528 input strings will return the same object:
1529
1530 .. code-block:: python3
1531
1532 >>> tz.gettz('America/Chicago') is tz.gettz('America/Chicago')
1533 True
1534
1535 In addition to improving performance, this ensures that
1536 `"same zone" semantics`_ are used for datetimes in the same zone.
1537
1538
1539 .. _`TZ variable`:
1540 https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
1541
1542 .. _`"same zone" semantics`:
1543 https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html
1544 """
1545 def __init__(self):
1546
1547 self.__instances = weakref.WeakValueDictionary()
1548 self.__strong_cache_size = 8
1549 self.__strong_cache = OrderedDict()
1550 self._cache_lock = _thread.allocate_lock()
1551
1552 def __call__(self, name=None):
1553 with self._cache_lock:
1554 rv = self.__instances.get(name, None)
1555
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
1571
1572 self.__strong_cache[name] = self.__strong_cache.pop(name, rv)
1573
1574 if len(self.__strong_cache) > self.__strong_cache_size:
1575 self.__strong_cache.popitem(last=False)
1576
1577 return rv
1578
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)
1584
1585 def cache_clear(self):
1586 with self._cache_lock:
1587 self.__instances = weakref.WeakValueDictionary()
1588 self.__strong_cache.clear()
1589
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 == ":":
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
1652
1653 if not tz:
1654 from dateutil.zoneinfo import get_zonefile_instance
1655 tz = get_zonefile_instance().get(name)
1656
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
1675
1676 return GettzFunc()
1677
1678
1679 gettz = __get_gettz()
1680 del __get_gettz
1681
1682
1683 def 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.
1687
1688 :param dt:
1689 A :class:`datetime.datetime` (whose time zone will be ignored if ``tz``
1690 is provided.)
1691
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.
1695
1696 :return:
1697 Returns a boolean value whether or not the "wall time" exists in
1698 ``tz``.
1699
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
1706
1707 dt = dt.replace(tzinfo=None)
1708
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)
1713
1714 return dt == dt_rt
1715
1716
1717 def 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).
1722
1723 :param dt:
1724 A :class:`datetime.datetime` (whose time zone will be ignored if ``tz``
1725 is provided.)
1726
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.
1730
1731 :return:
1732 Returns a boolean value whether or not the "wall time" is ambiguous in
1733 ``tz``.
1734
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.')
1740
1741 tz = dt.tzinfo
1742
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
1750
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)
1756
1757 same_offset = wall_0.utcoffset() == wall_1.utcoffset()
1758 same_dst = wall_0.dst() == wall_1.dst()
1759
1760 return not (same_offset and same_dst)
1761
1762
1763 def resolve_imaginary(dt):
1764 """
1765 Given a datetime that may be imaginary, return an existing datetime.
1766
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.
1770
1771 .. doctest::
1772
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
1778
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
1782
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).
1788
1789 :param dt:
1790 A :class:`datetime.datetime` which may or may not exist.
1791
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.
1796
1797 .. versionadded:: 2.7.0
1798 """
1799 if dt.tzinfo is not None and not datetime_exists(dt):
1800
1801 curr_offset = (dt + datetime.timedelta(hours=24)).utcoffset()
1802 old_offset = (dt - datetime.timedelta(hours=24)).utcoffset()
1803
1804 dt += curr_offset - old_offset
1805
1806 return dt
1807
1808
1809 def _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()
1815
1816
1817 if sys.version_info >= (3, 6):
1818 def _get_supported_offset(second_offset):
1819 return second_offset
1820 else:
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
1829
1830
1831 try:
1832 # Python 3.7 feature
1833 from contextlib import nullcontext as _nullcontext
1834 except 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
1842
1843 def __enter__(self):
1844 return self.context
1845
1846 def __exit__(*args, **kwargs):
1847 pass
1848
1849 # vim:ts=4:sw=4:et