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