Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
wiseplat
GitHub Repository: wiseplat/python-code
Path: blob/master/ invest-robot-contest_TinkoffBotTwitch-main/venv/lib/python3.8/site-packages/dateutil/tz/tz.py
7763 views
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 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
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
1850
1851