python-dateutil 2.9.0__py2.py3-none-any.whl → 3.0.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dateutil/__init__.py +7 -5
- dateutil/_tzdata_impl.py +147 -0
- dateutil/_version.py +2 -14
- dateutil/relativedelta.py +0 -3
- dateutil/rrule.py +28 -1
- dateutil/tz/__init__.py +52 -4
- dateutil/tz/_tzfile.py +1428 -0
- dateutil/tz/_tzpath.py +104 -0
- dateutil/tz/tz.py +201 -578
- dateutil/zoneinfo/__init__.py +107 -41
- dateutil/zoneinfo/rebuild.py +31 -64
- {python_dateutil-2.9.0.dist-info → python_dateutil-3.0.0.dist-info}/METADATA +16 -16
- python_dateutil-3.0.0.dist-info/RECORD +27 -0
- {python_dateutil-2.9.0.dist-info → python_dateutil-3.0.0.dist-info}/WHEEL +1 -1
- dateutil/zoneinfo/dateutil-zoneinfo.tar.gz +0 -0
- python_dateutil-2.9.0.dist-info/RECORD +0 -25
- {python_dateutil-2.9.0.dist-info → python_dateutil-3.0.0.dist-info/licenses}/LICENSE +0 -0
- {python_dateutil-2.9.0.dist-info → python_dateutil-3.0.0.dist-info}/top_level.txt +0 -0
- {python_dateutil-2.9.0.dist-info → python_dateutil-3.0.0.dist-info}/zip-safe +0 -0
dateutil/tz/tz.py
CHANGED
|
@@ -7,24 +7,33 @@ etc), TZ environment string (in all known formats), given ranges (with help
|
|
|
7
7
|
from relative deltas), local machine timezone, fixed offset timezone, and UTC
|
|
8
8
|
timezone.
|
|
9
9
|
"""
|
|
10
|
+
|
|
11
|
+
import bisect
|
|
10
12
|
import datetime
|
|
13
|
+
import os
|
|
11
14
|
import struct
|
|
12
|
-
import time
|
|
13
15
|
import sys
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
+
import time
|
|
17
|
+
import warnings
|
|
16
18
|
import weakref
|
|
17
19
|
from collections import OrderedDict
|
|
18
20
|
|
|
19
21
|
import six
|
|
20
22
|
from six import string_types
|
|
21
23
|
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
24
|
|
|
26
|
-
from
|
|
27
|
-
from .
|
|
25
|
+
from .. import _tzdata_impl
|
|
26
|
+
from . import _tzpath
|
|
27
|
+
from ._common import (
|
|
28
|
+
_tzinfo,
|
|
29
|
+
_validate_fromutc_inputs,
|
|
30
|
+
enfold,
|
|
31
|
+
tzname_in_python2,
|
|
32
|
+
tzrangebase,
|
|
33
|
+
)
|
|
34
|
+
from ._factories import _TzOffsetFactory, _TzSingleton, _TzStrFactory
|
|
35
|
+
from ._tzfile import tzfile
|
|
36
|
+
|
|
28
37
|
try:
|
|
29
38
|
from .win import tzwin, tzwinlocal
|
|
30
39
|
except ImportError:
|
|
@@ -35,7 +44,6 @@ from warnings import warn
|
|
|
35
44
|
|
|
36
45
|
ZERO = datetime.timedelta(0)
|
|
37
46
|
EPOCH = datetime.datetime(1970, 1, 1, 0, 0)
|
|
38
|
-
EPOCHORDINAL = EPOCH.toordinal()
|
|
39
47
|
|
|
40
48
|
|
|
41
49
|
@six.add_metaclass(_TzSingleton)
|
|
@@ -325,552 +333,6 @@ class tzlocal(_tzinfo):
|
|
|
325
333
|
__reduce__ = object.__reduce__
|
|
326
334
|
|
|
327
335
|
|
|
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
336
|
class tzrange(tzrangebase):
|
|
875
337
|
"""
|
|
876
338
|
The ``tzrange`` object is a time zone specified by a set of offsets and
|
|
@@ -1456,16 +918,7 @@ class tzical(object):
|
|
|
1456
918
|
return "%s(%s)" % (self.__class__.__name__, repr(self._s))
|
|
1457
919
|
|
|
1458
920
|
|
|
1459
|
-
|
|
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
|
-
|
|
921
|
+
TZFILES = ["/etc/localtime", "localtime"]
|
|
1469
922
|
|
|
1470
923
|
def __get_gettz():
|
|
1471
924
|
tzlocal_classes = (tzlocal,)
|
|
@@ -1489,7 +942,12 @@ def __get_gettz():
|
|
|
1489
942
|
tzfile('/etc/localtime')
|
|
1490
943
|
|
|
1491
944
|
This function is also the preferred way to map IANA tz database keys
|
|
1492
|
-
to :class:`tzfile` objects
|
|
945
|
+
to :class:`tzfile` objects. It searches for these keys in the
|
|
946
|
+
following order:
|
|
947
|
+
|
|
948
|
+
1. On the search path defined in :data:`TZPATH`.
|
|
949
|
+
2. In the `tzdata <https://pypi.org/project/tzdata/>`_ Python package.
|
|
950
|
+
3. Using the system-specific mechanisms (e.g. the Windows registry).
|
|
1493
951
|
|
|
1494
952
|
.. code-block:: python3
|
|
1495
953
|
|
|
@@ -1554,6 +1012,10 @@ def __get_gettz():
|
|
|
1554
1012
|
rv = self.__instances.get(name, None)
|
|
1555
1013
|
|
|
1556
1014
|
if rv is None:
|
|
1015
|
+
# TODO: This holds the cache lock while nocache() reads
|
|
1016
|
+
# the file, so a slow lookup (e.g. walking TZPATH on a
|
|
1017
|
+
# network file system) blocks every other gettz call,
|
|
1018
|
+
# including cache hits for unrelated keys.
|
|
1557
1019
|
rv = self.nocache(name=name)
|
|
1558
1020
|
if not (name is None
|
|
1559
1021
|
or isinstance(rv, tzlocal_classes)
|
|
@@ -1600,13 +1062,13 @@ def __get_gettz():
|
|
|
1600
1062
|
for filepath in TZFILES:
|
|
1601
1063
|
if not os.path.isabs(filepath):
|
|
1602
1064
|
filename = filepath
|
|
1603
|
-
for path in
|
|
1065
|
+
for path in _tzpath.TZPATH:
|
|
1604
1066
|
filepath = os.path.join(path, filename)
|
|
1605
|
-
if
|
|
1067
|
+
if _isfile(filepath):
|
|
1606
1068
|
break
|
|
1607
1069
|
else:
|
|
1608
1070
|
continue
|
|
1609
|
-
if
|
|
1071
|
+
if _isfile(filepath):
|
|
1610
1072
|
try:
|
|
1611
1073
|
tz = tzfile(filepath)
|
|
1612
1074
|
break
|
|
@@ -1624,25 +1086,59 @@ def __get_gettz():
|
|
|
1624
1086
|
six.raise_from(TypeError(new_msg), e)
|
|
1625
1087
|
else:
|
|
1626
1088
|
raise
|
|
1089
|
+
|
|
1090
|
+
# A NUL byte can never be part of a key or a path, and
|
|
1091
|
+
# depending on the platform and Python version it raises
|
|
1092
|
+
# ValueError from os.path, winreg or open(), so reject it
|
|
1093
|
+
# up front rather than catching it in every branch.
|
|
1094
|
+
if "\x00" in name:
|
|
1095
|
+
return None
|
|
1096
|
+
|
|
1627
1097
|
if os.path.isabs(name):
|
|
1628
|
-
if
|
|
1098
|
+
if _isfile(name):
|
|
1629
1099
|
tz = tzfile(name)
|
|
1630
1100
|
else:
|
|
1631
1101
|
tz = None
|
|
1632
1102
|
else:
|
|
1633
|
-
|
|
1103
|
+
if _escapes_tzpath(name):
|
|
1104
|
+
_warn_tzpath_escape(name)
|
|
1105
|
+
|
|
1106
|
+
for path in _tzpath.TZPATH:
|
|
1634
1107
|
filepath = os.path.join(path, name)
|
|
1635
|
-
if not
|
|
1108
|
+
if not _isfile(filepath):
|
|
1636
1109
|
filepath = filepath.replace(' ', '_')
|
|
1637
|
-
if not
|
|
1110
|
+
if not _isfile(filepath):
|
|
1638
1111
|
continue
|
|
1639
1112
|
try:
|
|
1640
|
-
tz = tzfile(filepath)
|
|
1113
|
+
tz = tzfile(filepath, key=name)
|
|
1641
1114
|
break
|
|
1642
1115
|
except (IOError, OSError, ValueError):
|
|
1116
|
+
# TODO: A file that exists but can't be read or
|
|
1117
|
+
# isn't a valid TZif file is silently skipped,
|
|
1118
|
+
# and the key is looked up in the next TZPATH
|
|
1119
|
+
# entry and then in tzdata. Consider warning
|
|
1120
|
+
# here, since this usually means the system time
|
|
1121
|
+
# zone data is broken.
|
|
1643
1122
|
pass
|
|
1644
1123
|
else:
|
|
1645
1124
|
tz = None
|
|
1125
|
+
# As with the search path lookup above, also try
|
|
1126
|
+
# the name with spaces replaced by underscores.
|
|
1127
|
+
candidates = [name]
|
|
1128
|
+
if " " in name:
|
|
1129
|
+
candidates.append(name.replace(" ", "_"))
|
|
1130
|
+
|
|
1131
|
+
for candidate in candidates:
|
|
1132
|
+
try:
|
|
1133
|
+
with _tzdata_impl._load_tzdata(candidate) as f:
|
|
1134
|
+
return tzfile(f, key=name)
|
|
1135
|
+
except ValueError:
|
|
1136
|
+
# TZFileNotFound is a ValueError, and so is
|
|
1137
|
+
# the error raised for files in tzdata that
|
|
1138
|
+
# are not TZif files (e.g. "zone.tab"),
|
|
1139
|
+
# which are skipped just as on TZPATH.
|
|
1140
|
+
pass
|
|
1141
|
+
|
|
1646
1142
|
if tzwin is not None:
|
|
1647
1143
|
try:
|
|
1648
1144
|
tz = tzwin(name)
|
|
@@ -1650,10 +1146,6 @@ def __get_gettz():
|
|
|
1650
1146
|
# UnicodeEncodeError is for Python 2.7 compat
|
|
1651
1147
|
tz = None
|
|
1652
1148
|
|
|
1653
|
-
if not tz:
|
|
1654
|
-
from dateutil.zoneinfo import get_zonefile_instance
|
|
1655
|
-
tz = get_zonefile_instance().get(name)
|
|
1656
|
-
|
|
1657
1149
|
if not tz:
|
|
1658
1150
|
for c in name:
|
|
1659
1151
|
# name is not a tzstr unless it has at least
|
|
@@ -1680,6 +1172,123 @@ gettz = __get_gettz()
|
|
|
1680
1172
|
del __get_gettz
|
|
1681
1173
|
|
|
1682
1174
|
|
|
1175
|
+
class DeprecatedTzKeyWarning(FutureWarning):
|
|
1176
|
+
"""Warning raised when :func:`gettz` is given a key outside of TZPATH.
|
|
1177
|
+
|
|
1178
|
+
Relative keys like ``"../../etc/localtime"`` resolve to files outside of
|
|
1179
|
+
every directory on :data:`TZPATH`. Support for these is deprecated, and a
|
|
1180
|
+
warnings filter turns this warning into an error by default. To keep the
|
|
1181
|
+
old behavior for now, filter it out, e.g.::
|
|
1182
|
+
|
|
1183
|
+
warnings.filterwarnings(
|
|
1184
|
+
"ignore", category=dateutil.tz.DeprecatedTzKeyWarning
|
|
1185
|
+
)
|
|
1186
|
+
|
|
1187
|
+
Absolute paths to TZif files are not affected.
|
|
1188
|
+
"""
|
|
1189
|
+
|
|
1190
|
+
|
|
1191
|
+
def _escapes_tzpath(name):
|
|
1192
|
+
# A relative key stays inside every TZPATH entry unless, once normalized
|
|
1193
|
+
# (which also turns os.altsep into os.sep), it starts by going up a
|
|
1194
|
+
# directory.
|
|
1195
|
+
return os.path.normpath(name).split(os.sep, 1)[0] == os.pardir
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
def _warn_tzpath_escape(name):
|
|
1199
|
+
# As in dateutil.zoneinfo.rebuild, the filter is appended at call time,
|
|
1200
|
+
# so any filter the user has installed for this category wins.
|
|
1201
|
+
warnings.filterwarnings(
|
|
1202
|
+
"error", category=DeprecatedTzKeyWarning, append=True
|
|
1203
|
+
)
|
|
1204
|
+
warnings.warn(
|
|
1205
|
+
"gettz(%r) refers to a file outside of TZPATH; support for keys "
|
|
1206
|
+
"like this is deprecated and will be removed in a future version. "
|
|
1207
|
+
"Use an absolute path instead." % (name,),
|
|
1208
|
+
DeprecatedTzKeyWarning,
|
|
1209
|
+
stacklevel=4,
|
|
1210
|
+
)
|
|
1211
|
+
|
|
1212
|
+
|
|
1213
|
+
def available_iana_timezones():
|
|
1214
|
+
"""Returns a set containing all available IANA time zones keys.
|
|
1215
|
+
|
|
1216
|
+
These are suitable for use with :func:`gettz`. This is essentially a direct
|
|
1217
|
+
port of :py:func:`zoneinfo.available_timezones`.
|
|
1218
|
+
|
|
1219
|
+
The search includes time zones found on :data:`TZPATH` as well as any
|
|
1220
|
+
time zones provided by the `tzdata <https://pypi.org/project/tzdata/>`_
|
|
1221
|
+
package if it is installed.
|
|
1222
|
+
|
|
1223
|
+
:return:
|
|
1224
|
+
Returns a mutable set containing strings of time zones. Each call will
|
|
1225
|
+
return a fresh set.
|
|
1226
|
+
|
|
1227
|
+
.. caution::
|
|
1228
|
+
|
|
1229
|
+
This may attempt to open a large number of files, since the best way to
|
|
1230
|
+
determine if a given file on the time zone search path is to open it
|
|
1231
|
+
and check for the "magic string" at the beginning.
|
|
1232
|
+
|
|
1233
|
+
.. note::
|
|
1234
|
+
|
|
1235
|
+
These values are not designed to be exposed to end-users; for
|
|
1236
|
+
user-facing elements, applications should use something like CLDR (the
|
|
1237
|
+
Unicode Common Locale Data Repository) to get more user-friendly
|
|
1238
|
+
strings.
|
|
1239
|
+
"""
|
|
1240
|
+
# Start with loading from the tzdata package if it exists: this has a
|
|
1241
|
+
# pre-assembled list of zones that only requires opening one file.
|
|
1242
|
+
try:
|
|
1243
|
+
valid_zones = set(_tzdata_impl._load_tzdata_keys())
|
|
1244
|
+
except ImportError:
|
|
1245
|
+
valid_zones = set()
|
|
1246
|
+
|
|
1247
|
+
def valid_key(fpath):
|
|
1248
|
+
try:
|
|
1249
|
+
with open(fpath, "rb") as f:
|
|
1250
|
+
return f.read(4) == b"TZif"
|
|
1251
|
+
except Exception: # pragma: nocover
|
|
1252
|
+
return False
|
|
1253
|
+
|
|
1254
|
+
for tz_root in _tzpath.TZPATH:
|
|
1255
|
+
if not os.path.exists(tz_root):
|
|
1256
|
+
continue
|
|
1257
|
+
|
|
1258
|
+
for root, dirnames, files in os.walk(tz_root):
|
|
1259
|
+
if root == tz_root:
|
|
1260
|
+
# right/ and posix/ are special directories and shouldn't be
|
|
1261
|
+
# included in the output of available zones
|
|
1262
|
+
if "right" in dirnames:
|
|
1263
|
+
dirnames.remove("right")
|
|
1264
|
+
if "posix" in dirnames:
|
|
1265
|
+
dirnames.remove("posix")
|
|
1266
|
+
|
|
1267
|
+
for file in files:
|
|
1268
|
+
fpath = os.path.join(root, file)
|
|
1269
|
+
|
|
1270
|
+
key = os.path.relpath(fpath, start=tz_root)
|
|
1271
|
+
if os.sep != "/": # pragma: nocover
|
|
1272
|
+
key = key.replace(os.sep, "/")
|
|
1273
|
+
|
|
1274
|
+
if not key or key in valid_zones:
|
|
1275
|
+
continue
|
|
1276
|
+
|
|
1277
|
+
if valid_key(fpath):
|
|
1278
|
+
valid_zones.add(key)
|
|
1279
|
+
|
|
1280
|
+
if "posixrules" in valid_zones:
|
|
1281
|
+
# posixrules is a special symlink-only time zone where it exists, it
|
|
1282
|
+
# should not be included in the output
|
|
1283
|
+
valid_zones.remove("posixrules")
|
|
1284
|
+
if "localtime" in valid_zones:
|
|
1285
|
+
# localtime is a special symlink-only time zone where it exists, it
|
|
1286
|
+
# should not be included in the output
|
|
1287
|
+
valid_zones.remove("localtime")
|
|
1288
|
+
|
|
1289
|
+
return valid_zones
|
|
1290
|
+
|
|
1291
|
+
|
|
1683
1292
|
def datetime_exists(dt, tz=None):
|
|
1684
1293
|
"""
|
|
1685
1294
|
Given a datetime and a time zone, determine whether or not a given datetime
|
|
@@ -1828,6 +1437,20 @@ else:
|
|
|
1828
1437
|
return calculated_offset
|
|
1829
1438
|
|
|
1830
1439
|
|
|
1440
|
+
if sys.version_info < (3, 8):
|
|
1441
|
+
|
|
1442
|
+
def _isfile(path):
|
|
1443
|
+
# bpo-33721: In Python 3.8 non-UTF8 paths return False rather than
|
|
1444
|
+
# raising an error. See https://bugs.python.org/issue33721
|
|
1445
|
+
try:
|
|
1446
|
+
return os.path.isfile(path)
|
|
1447
|
+
except ValueError:
|
|
1448
|
+
return False
|
|
1449
|
+
|
|
1450
|
+
else:
|
|
1451
|
+
_isfile = os.path.isfile
|
|
1452
|
+
|
|
1453
|
+
|
|
1831
1454
|
try:
|
|
1832
1455
|
# Python 3.7 feature
|
|
1833
1456
|
from contextlib import nullcontext as _nullcontext
|