Path: blob/master/ invest-robot-contest_TinkoffBotTwitch-main/venv/lib/python3.8/site-packages/dateutil/tz/tz.py
7763 views
# -*- coding: utf-8 -*-1"""2This module offers timezone implementations subclassing the abstract3:py:class:`datetime.tzinfo` type. There are classes to handle tzfile format4files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`,5etc), TZ environment string (in all known formats), given ranges (with help6from relative deltas), local machine timezone, fixed offset timezone, and UTC7timezone.8"""9import datetime10import struct11import time12import sys13import os14import bisect15import weakref16from collections import OrderedDict1718import six19from six import string_types20from six.moves import _thread21from ._common import tzname_in_python2, _tzinfo22from ._common import tzrangebase, enfold23from ._common import _validate_fromutc_inputs2425from ._factories import _TzSingleton, _TzOffsetFactory26from ._factories import _TzStrFactory27try:28from .win import tzwin, tzwinlocal29except ImportError:30tzwin = tzwinlocal = None3132# For warning about rounding tzinfo33from warnings import warn3435ZERO = datetime.timedelta(0)36EPOCH = datetime.datetime.utcfromtimestamp(0)37EPOCHORDINAL = EPOCH.toordinal()383940@six.add_metaclass(_TzSingleton)41class tzutc(datetime.tzinfo):42"""43This is a tzinfo object that represents the UTC time zone.4445**Examples:**4647.. doctest::4849>>> from datetime import *50>>> from dateutil.tz import *5152>>> datetime.now()53datetime.datetime(2003, 9, 27, 9, 40, 1, 521290)5455>>> datetime.now(tzutc())56datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc())5758>>> datetime.now(tzutc()).tzname()59'UTC'6061.. versionchanged:: 2.7.062``tzutc()`` is now a singleton, so the result of ``tzutc()`` will63always return the same object.6465.. doctest::6667>>> from dateutil.tz import tzutc, UTC68>>> tzutc() is tzutc()69True70>>> tzutc() is UTC71True72"""73def utcoffset(self, dt):74return ZERO7576def dst(self, dt):77return ZERO7879@tzname_in_python280def tzname(self, dt):81return "UTC"8283def is_ambiguous(self, dt):84"""85Whether or not the "wall time" of a given datetime is ambiguous in this86zone.8788:param dt:89A :py:class:`datetime.datetime`, naive or time zone aware.909192:return:93Returns ``True`` if ambiguous, ``False`` otherwise.9495.. versionadded:: 2.6.096"""97return False9899@_validate_fromutc_inputs100def fromutc(self, dt):101"""102Fast track version of fromutc() returns the original ``dt`` object for103any valid :py:class:`datetime.datetime` object.104"""105return dt106107def __eq__(self, other):108if not isinstance(other, (tzutc, tzoffset)):109return NotImplemented110111return (isinstance(other, tzutc) or112(isinstance(other, tzoffset) and other._offset == ZERO))113114__hash__ = None115116def __ne__(self, other):117return not (self == other)118119def __repr__(self):120return "%s()" % self.__class__.__name__121122__reduce__ = object.__reduce__123124125#: Convenience constant providing a :class:`tzutc()` instance126#:127#: .. versionadded:: 2.7.0128UTC = tzutc()129130131@six.add_metaclass(_TzOffsetFactory)132class tzoffset(datetime.tzinfo):133"""134A simple class for representing a fixed offset from UTC.135136:param name:137The timezone name, to be returned when ``tzname()`` is called.138:param offset:139The time zone offset in seconds, or (since version 2.6.0, represented140as a :py:class:`datetime.timedelta` object).141"""142def __init__(self, name, offset):143self._name = name144145try:146# Allow a timedelta147offset = offset.total_seconds()148except (TypeError, AttributeError):149pass150151self._offset = datetime.timedelta(seconds=_get_supported_offset(offset))152153def utcoffset(self, dt):154return self._offset155156def dst(self, dt):157return ZERO158159@tzname_in_python2160def tzname(self, dt):161return self._name162163@_validate_fromutc_inputs164def fromutc(self, dt):165return dt + self._offset166167def is_ambiguous(self, dt):168"""169Whether or not the "wall time" of a given datetime is ambiguous in this170zone.171172:param dt:173A :py:class:`datetime.datetime`, naive or time zone aware.174:return:175Returns ``True`` if ambiguous, ``False`` otherwise.176177.. versionadded:: 2.6.0178"""179return False180181def __eq__(self, other):182if not isinstance(other, tzoffset):183return NotImplemented184185return self._offset == other._offset186187__hash__ = None188189def __ne__(self, other):190return not (self == other)191192def __repr__(self):193return "%s(%s, %s)" % (self.__class__.__name__,194repr(self._name),195int(self._offset.total_seconds()))196197__reduce__ = object.__reduce__198199200class tzlocal(_tzinfo):201"""202A :class:`tzinfo` subclass built around the ``time`` timezone functions.203"""204def __init__(self):205super(tzlocal, self).__init__()206207self._std_offset = datetime.timedelta(seconds=-time.timezone)208if time.daylight:209self._dst_offset = datetime.timedelta(seconds=-time.altzone)210else:211self._dst_offset = self._std_offset212213self._dst_saved = self._dst_offset - self._std_offset214self._hasdst = bool(self._dst_saved)215self._tznames = tuple(time.tzname)216217def utcoffset(self, dt):218if dt is None and self._hasdst:219return None220221if self._isdst(dt):222return self._dst_offset223else:224return self._std_offset225226def dst(self, dt):227if dt is None and self._hasdst:228return None229230if self._isdst(dt):231return self._dst_offset - self._std_offset232else:233return ZERO234235@tzname_in_python2236def tzname(self, dt):237return self._tznames[self._isdst(dt)]238239def is_ambiguous(self, dt):240"""241Whether or not the "wall time" of a given datetime is ambiguous in this242zone.243244:param dt:245A :py:class:`datetime.datetime`, naive or time zone aware.246247248:return:249Returns ``True`` if ambiguous, ``False`` otherwise.250251.. versionadded:: 2.6.0252"""253naive_dst = self._naive_is_dst(dt)254return (not naive_dst and255(naive_dst != self._naive_is_dst(dt - self._dst_saved)))256257def _naive_is_dst(self, dt):258timestamp = _datetime_to_timestamp(dt)259return time.localtime(timestamp + time.timezone).tm_isdst260261def _isdst(self, dt, fold_naive=True):262# We can't use mktime here. It is unstable when deciding if263# the hour near to a change is DST or not.264#265# timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour,266# dt.minute, dt.second, dt.weekday(), 0, -1))267# return time.localtime(timestamp).tm_isdst268#269# The code above yields the following result:270#271# >>> import tz, datetime272# >>> t = tz.tzlocal()273# >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()274# 'BRDT'275# >>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()276# 'BRST'277# >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()278# 'BRST'279# >>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()280# 'BRDT'281# >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()282# 'BRDT'283#284# Here is a more stable implementation:285#286if not self._hasdst:287return False288289# Check for ambiguous times:290dstval = self._naive_is_dst(dt)291fold = getattr(dt, 'fold', None)292293if self.is_ambiguous(dt):294if fold is not None:295return not self._fold(dt)296else:297return True298299return dstval300301def __eq__(self, other):302if isinstance(other, tzlocal):303return (self._std_offset == other._std_offset and304self._dst_offset == other._dst_offset)305elif isinstance(other, tzutc):306return (not self._hasdst and307self._tznames[0] in {'UTC', 'GMT'} and308self._std_offset == ZERO)309elif isinstance(other, tzoffset):310return (not self._hasdst and311self._tznames[0] == other._name and312self._std_offset == other._offset)313else:314return NotImplemented315316__hash__ = None317318def __ne__(self, other):319return not (self == other)320321def __repr__(self):322return "%s()" % self.__class__.__name__323324__reduce__ = object.__reduce__325326327class _ttinfo(object):328__slots__ = ["offset", "delta", "isdst", "abbr",329"isstd", "isgmt", "dstoffset"]330331def __init__(self):332for attr in self.__slots__:333setattr(self, attr, None)334335def __repr__(self):336l = []337for attr in self.__slots__:338value = getattr(self, attr)339if value is not None:340l.append("%s=%s" % (attr, repr(value)))341return "%s(%s)" % (self.__class__.__name__, ", ".join(l))342343def __eq__(self, other):344if not isinstance(other, _ttinfo):345return NotImplemented346347return (self.offset == other.offset and348self.delta == other.delta and349self.isdst == other.isdst and350self.abbr == other.abbr and351self.isstd == other.isstd and352self.isgmt == other.isgmt and353self.dstoffset == other.dstoffset)354355__hash__ = None356357def __ne__(self, other):358return not (self == other)359360def __getstate__(self):361state = {}362for name in self.__slots__:363state[name] = getattr(self, name, None)364return state365366def __setstate__(self, state):367for name in self.__slots__:368if name in state:369setattr(self, name, state[name])370371372class _tzfile(object):373"""374Lightweight class for holding the relevant transition and time zone375information read from binary tzfiles.376"""377attrs = ['trans_list', 'trans_list_utc', 'trans_idx', 'ttinfo_list',378'ttinfo_std', 'ttinfo_dst', 'ttinfo_before', 'ttinfo_first']379380def __init__(self, **kwargs):381for attr in self.attrs:382setattr(self, attr, kwargs.get(attr, None))383384385class tzfile(_tzinfo):386"""387This is a ``tzinfo`` subclass that allows one to use the ``tzfile(5)``388format timezone files to extract current and historical zone information.389390:param fileobj:391This can be an opened file stream or a file name that the time zone392information can be read from.393394:param filename:395This is an optional parameter specifying the source of the time zone396information in the event that ``fileobj`` is a file object. If omitted397and ``fileobj`` is a file stream, this parameter will be set either to398``fileobj``'s ``name`` attribute or to ``repr(fileobj)``.399400See `Sources for Time Zone and Daylight Saving Time Data401<https://data.iana.org/time-zones/tz-link.html>`_ for more information.402Time zone files can be compiled from the `IANA Time Zone database files403<https://www.iana.org/time-zones>`_ with the `zic time zone compiler404<https://www.freebsd.org/cgi/man.cgi?query=zic&sektion=8>`_405406.. note::407408Only construct a ``tzfile`` directly if you have a specific timezone409file on disk that you want to read into a Python ``tzinfo`` object.410If you want to get a ``tzfile`` representing a specific IANA zone,411(e.g. ``'America/New_York'``), you should call412:func:`dateutil.tz.gettz` with the zone identifier.413414415**Examples:**416417Using the US Eastern time zone as an example, we can see that a ``tzfile``418provides time zone information for the standard Daylight Saving offsets:419420.. testsetup:: tzfile421422from dateutil.tz import gettz423from datetime import datetime424425.. doctest:: tzfile426427>>> NYC = gettz('America/New_York')428>>> NYC429tzfile('/usr/share/zoneinfo/America/New_York')430431>>> print(datetime(2016, 1, 3, tzinfo=NYC)) # EST4322016-01-03 00:00:00-05:00433434>>> print(datetime(2016, 7, 7, tzinfo=NYC)) # EDT4352016-07-07 00:00:00-04:00436437438The ``tzfile`` structure contains a fully history of the time zone,439so historical dates will also have the right offsets. For example, before440the adoption of the UTC standards, New York used local solar mean time:441442.. doctest:: tzfile443444>>> print(datetime(1901, 4, 12, tzinfo=NYC)) # LMT4451901-04-12 00:00:00-04:56446447And during World War II, New York was on "Eastern War Time", which was a448state of permanent daylight saving time:449450.. doctest:: tzfile451452>>> print(datetime(1944, 2, 7, tzinfo=NYC)) # EWT4531944-02-07 00:00:00-04:00454455"""456457def __init__(self, fileobj, filename=None):458super(tzfile, self).__init__()459460file_opened_here = False461if isinstance(fileobj, string_types):462self._filename = fileobj463fileobj = open(fileobj, 'rb')464file_opened_here = True465elif filename is not None:466self._filename = filename467elif hasattr(fileobj, "name"):468self._filename = fileobj.name469else:470self._filename = repr(fileobj)471472if fileobj is not None:473if not file_opened_here:474fileobj = _nullcontext(fileobj)475476with fileobj as file_stream:477tzobj = self._read_tzfile(file_stream)478479self._set_tzdata(tzobj)480481def _set_tzdata(self, tzobj):482""" Set the time zone data of this object from a _tzfile object """483# Copy the relevant attributes over as private attributes484for attr in _tzfile.attrs:485setattr(self, '_' + attr, getattr(tzobj, attr))486487def _read_tzfile(self, fileobj):488out = _tzfile()489490# From tzfile(5):491#492# The time zone information files used by tzset(3)493# begin with the magic characters "TZif" to identify494# them as time zone information files, followed by495# sixteen bytes reserved for future use, followed by496# six four-byte values of type long, written in a497# ``standard'' byte order (the high-order byte498# of the value is written first).499if fileobj.read(4).decode() != "TZif":500raise ValueError("magic not found")501502fileobj.read(16)503504(505# The number of UTC/local indicators stored in the file.506ttisgmtcnt,507508# The number of standard/wall indicators stored in the file.509ttisstdcnt,510511# The number of leap seconds for which data is512# stored in the file.513leapcnt,514515# The number of "transition times" for which data516# is stored in the file.517timecnt,518519# The number of "local time types" for which data520# is stored in the file (must not be zero).521typecnt,522523# The number of characters of "time zone524# abbreviation strings" stored in the file.525charcnt,526527) = struct.unpack(">6l", fileobj.read(24))528529# The above header is followed by tzh_timecnt four-byte530# values of type long, sorted in ascending order.531# These values are written in ``standard'' byte order.532# Each is used as a transition time (as returned by533# time(2)) at which the rules for computing local time534# change.535536if timecnt:537out.trans_list_utc = list(struct.unpack(">%dl" % timecnt,538fileobj.read(timecnt*4)))539else:540out.trans_list_utc = []541542# Next come tzh_timecnt one-byte values of type unsigned543# char; each one tells which of the different types of544# ``local time'' types described in the file is associated545# with the same-indexed transition time. These values546# serve as indices into an array of ttinfo structures that547# appears next in the file.548549if timecnt:550out.trans_idx = struct.unpack(">%dB" % timecnt,551fileobj.read(timecnt))552else:553out.trans_idx = []554555# Each ttinfo structure is written as a four-byte value556# for tt_gmtoff of type long, in a standard byte557# order, followed by a one-byte value for tt_isdst558# and a one-byte value for tt_abbrind. In each559# structure, tt_gmtoff gives the number of560# seconds to be added to UTC, tt_isdst tells whether561# tm_isdst should be set by localtime(3), and562# tt_abbrind serves as an index into the array of563# time zone abbreviation characters that follow the564# ttinfo structure(s) in the file.565566ttinfo = []567568for i in range(typecnt):569ttinfo.append(struct.unpack(">lbb", fileobj.read(6)))570571abbr = fileobj.read(charcnt).decode()572573# Then there are tzh_leapcnt pairs of four-byte574# values, written in standard byte order; the575# first value of each pair gives the time (as576# returned by time(2)) at which a leap second577# occurs; the second gives the total number of578# leap seconds to be applied after the given time.579# The pairs of values are sorted in ascending order580# by time.581582# Not used, for now (but seek for correct file position)583if leapcnt:584fileobj.seek(leapcnt * 8, os.SEEK_CUR)585586# Then there are tzh_ttisstdcnt standard/wall587# indicators, each stored as a one-byte value;588# they tell whether the transition times associated589# with local time types were specified as standard590# time or wall clock time, and are used when591# a time zone file is used in handling POSIX-style592# time zone environment variables.593594if ttisstdcnt:595isstd = struct.unpack(">%db" % ttisstdcnt,596fileobj.read(ttisstdcnt))597598# Finally, there are tzh_ttisgmtcnt UTC/local599# indicators, each stored as a one-byte value;600# they tell whether the transition times associated601# with local time types were specified as UTC or602# local time, and are used when a time zone file603# is used in handling POSIX-style time zone envi-604# ronment variables.605606if ttisgmtcnt:607isgmt = struct.unpack(">%db" % ttisgmtcnt,608fileobj.read(ttisgmtcnt))609610# Build ttinfo list611out.ttinfo_list = []612for i in range(typecnt):613gmtoff, isdst, abbrind = ttinfo[i]614gmtoff = _get_supported_offset(gmtoff)615tti = _ttinfo()616tti.offset = gmtoff617tti.dstoffset = datetime.timedelta(0)618tti.delta = datetime.timedelta(seconds=gmtoff)619tti.isdst = isdst620tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)]621tti.isstd = (ttisstdcnt > i and isstd[i] != 0)622tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0)623out.ttinfo_list.append(tti)624625# Replace ttinfo indexes for ttinfo objects.626out.trans_idx = [out.ttinfo_list[idx] for idx in out.trans_idx]627628# Set standard, dst, and before ttinfos. before will be629# used when a given time is before any transitions,630# and will be set to the first non-dst ttinfo, or to631# the first dst, if all of them are dst.632out.ttinfo_std = None633out.ttinfo_dst = None634out.ttinfo_before = None635if out.ttinfo_list:636if not out.trans_list_utc:637out.ttinfo_std = out.ttinfo_first = out.ttinfo_list[0]638else:639for i in range(timecnt-1, -1, -1):640tti = out.trans_idx[i]641if not out.ttinfo_std and not tti.isdst:642out.ttinfo_std = tti643elif not out.ttinfo_dst and tti.isdst:644out.ttinfo_dst = tti645646if out.ttinfo_std and out.ttinfo_dst:647break648else:649if out.ttinfo_dst and not out.ttinfo_std:650out.ttinfo_std = out.ttinfo_dst651652for tti in out.ttinfo_list:653if not tti.isdst:654out.ttinfo_before = tti655break656else:657out.ttinfo_before = out.ttinfo_list[0]658659# Now fix transition times to become relative to wall time.660#661# I'm not sure about this. In my tests, the tz source file662# is setup to wall time, and in the binary file isstd and663# isgmt are off, so it should be in wall time. OTOH, it's664# always in gmt time. Let me know if you have comments665# about this.666lastdst = None667lastoffset = None668lastdstoffset = None669lastbaseoffset = None670out.trans_list = []671672for i, tti in enumerate(out.trans_idx):673offset = tti.offset674dstoffset = 0675676if lastdst is not None:677if tti.isdst:678if not lastdst:679dstoffset = offset - lastoffset680681if not dstoffset and lastdstoffset:682dstoffset = lastdstoffset683684tti.dstoffset = datetime.timedelta(seconds=dstoffset)685lastdstoffset = dstoffset686687# If a time zone changes its base offset during a DST transition,688# then you need to adjust by the previous base offset to get the689# transition time in local time. Otherwise you use the current690# base offset. Ideally, I would have some mathematical proof of691# why this is true, but I haven't really thought about it enough.692baseoffset = offset - dstoffset693adjustment = baseoffset694if (lastbaseoffset is not None and baseoffset != lastbaseoffset695and tti.isdst != lastdst):696# The base DST has changed697adjustment = lastbaseoffset698699lastdst = tti.isdst700lastoffset = offset701lastbaseoffset = baseoffset702703out.trans_list.append(out.trans_list_utc[i] + adjustment)704705out.trans_idx = tuple(out.trans_idx)706out.trans_list = tuple(out.trans_list)707out.trans_list_utc = tuple(out.trans_list_utc)708709return out710711def _find_last_transition(self, dt, in_utc=False):712# If there's no list, there are no transitions to find713if not self._trans_list:714return None715716timestamp = _datetime_to_timestamp(dt)717718# Find where the timestamp fits in the transition list - if the719# timestamp is a transition time, it's part of the "after" period.720trans_list = self._trans_list_utc if in_utc else self._trans_list721idx = bisect.bisect_right(trans_list, timestamp)722723# We want to know when the previous transition was, so subtract off 1724return idx - 1725726def _get_ttinfo(self, idx):727# For no list or after the last transition, default to _ttinfo_std728if idx is None or (idx + 1) >= len(self._trans_list):729return self._ttinfo_std730731# If there is a list and the time is before it, return _ttinfo_before732if idx < 0:733return self._ttinfo_before734735return self._trans_idx[idx]736737def _find_ttinfo(self, dt):738idx = self._resolve_ambiguous_time(dt)739740return self._get_ttinfo(idx)741742def fromutc(self, dt):743"""744The ``tzfile`` implementation of :py:func:`datetime.tzinfo.fromutc`.745746:param dt:747A :py:class:`datetime.datetime` object.748749:raises TypeError:750Raised if ``dt`` is not a :py:class:`datetime.datetime` object.751752:raises ValueError:753Raised if this is called with a ``dt`` which does not have this754``tzinfo`` attached.755756:return:757Returns a :py:class:`datetime.datetime` object representing the758wall time in ``self``'s time zone.759"""760# These isinstance checks are in datetime.tzinfo, so we'll preserve761# them, even if we don't care about duck typing.762if not isinstance(dt, datetime.datetime):763raise TypeError("fromutc() requires a datetime argument")764765if dt.tzinfo is not self:766raise ValueError("dt.tzinfo is not self")767768# First treat UTC as wall time and get the transition we're in.769idx = self._find_last_transition(dt, in_utc=True)770tti = self._get_ttinfo(idx)771772dt_out = dt + datetime.timedelta(seconds=tti.offset)773774fold = self.is_ambiguous(dt_out, idx=idx)775776return enfold(dt_out, fold=int(fold))777778def is_ambiguous(self, dt, idx=None):779"""780Whether or not the "wall time" of a given datetime is ambiguous in this781zone.782783:param dt:784A :py:class:`datetime.datetime`, naive or time zone aware.785786787:return:788Returns ``True`` if ambiguous, ``False`` otherwise.789790.. versionadded:: 2.6.0791"""792if idx is None:793idx = self._find_last_transition(dt)794795# Calculate the difference in offsets from current to previous796timestamp = _datetime_to_timestamp(dt)797tti = self._get_ttinfo(idx)798799if idx is None or idx <= 0:800return False801802od = self._get_ttinfo(idx - 1).offset - tti.offset803tt = self._trans_list[idx] # Transition time804805return timestamp < tt + od806807def _resolve_ambiguous_time(self, dt):808idx = self._find_last_transition(dt)809810# If we have no transitions, return the index811_fold = self._fold(dt)812if idx is None or idx == 0:813return idx814815# If it's ambiguous and we're in a fold, shift to a different index.816idx_offset = int(not _fold and self.is_ambiguous(dt, idx))817818return idx - idx_offset819820def utcoffset(self, dt):821if dt is None:822return None823824if not self._ttinfo_std:825return ZERO826827return self._find_ttinfo(dt).delta828829def dst(self, dt):830if dt is None:831return None832833if not self._ttinfo_dst:834return ZERO835836tti = self._find_ttinfo(dt)837838if not tti.isdst:839return ZERO840841# The documentation says that utcoffset()-dst() must842# be constant for every dt.843return tti.dstoffset844845@tzname_in_python2846def tzname(self, dt):847if not self._ttinfo_std or dt is None:848return None849return self._find_ttinfo(dt).abbr850851def __eq__(self, other):852if not isinstance(other, tzfile):853return NotImplemented854return (self._trans_list == other._trans_list and855self._trans_idx == other._trans_idx and856self._ttinfo_list == other._ttinfo_list)857858__hash__ = None859860def __ne__(self, other):861return not (self == other)862863def __repr__(self):864return "%s(%s)" % (self.__class__.__name__, repr(self._filename))865866def __reduce__(self):867return self.__reduce_ex__(None)868869def __reduce_ex__(self, protocol):870return (self.__class__, (None, self._filename), self.__dict__)871872873class tzrange(tzrangebase):874"""875The ``tzrange`` object is a time zone specified by a set of offsets and876abbreviations, equivalent to the way the ``TZ`` variable can be specified877in POSIX-like systems, but using Python delta objects to specify DST878start, end and offsets.879880:param stdabbr:881The abbreviation for standard time (e.g. ``'EST'``).882883:param stdoffset:884An integer or :class:`datetime.timedelta` object or equivalent885specifying the base offset from UTC.886887If unspecified, +00:00 is used.888889:param dstabbr:890The abbreviation for DST / "Summer" time (e.g. ``'EDT'``).891892If specified, with no other DST information, DST is assumed to occur893and the default behavior or ``dstoffset``, ``start`` and ``end`` is894used. If unspecified and no other DST information is specified, it895is assumed that this zone has no DST.896897If this is unspecified and other DST information is *is* specified,898DST occurs in the zone but the time zone abbreviation is left899unchanged.900901:param dstoffset:902A an integer or :class:`datetime.timedelta` object or equivalent903specifying the UTC offset during DST. If unspecified and any other DST904information is specified, it is assumed to be the STD offset +1 hour.905906:param start:907A :class:`relativedelta.relativedelta` object or equivalent specifying908the time and time of year that daylight savings time starts. To909specify, for example, that DST starts at 2AM on the 2nd Sunday in910March, pass:911912``relativedelta(hours=2, month=3, day=1, weekday=SU(+2))``913914If unspecified and any other DST information is specified, the default915value is 2 AM on the first Sunday in April.916917:param end:918A :class:`relativedelta.relativedelta` object or equivalent919representing the time and time of year that daylight savings time920ends, with the same specification method as in ``start``. One note is921that this should point to the first time in the *standard* zone, so if922a transition occurs at 2AM in the DST zone and the clocks are set back9231 hour to 1AM, set the ``hours`` parameter to +1.924925926**Examples:**927928.. testsetup:: tzrange929930from dateutil.tz import tzrange, tzstr931932.. doctest:: tzrange933934>>> tzstr('EST5EDT') == tzrange("EST", -18000, "EDT")935True936937>>> from dateutil.relativedelta import *938>>> range1 = tzrange("EST", -18000, "EDT")939>>> range2 = tzrange("EST", -18000, "EDT", -14400,940... relativedelta(hours=+2, month=4, day=1,941... weekday=SU(+1)),942... relativedelta(hours=+1, month=10, day=31,943... weekday=SU(-1)))944>>> tzstr('EST5EDT') == range1 == range2945True946947"""948def __init__(self, stdabbr, stdoffset=None,949dstabbr=None, dstoffset=None,950start=None, end=None):951952global relativedelta953from dateutil import relativedelta954955self._std_abbr = stdabbr956self._dst_abbr = dstabbr957958try:959stdoffset = stdoffset.total_seconds()960except (TypeError, AttributeError):961pass962963try:964dstoffset = dstoffset.total_seconds()965except (TypeError, AttributeError):966pass967968if stdoffset is not None:969self._std_offset = datetime.timedelta(seconds=stdoffset)970else:971self._std_offset = ZERO972973if dstoffset is not None:974self._dst_offset = datetime.timedelta(seconds=dstoffset)975elif dstabbr and stdoffset is not None:976self._dst_offset = self._std_offset + datetime.timedelta(hours=+1)977else:978self._dst_offset = ZERO979980if dstabbr and start is None:981self._start_delta = relativedelta.relativedelta(982hours=+2, month=4, day=1, weekday=relativedelta.SU(+1))983else:984self._start_delta = start985986if dstabbr and end is None:987self._end_delta = relativedelta.relativedelta(988hours=+1, month=10, day=31, weekday=relativedelta.SU(-1))989else:990self._end_delta = end991992self._dst_base_offset_ = self._dst_offset - self._std_offset993self.hasdst = bool(self._start_delta)994995def transitions(self, year):996"""997For a given year, get the DST on and off transition times, expressed998always on the standard time side. For zones with no transitions, this999function returns ``None``.10001001:param year:1002The year whose transitions you would like to query.10031004:return:1005Returns a :class:`tuple` of :class:`datetime.datetime` objects,1006``(dston, dstoff)`` for zones with an annual DST transition, or1007``None`` for fixed offset zones.1008"""1009if not self.hasdst:1010return None10111012base_year = datetime.datetime(year, 1, 1)10131014start = base_year + self._start_delta1015end = base_year + self._end_delta10161017return (start, end)10181019def __eq__(self, other):1020if not isinstance(other, tzrange):1021return NotImplemented10221023return (self._std_abbr == other._std_abbr and1024self._dst_abbr == other._dst_abbr and1025self._std_offset == other._std_offset and1026self._dst_offset == other._dst_offset and1027self._start_delta == other._start_delta and1028self._end_delta == other._end_delta)10291030@property1031def _dst_base_offset(self):1032return self._dst_base_offset_103310341035@six.add_metaclass(_TzStrFactory)1036class tzstr(tzrange):1037"""1038``tzstr`` objects are time zone objects specified by a time-zone string as1039it would be passed to a ``TZ`` variable on POSIX-style systems (see1040the `GNU C Library: TZ Variable`_ for more details).10411042There is one notable exception, which is that POSIX-style time zones use an1043inverted offset format, so normally ``GMT+3`` would be parsed as an offset10443 hours *behind* GMT. The ``tzstr`` time zone object will parse this as an1045offset 3 hours *ahead* of GMT. If you would like to maintain the POSIX1046behavior, pass a ``True`` value to ``posix_offset``.10471048The :class:`tzrange` object provides the same functionality, but is1049specified using :class:`relativedelta.relativedelta` objects. rather than1050strings.10511052:param s:1053A time zone string in ``TZ`` variable format. This can be a1054:class:`bytes` (2.x: :class:`str`), :class:`str` (2.x:1055:class:`unicode`) or a stream emitting unicode characters1056(e.g. :class:`StringIO`).10571058:param posix_offset:1059Optional. If set to ``True``, interpret strings such as ``GMT+3`` or1060``UTC+3`` as being 3 hours *behind* UTC rather than ahead, per the1061POSIX standard.10621063.. caution::10641065Prior to version 2.7.0, this function also supported time zones1066in the format:10671068* ``EST5EDT,4,0,6,7200,10,0,26,7200,3600``1069* ``EST5EDT,4,1,0,7200,10,-1,0,7200,3600``10701071This format is non-standard and has been deprecated; this function1072will raise a :class:`DeprecatedTZFormatWarning` until1073support is removed in a future version.10741075.. _`GNU C Library: TZ Variable`:1076https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html1077"""1078def __init__(self, s, posix_offset=False):1079global parser1080from dateutil.parser import _parser as parser10811082self._s = s10831084res = parser._parsetz(s)1085if res is None or res.any_unused_tokens:1086raise ValueError("unknown string format")10871088# Here we break the compatibility with the TZ variable handling.1089# GMT-3 actually *means* the timezone -3.1090if res.stdabbr in ("GMT", "UTC") and not posix_offset:1091res.stdoffset *= -110921093# We must initialize it first, since _delta() needs1094# _std_offset and _dst_offset set. Use False in start/end1095# to avoid building it two times.1096tzrange.__init__(self, res.stdabbr, res.stdoffset,1097res.dstabbr, res.dstoffset,1098start=False, end=False)10991100if not res.dstabbr:1101self._start_delta = None1102self._end_delta = None1103else:1104self._start_delta = self._delta(res.start)1105if self._start_delta:1106self._end_delta = self._delta(res.end, isend=1)11071108self.hasdst = bool(self._start_delta)11091110def _delta(self, x, isend=0):1111from dateutil import relativedelta1112kwargs = {}1113if x.month is not None:1114kwargs["month"] = x.month1115if x.weekday is not None:1116kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week)1117if x.week > 0:1118kwargs["day"] = 11119else:1120kwargs["day"] = 311121elif x.day:1122kwargs["day"] = x.day1123elif x.yday is not None:1124kwargs["yearday"] = x.yday1125elif x.jyday is not None:1126kwargs["nlyearday"] = x.jyday1127if not kwargs:1128# Default is to start on first sunday of april, and end1129# on last sunday of october.1130if not isend:1131kwargs["month"] = 41132kwargs["day"] = 11133kwargs["weekday"] = relativedelta.SU(+1)1134else:1135kwargs["month"] = 101136kwargs["day"] = 311137kwargs["weekday"] = relativedelta.SU(-1)1138if x.time is not None:1139kwargs["seconds"] = x.time1140else:1141# Default is 2AM.1142kwargs["seconds"] = 72001143if isend:1144# Convert to standard time, to follow the documented way1145# of working with the extra hour. See the documentation1146# of the tzinfo class.1147delta = self._dst_offset - self._std_offset1148kwargs["seconds"] -= delta.seconds + delta.days * 864001149return relativedelta.relativedelta(**kwargs)11501151def __repr__(self):1152return "%s(%s)" % (self.__class__.__name__, repr(self._s))115311541155class _tzicalvtzcomp(object):1156def __init__(self, tzoffsetfrom, tzoffsetto, isdst,1157tzname=None, rrule=None):1158self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom)1159self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto)1160self.tzoffsetdiff = self.tzoffsetto - self.tzoffsetfrom1161self.isdst = isdst1162self.tzname = tzname1163self.rrule = rrule116411651166class _tzicalvtz(_tzinfo):1167def __init__(self, tzid, comps=[]):1168super(_tzicalvtz, self).__init__()11691170self._tzid = tzid1171self._comps = comps1172self._cachedate = []1173self._cachecomp = []1174self._cache_lock = _thread.allocate_lock()11751176def _find_comp(self, dt):1177if len(self._comps) == 1:1178return self._comps[0]11791180dt = dt.replace(tzinfo=None)11811182try:1183with self._cache_lock:1184return self._cachecomp[self._cachedate.index(1185(dt, self._fold(dt)))]1186except ValueError:1187pass11881189lastcompdt = None1190lastcomp = None11911192for comp in self._comps:1193compdt = self._find_compdt(comp, dt)11941195if compdt and (not lastcompdt or lastcompdt < compdt):1196lastcompdt = compdt1197lastcomp = comp11981199if not lastcomp:1200# RFC says nothing about what to do when a given1201# time is before the first onset date. We'll look for the1202# first standard component, or the first component, if1203# none is found.1204for comp in self._comps:1205if not comp.isdst:1206lastcomp = comp1207break1208else:1209lastcomp = comp[0]12101211with self._cache_lock:1212self._cachedate.insert(0, (dt, self._fold(dt)))1213self._cachecomp.insert(0, lastcomp)12141215if len(self._cachedate) > 10:1216self._cachedate.pop()1217self._cachecomp.pop()12181219return lastcomp12201221def _find_compdt(self, comp, dt):1222if comp.tzoffsetdiff < ZERO and self._fold(dt):1223dt -= comp.tzoffsetdiff12241225compdt = comp.rrule.before(dt, inc=True)12261227return compdt12281229def utcoffset(self, dt):1230if dt is None:1231return None12321233return self._find_comp(dt).tzoffsetto12341235def dst(self, dt):1236comp = self._find_comp(dt)1237if comp.isdst:1238return comp.tzoffsetdiff1239else:1240return ZERO12411242@tzname_in_python21243def tzname(self, dt):1244return self._find_comp(dt).tzname12451246def __repr__(self):1247return "<tzicalvtz %s>" % repr(self._tzid)12481249__reduce__ = object.__reduce__125012511252class tzical(object):1253"""1254This object is designed to parse an iCalendar-style ``VTIMEZONE`` structure1255as set out in `RFC 5545`_ Section 4.6.5 into one or more `tzinfo` objects.12561257:param `fileobj`:1258A file or stream in iCalendar format, which should be UTF-8 encoded1259with CRLF endings.12601261.. _`RFC 5545`: https://tools.ietf.org/html/rfc55451262"""1263def __init__(self, fileobj):1264global rrule1265from dateutil import rrule12661267if isinstance(fileobj, string_types):1268self._s = fileobj1269# ical should be encoded in UTF-8 with CRLF1270fileobj = open(fileobj, 'r')1271else:1272self._s = getattr(fileobj, 'name', repr(fileobj))1273fileobj = _nullcontext(fileobj)12741275self._vtz = {}12761277with fileobj as fobj:1278self._parse_rfc(fobj.read())12791280def keys(self):1281"""1282Retrieves the available time zones as a list.1283"""1284return list(self._vtz.keys())12851286def get(self, tzid=None):1287"""1288Retrieve a :py:class:`datetime.tzinfo` object by its ``tzid``.12891290:param tzid:1291If there is exactly one time zone available, omitting ``tzid``1292or passing :py:const:`None` value returns it. Otherwise a valid1293key (which can be retrieved from :func:`keys`) is required.12941295:raises ValueError:1296Raised if ``tzid`` is not specified but there are either more1297or fewer than 1 zone defined.12981299:returns:1300Returns either a :py:class:`datetime.tzinfo` object representing1301the relevant time zone or :py:const:`None` if the ``tzid`` was1302not found.1303"""1304if tzid is None:1305if len(self._vtz) == 0:1306raise ValueError("no timezones defined")1307elif len(self._vtz) > 1:1308raise ValueError("more than one timezone available")1309tzid = next(iter(self._vtz))13101311return self._vtz.get(tzid)13121313def _parse_offset(self, s):1314s = s.strip()1315if not s:1316raise ValueError("empty offset")1317if s[0] in ('+', '-'):1318signal = (-1, +1)[s[0] == '+']1319s = s[1:]1320else:1321signal = +11322if len(s) == 4:1323return (int(s[:2]) * 3600 + int(s[2:]) * 60) * signal1324elif len(s) == 6:1325return (int(s[:2]) * 3600 + int(s[2:4]) * 60 + int(s[4:])) * signal1326else:1327raise ValueError("invalid offset: " + s)13281329def _parse_rfc(self, s):1330lines = s.splitlines()1331if not lines:1332raise ValueError("empty string")13331334# Unfold1335i = 01336while i < len(lines):1337line = lines[i].rstrip()1338if not line:1339del lines[i]1340elif i > 0 and line[0] == " ":1341lines[i-1] += line[1:]1342del lines[i]1343else:1344i += 113451346tzid = None1347comps = []1348invtz = False1349comptype = None1350for line in lines:1351if not line:1352continue1353name, value = line.split(':', 1)1354parms = name.split(';')1355if not parms:1356raise ValueError("empty property name")1357name = parms[0].upper()1358parms = parms[1:]1359if invtz:1360if name == "BEGIN":1361if value in ("STANDARD", "DAYLIGHT"):1362# Process component1363pass1364else:1365raise ValueError("unknown component: "+value)1366comptype = value1367founddtstart = False1368tzoffsetfrom = None1369tzoffsetto = None1370rrulelines = []1371tzname = None1372elif name == "END":1373if value == "VTIMEZONE":1374if comptype:1375raise ValueError("component not closed: "+comptype)1376if not tzid:1377raise ValueError("mandatory TZID not found")1378if not comps:1379raise ValueError(1380"at least one component is needed")1381# Process vtimezone1382self._vtz[tzid] = _tzicalvtz(tzid, comps)1383invtz = False1384elif value == comptype:1385if not founddtstart:1386raise ValueError("mandatory DTSTART not found")1387if tzoffsetfrom is None:1388raise ValueError(1389"mandatory TZOFFSETFROM not found")1390if tzoffsetto is None:1391raise ValueError(1392"mandatory TZOFFSETFROM not found")1393# Process component1394rr = None1395if rrulelines:1396rr = rrule.rrulestr("\n".join(rrulelines),1397compatible=True,1398ignoretz=True,1399cache=True)1400comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto,1401(comptype == "DAYLIGHT"),1402tzname, rr)1403comps.append(comp)1404comptype = None1405else:1406raise ValueError("invalid component end: "+value)1407elif comptype:1408if name == "DTSTART":1409# DTSTART in VTIMEZONE takes a subset of valid RRULE1410# values under RFC 5545.1411for parm in parms:1412if parm != 'VALUE=DATE-TIME':1413msg = ('Unsupported DTSTART param in ' +1414'VTIMEZONE: ' + parm)1415raise ValueError(msg)1416rrulelines.append(line)1417founddtstart = True1418elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"):1419rrulelines.append(line)1420elif name == "TZOFFSETFROM":1421if parms:1422raise ValueError(1423"unsupported %s parm: %s " % (name, parms[0]))1424tzoffsetfrom = self._parse_offset(value)1425elif name == "TZOFFSETTO":1426if parms:1427raise ValueError(1428"unsupported TZOFFSETTO parm: "+parms[0])1429tzoffsetto = self._parse_offset(value)1430elif name == "TZNAME":1431if parms:1432raise ValueError(1433"unsupported TZNAME parm: "+parms[0])1434tzname = value1435elif name == "COMMENT":1436pass1437else:1438raise ValueError("unsupported property: "+name)1439else:1440if name == "TZID":1441if parms:1442raise ValueError(1443"unsupported TZID parm: "+parms[0])1444tzid = value1445elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"):1446pass1447else:1448raise ValueError("unsupported property: "+name)1449elif name == "BEGIN" and value == "VTIMEZONE":1450tzid = None1451comps = []1452invtz = True14531454def __repr__(self):1455return "%s(%s)" % (self.__class__.__name__, repr(self._s))145614571458if sys.platform != "win32":1459TZFILES = ["/etc/localtime", "localtime"]1460TZPATHS = ["/usr/share/zoneinfo",1461"/usr/lib/zoneinfo",1462"/usr/share/lib/zoneinfo",1463"/etc/zoneinfo"]1464else:1465TZFILES = []1466TZPATHS = []146714681469def __get_gettz():1470tzlocal_classes = (tzlocal,)1471if tzwinlocal is not None:1472tzlocal_classes += (tzwinlocal,)14731474class GettzFunc(object):1475"""1476Retrieve a time zone object from a string representation14771478This function is intended to retrieve the :py:class:`tzinfo` subclass1479that best represents the time zone that would be used if a POSIX1480`TZ variable`_ were set to the same value.14811482If no argument or an empty string is passed to ``gettz``, local time1483is returned:14841485.. code-block:: python314861487>>> gettz()1488tzfile('/etc/localtime')14891490This function is also the preferred way to map IANA tz database keys1491to :class:`tzfile` objects:14921493.. code-block:: python314941495>>> gettz('Pacific/Kiritimati')1496tzfile('/usr/share/zoneinfo/Pacific/Kiritimati')14971498On Windows, the standard is extended to include the Windows-specific1499zone names provided by the operating system:15001501.. code-block:: python315021503>>> gettz('Egypt Standard Time')1504tzwin('Egypt Standard Time')15051506Passing a GNU ``TZ`` style string time zone specification returns a1507:class:`tzstr` object:15081509.. code-block:: python315101511>>> gettz('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3')1512tzstr('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3')15131514:param name:1515A time zone name (IANA, or, on Windows, Windows keys), location of1516a ``tzfile(5)`` zoneinfo file or ``TZ`` variable style time zone1517specifier. An empty string, no argument or ``None`` is interpreted1518as local time.15191520:return:1521Returns an instance of one of ``dateutil``'s :py:class:`tzinfo`1522subclasses.15231524.. versionchanged:: 2.7.015251526After version 2.7.0, any two calls to ``gettz`` using the same1527input strings will return the same object:15281529.. code-block:: python315301531>>> tz.gettz('America/Chicago') is tz.gettz('America/Chicago')1532True15331534In addition to improving performance, this ensures that1535`"same zone" semantics`_ are used for datetimes in the same zone.153615371538.. _`TZ variable`:1539https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html15401541.. _`"same zone" semantics`:1542https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html1543"""1544def __init__(self):15451546self.__instances = weakref.WeakValueDictionary()1547self.__strong_cache_size = 81548self.__strong_cache = OrderedDict()1549self._cache_lock = _thread.allocate_lock()15501551def __call__(self, name=None):1552with self._cache_lock:1553rv = self.__instances.get(name, None)15541555if rv is None:1556rv = self.nocache(name=name)1557if not (name is None1558or isinstance(rv, tzlocal_classes)1559or rv is None):1560# tzlocal is slightly more complicated than the other1561# time zone providers because it depends on environment1562# at construction time, so don't cache that.1563#1564# We also cannot store weak references to None, so we1565# will also not store that.1566self.__instances[name] = rv1567else:1568# No need for strong caching, return immediately1569return rv15701571self.__strong_cache[name] = self.__strong_cache.pop(name, rv)15721573if len(self.__strong_cache) > self.__strong_cache_size:1574self.__strong_cache.popitem(last=False)15751576return rv15771578def set_cache_size(self, size):1579with self._cache_lock:1580self.__strong_cache_size = size1581while len(self.__strong_cache) > size:1582self.__strong_cache.popitem(last=False)15831584def cache_clear(self):1585with self._cache_lock:1586self.__instances = weakref.WeakValueDictionary()1587self.__strong_cache.clear()15881589@staticmethod1590def nocache(name=None):1591"""A non-cached version of gettz"""1592tz = None1593if not name:1594try:1595name = os.environ["TZ"]1596except KeyError:1597pass1598if name is None or name in ("", ":"):1599for filepath in TZFILES:1600if not os.path.isabs(filepath):1601filename = filepath1602for path in TZPATHS:1603filepath = os.path.join(path, filename)1604if os.path.isfile(filepath):1605break1606else:1607continue1608if os.path.isfile(filepath):1609try:1610tz = tzfile(filepath)1611break1612except (IOError, OSError, ValueError):1613pass1614else:1615tz = tzlocal()1616else:1617try:1618if name.startswith(":"):1619name = name[1:]1620except TypeError as e:1621if isinstance(name, bytes):1622new_msg = "gettz argument should be str, not bytes"1623six.raise_from(TypeError(new_msg), e)1624else:1625raise1626if os.path.isabs(name):1627if os.path.isfile(name):1628tz = tzfile(name)1629else:1630tz = None1631else:1632for path in TZPATHS:1633filepath = os.path.join(path, name)1634if not os.path.isfile(filepath):1635filepath = filepath.replace(' ', '_')1636if not os.path.isfile(filepath):1637continue1638try:1639tz = tzfile(filepath)1640break1641except (IOError, OSError, ValueError):1642pass1643else:1644tz = None1645if tzwin is not None:1646try:1647tz = tzwin(name)1648except (WindowsError, UnicodeEncodeError):1649# UnicodeEncodeError is for Python 2.7 compat1650tz = None16511652if not tz:1653from dateutil.zoneinfo import get_zonefile_instance1654tz = get_zonefile_instance().get(name)16551656if not tz:1657for c in name:1658# name is not a tzstr unless it has at least1659# one offset. For short values of "name", an1660# explicit for loop seems to be the fastest way1661# To determine if a string contains a digit1662if c in "0123456789":1663try:1664tz = tzstr(name)1665except ValueError:1666pass1667break1668else:1669if name in ("GMT", "UTC"):1670tz = UTC1671elif name in time.tzname:1672tz = tzlocal()1673return tz16741675return GettzFunc()167616771678gettz = __get_gettz()1679del __get_gettz168016811682def datetime_exists(dt, tz=None):1683"""1684Given a datetime and a time zone, determine whether or not a given datetime1685would fall in a gap.16861687:param dt:1688A :class:`datetime.datetime` (whose time zone will be ignored if ``tz``1689is provided.)16901691:param tz:1692A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If1693``None`` or not provided, the datetime's own time zone will be used.16941695:return:1696Returns a boolean value whether or not the "wall time" exists in1697``tz``.16981699.. versionadded:: 2.7.01700"""1701if tz is None:1702if dt.tzinfo is None:1703raise ValueError('Datetime is naive and no time zone provided.')1704tz = dt.tzinfo17051706dt = dt.replace(tzinfo=None)17071708# This is essentially a test of whether or not the datetime can survive1709# a round trip to UTC.1710dt_rt = dt.replace(tzinfo=tz).astimezone(UTC).astimezone(tz)1711dt_rt = dt_rt.replace(tzinfo=None)17121713return dt == dt_rt171417151716def datetime_ambiguous(dt, tz=None):1717"""1718Given a datetime and a time zone, determine whether or not a given datetime1719is ambiguous (i.e if there are two times differentiated only by their DST1720status).17211722:param dt:1723A :class:`datetime.datetime` (whose time zone will be ignored if ``tz``1724is provided.)17251726:param tz:1727A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If1728``None`` or not provided, the datetime's own time zone will be used.17291730:return:1731Returns a boolean value whether or not the "wall time" is ambiguous in1732``tz``.17331734.. versionadded:: 2.6.01735"""1736if tz is None:1737if dt.tzinfo is None:1738raise ValueError('Datetime is naive and no time zone provided.')17391740tz = dt.tzinfo17411742# If a time zone defines its own "is_ambiguous" function, we'll use that.1743is_ambiguous_fn = getattr(tz, 'is_ambiguous', None)1744if is_ambiguous_fn is not None:1745try:1746return tz.is_ambiguous(dt)1747except Exception:1748pass17491750# If it doesn't come out and tell us it's ambiguous, we'll just check if1751# the fold attribute has any effect on this particular date and time.1752dt = dt.replace(tzinfo=tz)1753wall_0 = enfold(dt, fold=0)1754wall_1 = enfold(dt, fold=1)17551756same_offset = wall_0.utcoffset() == wall_1.utcoffset()1757same_dst = wall_0.dst() == wall_1.dst()17581759return not (same_offset and same_dst)176017611762def resolve_imaginary(dt):1763"""1764Given a datetime that may be imaginary, return an existing datetime.17651766This function assumes that an imaginary datetime represents what the1767wall time would be in a zone had the offset transition not occurred, so1768it will always fall forward by the transition's change in offset.17691770.. doctest::17711772>>> from dateutil import tz1773>>> from datetime import datetime1774>>> NYC = tz.gettz('America/New_York')1775>>> print(tz.resolve_imaginary(datetime(2017, 3, 12, 2, 30, tzinfo=NYC)))17762017-03-12 03:30:00-04:0017771778>>> KIR = tz.gettz('Pacific/Kiritimati')1779>>> print(tz.resolve_imaginary(datetime(1995, 1, 1, 12, 30, tzinfo=KIR)))17801995-01-02 12:30:00+14:0017811782As a note, :func:`datetime.astimezone` is guaranteed to produce a valid,1783existing datetime, so a round-trip to and from UTC is sufficient to get1784an extant datetime, however, this generally "falls back" to an earlier time1785rather than falling forward to the STD side (though no guarantees are made1786about this behavior).17871788:param dt:1789A :class:`datetime.datetime` which may or may not exist.17901791:return:1792Returns an existing :class:`datetime.datetime`. If ``dt`` was not1793imaginary, the datetime returned is guaranteed to be the same object1794passed to the function.17951796.. versionadded:: 2.7.01797"""1798if dt.tzinfo is not None and not datetime_exists(dt):17991800curr_offset = (dt + datetime.timedelta(hours=24)).utcoffset()1801old_offset = (dt - datetime.timedelta(hours=24)).utcoffset()18021803dt += curr_offset - old_offset18041805return dt180618071808def _datetime_to_timestamp(dt):1809"""1810Convert a :class:`datetime.datetime` object to an epoch timestamp in1811seconds since January 1, 1970, ignoring the time zone.1812"""1813return (dt.replace(tzinfo=None) - EPOCH).total_seconds()181418151816if sys.version_info >= (3, 6):1817def _get_supported_offset(second_offset):1818return second_offset1819else:1820def _get_supported_offset(second_offset):1821# For python pre-3.6, round to full-minutes if that's not the case.1822# Python's datetime doesn't accept sub-minute timezones. Check1823# http://python.org/sf/1447945 or https://bugs.python.org/issue52881824# for some information.1825old_offset = second_offset1826calculated_offset = 60 * ((second_offset + 30) // 60)1827return calculated_offset182818291830try:1831# Python 3.7 feature1832from contextlib import nullcontext as _nullcontext1833except ImportError:1834class _nullcontext(object):1835"""1836Class for wrapping contexts so that they are passed through in a1837with statement.1838"""1839def __init__(self, context):1840self.context = context18411842def __enter__(self):1843return self.context18441845def __exit__(*args, **kwargs):1846pass18471848# vim:ts=4:sw=4:et184918501851