1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849 |
- # -*- coding: utf-8 -*-
- """
- This module offers timezone implementations subclassing the abstract
- :py:class:`datetime.tzinfo` type. There are classes to handle tzfile format
- files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`,
- etc), TZ environment string (in all known formats), given ranges (with help
- from relative deltas), local machine timezone, fixed offset timezone, and UTC
- timezone.
- """
- import datetime
- import struct
- import time
- import sys
- import os
- import bisect
- import weakref
- from collections import OrderedDict
- import six
- from six import string_types
- from six.moves import _thread
- from ._common import tzname_in_python2, _tzinfo
- from ._common import tzrangebase, enfold
- from ._common import _validate_fromutc_inputs
- from ._factories import _TzSingleton, _TzOffsetFactory
- from ._factories import _TzStrFactory
- try:
- from .win import tzwin, tzwinlocal
- except ImportError:
- tzwin = tzwinlocal = None
- # For warning about rounding tzinfo
- from warnings import warn
- ZERO = datetime.timedelta(0)
- EPOCH = datetime.datetime.utcfromtimestamp(0)
- EPOCHORDINAL = EPOCH.toordinal()
- @six.add_metaclass(_TzSingleton)
- class tzutc(datetime.tzinfo):
- """
- This is a tzinfo object that represents the UTC time zone.
- **Examples:**
- .. doctest::
- >>> from datetime import *
- >>> from dateutil.tz import *
- >>> datetime.now()
- datetime.datetime(2003, 9, 27, 9, 40, 1, 521290)
- >>> datetime.now(tzutc())
- datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc())
- >>> datetime.now(tzutc()).tzname()
- 'UTC'
- .. versionchanged:: 2.7.0
- ``tzutc()`` is now a singleton, so the result of ``tzutc()`` will
- always return the same object.
- .. doctest::
- >>> from dateutil.tz import tzutc, UTC
- >>> tzutc() is tzutc()
- True
- >>> tzutc() is UTC
- True
- """
- def utcoffset(self, dt):
- return ZERO
- def dst(self, dt):
- return ZERO
- @tzname_in_python2
- def tzname(self, dt):
- return "UTC"
- def is_ambiguous(self, dt):
- """
- Whether or not the "wall time" of a given datetime is ambiguous in this
- zone.
- :param dt:
- A :py:class:`datetime.datetime`, naive or time zone aware.
- :return:
- Returns ``True`` if ambiguous, ``False`` otherwise.
- .. versionadded:: 2.6.0
- """
- return False
- @_validate_fromutc_inputs
- def fromutc(self, dt):
- """
- Fast track version of fromutc() returns the original ``dt`` object for
- any valid :py:class:`datetime.datetime` object.
- """
- return dt
- def __eq__(self, other):
- if not isinstance(other, (tzutc, tzoffset)):
- return NotImplemented
- return (isinstance(other, tzutc) or
- (isinstance(other, tzoffset) and other._offset == ZERO))
- __hash__ = None
- def __ne__(self, other):
- return not (self == other)
- def __repr__(self):
- return "%s()" % self.__class__.__name__
- __reduce__ = object.__reduce__
- #: Convenience constant providing a :class:`tzutc()` instance
- #:
- #: .. versionadded:: 2.7.0
- UTC = tzutc()
- @six.add_metaclass(_TzOffsetFactory)
- class tzoffset(datetime.tzinfo):
- """
- A simple class for representing a fixed offset from UTC.
- :param name:
- The timezone name, to be returned when ``tzname()`` is called.
- :param offset:
- The time zone offset in seconds, or (since version 2.6.0, represented
- as a :py:class:`datetime.timedelta` object).
- """
- def __init__(self, name, offset):
- self._name = name
- try:
- # Allow a timedelta
- offset = offset.total_seconds()
- except (TypeError, AttributeError):
- pass
- self._offset = datetime.timedelta(seconds=_get_supported_offset(offset))
- def utcoffset(self, dt):
- return self._offset
- def dst(self, dt):
- return ZERO
- @tzname_in_python2
- def tzname(self, dt):
- return self._name
- @_validate_fromutc_inputs
- def fromutc(self, dt):
- return dt + self._offset
- def is_ambiguous(self, dt):
- """
- Whether or not the "wall time" of a given datetime is ambiguous in this
- zone.
- :param dt:
- A :py:class:`datetime.datetime`, naive or time zone aware.
- :return:
- Returns ``True`` if ambiguous, ``False`` otherwise.
- .. versionadded:: 2.6.0
- """
- return False
- def __eq__(self, other):
- if not isinstance(other, tzoffset):
- return NotImplemented
- return self._offset == other._offset
- __hash__ = None
- def __ne__(self, other):
- return not (self == other)
- def __repr__(self):
- return "%s(%s, %s)" % (self.__class__.__name__,
- repr(self._name),
- int(self._offset.total_seconds()))
- __reduce__ = object.__reduce__
- class tzlocal(_tzinfo):
- """
- A :class:`tzinfo` subclass built around the ``time`` timezone functions.
- """
- def __init__(self):
- super(tzlocal, self).__init__()
- self._std_offset = datetime.timedelta(seconds=-time.timezone)
- if time.daylight:
- self._dst_offset = datetime.timedelta(seconds=-time.altzone)
- else:
- self._dst_offset = self._std_offset
- self._dst_saved = self._dst_offset - self._std_offset
- self._hasdst = bool(self._dst_saved)
- self._tznames = tuple(time.tzname)
- def utcoffset(self, dt):
- if dt is None and self._hasdst:
- return None
- if self._isdst(dt):
- return self._dst_offset
- else:
- return self._std_offset
- def dst(self, dt):
- if dt is None and self._hasdst:
- return None
- if self._isdst(dt):
- return self._dst_offset - self._std_offset
- else:
- return ZERO
- @tzname_in_python2
- def tzname(self, dt):
- return self._tznames[self._isdst(dt)]
- def is_ambiguous(self, dt):
- """
- Whether or not the "wall time" of a given datetime is ambiguous in this
- zone.
- :param dt:
- A :py:class:`datetime.datetime`, naive or time zone aware.
- :return:
- Returns ``True`` if ambiguous, ``False`` otherwise.
- .. versionadded:: 2.6.0
- """
- naive_dst = self._naive_is_dst(dt)
- return (not naive_dst and
- (naive_dst != self._naive_is_dst(dt - self._dst_saved)))
- def _naive_is_dst(self, dt):
- timestamp = _datetime_to_timestamp(dt)
- return time.localtime(timestamp + time.timezone).tm_isdst
- def _isdst(self, dt, fold_naive=True):
- # We can't use mktime here. It is unstable when deciding if
- # the hour near to a change is DST or not.
- #
- # timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour,
- # dt.minute, dt.second, dt.weekday(), 0, -1))
- # return time.localtime(timestamp).tm_isdst
- #
- # The code above yields the following result:
- #
- # >>> import tz, datetime
- # >>> t = tz.tzlocal()
- # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
- # 'BRDT'
- # >>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()
- # 'BRST'
- # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
- # 'BRST'
- # >>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()
- # 'BRDT'
- # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
- # 'BRDT'
- #
- # Here is a more stable implementation:
- #
- if not self._hasdst:
- return False
- # Check for ambiguous times:
- dstval = self._naive_is_dst(dt)
- fold = getattr(dt, 'fold', None)
- if self.is_ambiguous(dt):
- if fold is not None:
- return not self._fold(dt)
- else:
- return True
- return dstval
- def __eq__(self, other):
- if isinstance(other, tzlocal):
- return (self._std_offset == other._std_offset and
- self._dst_offset == other._dst_offset)
- elif isinstance(other, tzutc):
- return (not self._hasdst and
- self._tznames[0] in {'UTC', 'GMT'} and
- self._std_offset == ZERO)
- elif isinstance(other, tzoffset):
- return (not self._hasdst and
- self._tznames[0] == other._name and
- self._std_offset == other._offset)
- else:
- return NotImplemented
- __hash__ = None
- def __ne__(self, other):
- return not (self == other)
- def __repr__(self):
- return "%s()" % self.__class__.__name__
- __reduce__ = object.__reduce__
- class _ttinfo(object):
- __slots__ = ["offset", "delta", "isdst", "abbr",
- "isstd", "isgmt", "dstoffset"]
- def __init__(self):
- for attr in self.__slots__:
- setattr(self, attr, None)
- def __repr__(self):
- l = []
- for attr in self.__slots__:
- value = getattr(self, attr)
- if value is not None:
- l.append("%s=%s" % (attr, repr(value)))
- return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
- def __eq__(self, other):
- if not isinstance(other, _ttinfo):
- return NotImplemented
- return (self.offset == other.offset and
- self.delta == other.delta and
- self.isdst == other.isdst and
- self.abbr == other.abbr and
- self.isstd == other.isstd and
- self.isgmt == other.isgmt and
- self.dstoffset == other.dstoffset)
- __hash__ = None
- def __ne__(self, other):
- return not (self == other)
- def __getstate__(self):
- state = {}
- for name in self.__slots__:
- state[name] = getattr(self, name, None)
- return state
- def __setstate__(self, state):
- for name in self.__slots__:
- if name in state:
- setattr(self, name, state[name])
- class _tzfile(object):
- """
- Lightweight class for holding the relevant transition and time zone
- information read from binary tzfiles.
- """
- attrs = ['trans_list', 'trans_list_utc', 'trans_idx', 'ttinfo_list',
- 'ttinfo_std', 'ttinfo_dst', 'ttinfo_before', 'ttinfo_first']
- def __init__(self, **kwargs):
- for attr in self.attrs:
- setattr(self, attr, kwargs.get(attr, None))
- class tzfile(_tzinfo):
- """
- This is a ``tzinfo`` subclass that allows one to use the ``tzfile(5)``
- format timezone files to extract current and historical zone information.
- :param fileobj:
- This can be an opened file stream or a file name that the time zone
- information can be read from.
- :param filename:
- This is an optional parameter specifying the source of the time zone
- information in the event that ``fileobj`` is a file object. If omitted
- and ``fileobj`` is a file stream, this parameter will be set either to
- ``fileobj``'s ``name`` attribute or to ``repr(fileobj)``.
- See `Sources for Time Zone and Daylight Saving Time Data
- <https://data.iana.org/time-zones/tz-link.html>`_ for more information.
- Time zone files can be compiled from the `IANA Time Zone database files
- <https://www.iana.org/time-zones>`_ with the `zic time zone compiler
- <https://www.freebsd.org/cgi/man.cgi?query=zic&sektion=8>`_
- .. note::
- Only construct a ``tzfile`` directly if you have a specific timezone
- file on disk that you want to read into a Python ``tzinfo`` object.
- If you want to get a ``tzfile`` representing a specific IANA zone,
- (e.g. ``'America/New_York'``), you should call
- :func:`dateutil.tz.gettz` with the zone identifier.
- **Examples:**
- Using the US Eastern time zone as an example, we can see that a ``tzfile``
- provides time zone information for the standard Daylight Saving offsets:
- .. testsetup:: tzfile
- from dateutil.tz import gettz
- from datetime import datetime
- .. doctest:: tzfile
- >>> NYC = gettz('America/New_York')
- >>> NYC
- tzfile('/usr/share/zoneinfo/America/New_York')
- >>> print(datetime(2016, 1, 3, tzinfo=NYC)) # EST
- 2016-01-03 00:00:00-05:00
- >>> print(datetime(2016, 7, 7, tzinfo=NYC)) # EDT
- 2016-07-07 00:00:00-04:00
- The ``tzfile`` structure contains a fully history of the time zone,
- so historical dates will also have the right offsets. For example, before
- the adoption of the UTC standards, New York used local solar mean time:
- .. doctest:: tzfile
- >>> print(datetime(1901, 4, 12, tzinfo=NYC)) # LMT
- 1901-04-12 00:00:00-04:56
- And during World War II, New York was on "Eastern War Time", which was a
- state of permanent daylight saving time:
- .. doctest:: tzfile
- >>> print(datetime(1944, 2, 7, tzinfo=NYC)) # EWT
- 1944-02-07 00:00:00-04:00
- """
- def __init__(self, fileobj, filename=None):
- super(tzfile, self).__init__()
- file_opened_here = False
- if isinstance(fileobj, string_types):
- self._filename = fileobj
- fileobj = open(fileobj, 'rb')
- file_opened_here = True
- elif filename is not None:
- self._filename = filename
- elif hasattr(fileobj, "name"):
- self._filename = fileobj.name
- else:
- self._filename = repr(fileobj)
- if fileobj is not None:
- if not file_opened_here:
- fileobj = _nullcontext(fileobj)
- with fileobj as file_stream:
- tzobj = self._read_tzfile(file_stream)
- self._set_tzdata(tzobj)
- def _set_tzdata(self, tzobj):
- """ Set the time zone data of this object from a _tzfile object """
- # Copy the relevant attributes over as private attributes
- for attr in _tzfile.attrs:
- setattr(self, '_' + attr, getattr(tzobj, attr))
- def _read_tzfile(self, fileobj):
- out = _tzfile()
- # From tzfile(5):
- #
- # The time zone information files used by tzset(3)
- # begin with the magic characters "TZif" to identify
- # them as time zone information files, followed by
- # sixteen bytes reserved for future use, followed by
- # six four-byte values of type long, written in a
- # ``standard'' byte order (the high-order byte
- # of the value is written first).
- if fileobj.read(4).decode() != "TZif":
- raise ValueError("magic not found")
- fileobj.read(16)
- (
- # The number of UTC/local indicators stored in the file.
- ttisgmtcnt,
- # The number of standard/wall indicators stored in the file.
- ttisstdcnt,
- # The number of leap seconds for which data is
- # stored in the file.
- leapcnt,
- # The number of "transition times" for which data
- # is stored in the file.
- timecnt,
- # The number of "local time types" for which data
- # is stored in the file (must not be zero).
- typecnt,
- # The number of characters of "time zone
- # abbreviation strings" stored in the file.
- charcnt,
- ) = struct.unpack(">6l", fileobj.read(24))
- # The above header is followed by tzh_timecnt four-byte
- # values of type long, sorted in ascending order.
- # These values are written in ``standard'' byte order.
- # Each is used as a transition time (as returned by
- # time(2)) at which the rules for computing local time
- # change.
- if timecnt:
- out.trans_list_utc = list(struct.unpack(">%dl" % timecnt,
- fileobj.read(timecnt*4)))
- else:
- out.trans_list_utc = []
- # Next come tzh_timecnt one-byte values of type unsigned
- # char; each one tells which of the different types of
- # ``local time'' types described in the file is associated
- # with the same-indexed transition time. These values
- # serve as indices into an array of ttinfo structures that
- # appears next in the file.
- if timecnt:
- out.trans_idx = struct.unpack(">%dB" % timecnt,
- fileobj.read(timecnt))
- else:
- out.trans_idx = []
- # Each ttinfo structure is written as a four-byte value
- # for tt_gmtoff of type long, in a standard byte
- # order, followed by a one-byte value for tt_isdst
- # and a one-byte value for tt_abbrind. In each
- # structure, tt_gmtoff gives the number of
- # seconds to be added to UTC, tt_isdst tells whether
- # tm_isdst should be set by localtime(3), and
- # tt_abbrind serves as an index into the array of
- # time zone abbreviation characters that follow the
- # ttinfo structure(s) in the file.
- ttinfo = []
- for i in range(typecnt):
- ttinfo.append(struct.unpack(">lbb", fileobj.read(6)))
- abbr = fileobj.read(charcnt).decode()
- # Then there are tzh_leapcnt pairs of four-byte
- # values, written in standard byte order; the
- # first value of each pair gives the time (as
- # returned by time(2)) at which a leap second
- # occurs; the second gives the total number of
- # leap seconds to be applied after the given time.
- # The pairs of values are sorted in ascending order
- # by time.
- # Not used, for now (but seek for correct file position)
- if leapcnt:
- fileobj.seek(leapcnt * 8, os.SEEK_CUR)
- # Then there are tzh_ttisstdcnt standard/wall
- # indicators, each stored as a one-byte value;
- # they tell whether the transition times associated
- # with local time types were specified as standard
- # time or wall clock time, and are used when
- # a time zone file is used in handling POSIX-style
- # time zone environment variables.
- if ttisstdcnt:
- isstd = struct.unpack(">%db" % ttisstdcnt,
- fileobj.read(ttisstdcnt))
- # Finally, there are tzh_ttisgmtcnt UTC/local
- # indicators, each stored as a one-byte value;
- # they tell whether the transition times associated
- # with local time types were specified as UTC or
- # local time, and are used when a time zone file
- # is used in handling POSIX-style time zone envi-
- # ronment variables.
- if ttisgmtcnt:
- isgmt = struct.unpack(">%db" % ttisgmtcnt,
- fileobj.read(ttisgmtcnt))
- # Build ttinfo list
- out.ttinfo_list = []
- for i in range(typecnt):
- gmtoff, isdst, abbrind = ttinfo[i]
- gmtoff = _get_supported_offset(gmtoff)
- tti = _ttinfo()
- tti.offset = gmtoff
- tti.dstoffset = datetime.timedelta(0)
- tti.delta = datetime.timedelta(seconds=gmtoff)
- tti.isdst = isdst
- tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)]
- tti.isstd = (ttisstdcnt > i and isstd[i] != 0)
- tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0)
- out.ttinfo_list.append(tti)
- # Replace ttinfo indexes for ttinfo objects.
- out.trans_idx = [out.ttinfo_list[idx] for idx in out.trans_idx]
- # Set standard, dst, and before ttinfos. before will be
- # used when a given time is before any transitions,
- # and will be set to the first non-dst ttinfo, or to
- # the first dst, if all of them are dst.
- out.ttinfo_std = None
- out.ttinfo_dst = None
- out.ttinfo_before = None
- if out.ttinfo_list:
- if not out.trans_list_utc:
- out.ttinfo_std = out.ttinfo_first = out.ttinfo_list[0]
- else:
- for i in range(timecnt-1, -1, -1):
- tti = out.trans_idx[i]
- if not out.ttinfo_std and not tti.isdst:
- out.ttinfo_std = tti
- elif not out.ttinfo_dst and tti.isdst:
- out.ttinfo_dst = tti
- if out.ttinfo_std and out.ttinfo_dst:
- break
- else:
- if out.ttinfo_dst and not out.ttinfo_std:
- out.ttinfo_std = out.ttinfo_dst
- for tti in out.ttinfo_list:
- if not tti.isdst:
- out.ttinfo_before = tti
- break
- else:
- out.ttinfo_before = out.ttinfo_list[0]
- # Now fix transition times to become relative to wall time.
- #
- # I'm not sure about this. In my tests, the tz source file
- # is setup to wall time, and in the binary file isstd and
- # isgmt are off, so it should be in wall time. OTOH, it's
- # always in gmt time. Let me know if you have comments
- # about this.
- lastdst = None
- lastoffset = None
- lastdstoffset = None
- lastbaseoffset = None
- out.trans_list = []
- for i, tti in enumerate(out.trans_idx):
- offset = tti.offset
- dstoffset = 0
- if lastdst is not None:
- if tti.isdst:
- if not lastdst:
- dstoffset = offset - lastoffset
- if not dstoffset and lastdstoffset:
- dstoffset = lastdstoffset
- tti.dstoffset = datetime.timedelta(seconds=dstoffset)
- lastdstoffset = dstoffset
- # If a time zone changes its base offset during a DST transition,
- # then you need to adjust by the previous base offset to get the
- # transition time in local time. Otherwise you use the current
- # base offset. Ideally, I would have some mathematical proof of
- # why this is true, but I haven't really thought about it enough.
- baseoffset = offset - dstoffset
- adjustment = baseoffset
- if (lastbaseoffset is not None and baseoffset != lastbaseoffset
- and tti.isdst != lastdst):
- # The base DST has changed
- adjustment = lastbaseoffset
- lastdst = tti.isdst
- lastoffset = offset
- lastbaseoffset = baseoffset
- out.trans_list.append(out.trans_list_utc[i] + adjustment)
- out.trans_idx = tuple(out.trans_idx)
- out.trans_list = tuple(out.trans_list)
- out.trans_list_utc = tuple(out.trans_list_utc)
- return out
- def _find_last_transition(self, dt, in_utc=False):
- # If there's no list, there are no transitions to find
- if not self._trans_list:
- return None
- timestamp = _datetime_to_timestamp(dt)
- # Find where the timestamp fits in the transition list - if the
- # timestamp is a transition time, it's part of the "after" period.
- trans_list = self._trans_list_utc if in_utc else self._trans_list
- idx = bisect.bisect_right(trans_list, timestamp)
- # We want to know when the previous transition was, so subtract off 1
- return idx - 1
- def _get_ttinfo(self, idx):
- # For no list or after the last transition, default to _ttinfo_std
- if idx is None or (idx + 1) >= len(self._trans_list):
- return self._ttinfo_std
- # If there is a list and the time is before it, return _ttinfo_before
- if idx < 0:
- return self._ttinfo_before
- return self._trans_idx[idx]
- def _find_ttinfo(self, dt):
- idx = self._resolve_ambiguous_time(dt)
- return self._get_ttinfo(idx)
- def fromutc(self, dt):
- """
- The ``tzfile`` implementation of :py:func:`datetime.tzinfo.fromutc`.
- :param dt:
- A :py:class:`datetime.datetime` object.
- :raises TypeError:
- Raised if ``dt`` is not a :py:class:`datetime.datetime` object.
- :raises ValueError:
- Raised if this is called with a ``dt`` which does not have this
- ``tzinfo`` attached.
- :return:
- Returns a :py:class:`datetime.datetime` object representing the
- wall time in ``self``'s time zone.
- """
- # These isinstance checks are in datetime.tzinfo, so we'll preserve
- # them, even if we don't care about duck typing.
- if not isinstance(dt, datetime.datetime):
- raise TypeError("fromutc() requires a datetime argument")
- if dt.tzinfo is not self:
- raise ValueError("dt.tzinfo is not self")
- # First treat UTC as wall time and get the transition we're in.
- idx = self._find_last_transition(dt, in_utc=True)
- tti = self._get_ttinfo(idx)
- dt_out = dt + datetime.timedelta(seconds=tti.offset)
- fold = self.is_ambiguous(dt_out, idx=idx)
- return enfold(dt_out, fold=int(fold))
- def is_ambiguous(self, dt, idx=None):
- """
- Whether or not the "wall time" of a given datetime is ambiguous in this
- zone.
- :param dt:
- A :py:class:`datetime.datetime`, naive or time zone aware.
- :return:
- Returns ``True`` if ambiguous, ``False`` otherwise.
- .. versionadded:: 2.6.0
- """
- if idx is None:
- idx = self._find_last_transition(dt)
- # Calculate the difference in offsets from current to previous
- timestamp = _datetime_to_timestamp(dt)
- tti = self._get_ttinfo(idx)
- if idx is None or idx <= 0:
- return False
- od = self._get_ttinfo(idx - 1).offset - tti.offset
- tt = self._trans_list[idx] # Transition time
- return timestamp < tt + od
- def _resolve_ambiguous_time(self, dt):
- idx = self._find_last_transition(dt)
- # If we have no transitions, return the index
- _fold = self._fold(dt)
- if idx is None or idx == 0:
- return idx
- # If it's ambiguous and we're in a fold, shift to a different index.
- idx_offset = int(not _fold and self.is_ambiguous(dt, idx))
- return idx - idx_offset
- def utcoffset(self, dt):
- if dt is None:
- return None
- if not self._ttinfo_std:
- return ZERO
- return self._find_ttinfo(dt).delta
- def dst(self, dt):
- if dt is None:
- return None
- if not self._ttinfo_dst:
- return ZERO
- tti = self._find_ttinfo(dt)
- if not tti.isdst:
- return ZERO
- # The documentation says that utcoffset()-dst() must
- # be constant for every dt.
- return tti.dstoffset
- @tzname_in_python2
- def tzname(self, dt):
- if not self._ttinfo_std or dt is None:
- return None
- return self._find_ttinfo(dt).abbr
- def __eq__(self, other):
- if not isinstance(other, tzfile):
- return NotImplemented
- return (self._trans_list == other._trans_list and
- self._trans_idx == other._trans_idx and
- self._ttinfo_list == other._ttinfo_list)
- __hash__ = None
- def __ne__(self, other):
- return not (self == other)
- def __repr__(self):
- return "%s(%s)" % (self.__class__.__name__, repr(self._filename))
- def __reduce__(self):
- return self.__reduce_ex__(None)
- def __reduce_ex__(self, protocol):
- return (self.__class__, (None, self._filename), self.__dict__)
- class tzrange(tzrangebase):
- """
- The ``tzrange`` object is a time zone specified by a set of offsets and
- abbreviations, equivalent to the way the ``TZ`` variable can be specified
- in POSIX-like systems, but using Python delta objects to specify DST
- start, end and offsets.
- :param stdabbr:
- The abbreviation for standard time (e.g. ``'EST'``).
- :param stdoffset:
- An integer or :class:`datetime.timedelta` object or equivalent
- specifying the base offset from UTC.
- If unspecified, +00:00 is used.
- :param dstabbr:
- The abbreviation for DST / "Summer" time (e.g. ``'EDT'``).
- If specified, with no other DST information, DST is assumed to occur
- and the default behavior or ``dstoffset``, ``start`` and ``end`` is
- used. If unspecified and no other DST information is specified, it
- is assumed that this zone has no DST.
- If this is unspecified and other DST information is *is* specified,
- DST occurs in the zone but the time zone abbreviation is left
- unchanged.
- :param dstoffset:
- A an integer or :class:`datetime.timedelta` object or equivalent
- specifying the UTC offset during DST. If unspecified and any other DST
- information is specified, it is assumed to be the STD offset +1 hour.
- :param start:
- A :class:`relativedelta.relativedelta` object or equivalent specifying
- the time and time of year that daylight savings time starts. To
- specify, for example, that DST starts at 2AM on the 2nd Sunday in
- March, pass:
- ``relativedelta(hours=2, month=3, day=1, weekday=SU(+2))``
- If unspecified and any other DST information is specified, the default
- value is 2 AM on the first Sunday in April.
- :param end:
- A :class:`relativedelta.relativedelta` object or equivalent
- representing the time and time of year that daylight savings time
- ends, with the same specification method as in ``start``. One note is
- that this should point to the first time in the *standard* zone, so if
- a transition occurs at 2AM in the DST zone and the clocks are set back
- 1 hour to 1AM, set the ``hours`` parameter to +1.
- **Examples:**
- .. testsetup:: tzrange
- from dateutil.tz import tzrange, tzstr
- .. doctest:: tzrange
- >>> tzstr('EST5EDT') == tzrange("EST", -18000, "EDT")
- True
- >>> from dateutil.relativedelta import *
- >>> range1 = tzrange("EST", -18000, "EDT")
- >>> range2 = tzrange("EST", -18000, "EDT", -14400,
- ... relativedelta(hours=+2, month=4, day=1,
- ... weekday=SU(+1)),
- ... relativedelta(hours=+1, month=10, day=31,
- ... weekday=SU(-1)))
- >>> tzstr('EST5EDT') == range1 == range2
- True
- """
- def __init__(self, stdabbr, stdoffset=None,
- dstabbr=None, dstoffset=None,
- start=None, end=None):
- global relativedelta
- from dateutil import relativedelta
- self._std_abbr = stdabbr
- self._dst_abbr = dstabbr
- try:
- stdoffset = stdoffset.total_seconds()
- except (TypeError, AttributeError):
- pass
- try:
- dstoffset = dstoffset.total_seconds()
- except (TypeError, AttributeError):
- pass
- if stdoffset is not None:
- self._std_offset = datetime.timedelta(seconds=stdoffset)
- else:
- self._std_offset = ZERO
- if dstoffset is not None:
- self._dst_offset = datetime.timedelta(seconds=dstoffset)
- elif dstabbr and stdoffset is not None:
- self._dst_offset = self._std_offset + datetime.timedelta(hours=+1)
- else:
- self._dst_offset = ZERO
- if dstabbr and start is None:
- self._start_delta = relativedelta.relativedelta(
- hours=+2, month=4, day=1, weekday=relativedelta.SU(+1))
- else:
- self._start_delta = start
- if dstabbr and end is None:
- self._end_delta = relativedelta.relativedelta(
- hours=+1, month=10, day=31, weekday=relativedelta.SU(-1))
- else:
- self._end_delta = end
- self._dst_base_offset_ = self._dst_offset - self._std_offset
- self.hasdst = bool(self._start_delta)
- def transitions(self, year):
- """
- For a given year, get the DST on and off transition times, expressed
- always on the standard time side. For zones with no transitions, this
- function returns ``None``.
- :param year:
- The year whose transitions you would like to query.
- :return:
- Returns a :class:`tuple` of :class:`datetime.datetime` objects,
- ``(dston, dstoff)`` for zones with an annual DST transition, or
- ``None`` for fixed offset zones.
- """
- if not self.hasdst:
- return None
- base_year = datetime.datetime(year, 1, 1)
- start = base_year + self._start_delta
- end = base_year + self._end_delta
- return (start, end)
- def __eq__(self, other):
- if not isinstance(other, tzrange):
- return NotImplemented
- return (self._std_abbr == other._std_abbr and
- self._dst_abbr == other._dst_abbr and
- self._std_offset == other._std_offset and
- self._dst_offset == other._dst_offset and
- self._start_delta == other._start_delta and
- self._end_delta == other._end_delta)
- @property
- def _dst_base_offset(self):
- return self._dst_base_offset_
- @six.add_metaclass(_TzStrFactory)
- class tzstr(tzrange):
- """
- ``tzstr`` objects are time zone objects specified by a time-zone string as
- it would be passed to a ``TZ`` variable on POSIX-style systems (see
- the `GNU C Library: TZ Variable`_ for more details).
- There is one notable exception, which is that POSIX-style time zones use an
- inverted offset format, so normally ``GMT+3`` would be parsed as an offset
- 3 hours *behind* GMT. The ``tzstr`` time zone object will parse this as an
- offset 3 hours *ahead* of GMT. If you would like to maintain the POSIX
- behavior, pass a ``True`` value to ``posix_offset``.
- The :class:`tzrange` object provides the same functionality, but is
- specified using :class:`relativedelta.relativedelta` objects. rather than
- strings.
- :param s:
- A time zone string in ``TZ`` variable format. This can be a
- :class:`bytes` (2.x: :class:`str`), :class:`str` (2.x:
- :class:`unicode`) or a stream emitting unicode characters
- (e.g. :class:`StringIO`).
- :param posix_offset:
- Optional. If set to ``True``, interpret strings such as ``GMT+3`` or
- ``UTC+3`` as being 3 hours *behind* UTC rather than ahead, per the
- POSIX standard.
- .. caution::
- Prior to version 2.7.0, this function also supported time zones
- in the format:
- * ``EST5EDT,4,0,6,7200,10,0,26,7200,3600``
- * ``EST5EDT,4,1,0,7200,10,-1,0,7200,3600``
- This format is non-standard and has been deprecated; this function
- will raise a :class:`DeprecatedTZFormatWarning` until
- support is removed in a future version.
- .. _`GNU C Library: TZ Variable`:
- https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
- """
- def __init__(self, s, posix_offset=False):
- global parser
- from dateutil.parser import _parser as parser
- self._s = s
- res = parser._parsetz(s)
- if res is None or res.any_unused_tokens:
- raise ValueError("unknown string format")
- # Here we break the compatibility with the TZ variable handling.
- # GMT-3 actually *means* the timezone -3.
- if res.stdabbr in ("GMT", "UTC") and not posix_offset:
- res.stdoffset *= -1
- # We must initialize it first, since _delta() needs
- # _std_offset and _dst_offset set. Use False in start/end
- # to avoid building it two times.
- tzrange.__init__(self, res.stdabbr, res.stdoffset,
- res.dstabbr, res.dstoffset,
- start=False, end=False)
- if not res.dstabbr:
- self._start_delta = None
- self._end_delta = None
- else:
- self._start_delta = self._delta(res.start)
- if self._start_delta:
- self._end_delta = self._delta(res.end, isend=1)
- self.hasdst = bool(self._start_delta)
- def _delta(self, x, isend=0):
- from dateutil import relativedelta
- kwargs = {}
- if x.month is not None:
- kwargs["month"] = x.month
- if x.weekday is not None:
- kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week)
- if x.week > 0:
- kwargs["day"] = 1
- else:
- kwargs["day"] = 31
- elif x.day:
- kwargs["day"] = x.day
- elif x.yday is not None:
- kwargs["yearday"] = x.yday
- elif x.jyday is not None:
- kwargs["nlyearday"] = x.jyday
- if not kwargs:
- # Default is to start on first sunday of april, and end
- # on last sunday of october.
- if not isend:
- kwargs["month"] = 4
- kwargs["day"] = 1
- kwargs["weekday"] = relativedelta.SU(+1)
- else:
- kwargs["month"] = 10
- kwargs["day"] = 31
- kwargs["weekday"] = relativedelta.SU(-1)
- if x.time is not None:
- kwargs["seconds"] = x.time
- else:
- # Default is 2AM.
- kwargs["seconds"] = 7200
- if isend:
- # Convert to standard time, to follow the documented way
- # of working with the extra hour. See the documentation
- # of the tzinfo class.
- delta = self._dst_offset - self._std_offset
- kwargs["seconds"] -= delta.seconds + delta.days * 86400
- return relativedelta.relativedelta(**kwargs)
- def __repr__(self):
- return "%s(%s)" % (self.__class__.__name__, repr(self._s))
- class _tzicalvtzcomp(object):
- def __init__(self, tzoffsetfrom, tzoffsetto, isdst,
- tzname=None, rrule=None):
- self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom)
- self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto)
- self.tzoffsetdiff = self.tzoffsetto - self.tzoffsetfrom
- self.isdst = isdst
- self.tzname = tzname
- self.rrule = rrule
- class _tzicalvtz(_tzinfo):
- def __init__(self, tzid, comps=[]):
- super(_tzicalvtz, self).__init__()
- self._tzid = tzid
- self._comps = comps
- self._cachedate = []
- self._cachecomp = []
- self._cache_lock = _thread.allocate_lock()
- def _find_comp(self, dt):
- if len(self._comps) == 1:
- return self._comps[0]
- dt = dt.replace(tzinfo=None)
- try:
- with self._cache_lock:
- return self._cachecomp[self._cachedate.index(
- (dt, self._fold(dt)))]
- except ValueError:
- pass
- lastcompdt = None
- lastcomp = None
- for comp in self._comps:
- compdt = self._find_compdt(comp, dt)
- if compdt and (not lastcompdt or lastcompdt < compdt):
- lastcompdt = compdt
- lastcomp = comp
- if not lastcomp:
- # RFC says nothing about what to do when a given
- # time is before the first onset date. We'll look for the
- # first standard component, or the first component, if
- # none is found.
- for comp in self._comps:
- if not comp.isdst:
- lastcomp = comp
- break
- else:
- lastcomp = comp[0]
- with self._cache_lock:
- self._cachedate.insert(0, (dt, self._fold(dt)))
- self._cachecomp.insert(0, lastcomp)
- if len(self._cachedate) > 10:
- self._cachedate.pop()
- self._cachecomp.pop()
- return lastcomp
- def _find_compdt(self, comp, dt):
- if comp.tzoffsetdiff < ZERO and self._fold(dt):
- dt -= comp.tzoffsetdiff
- compdt = comp.rrule.before(dt, inc=True)
- return compdt
- def utcoffset(self, dt):
- if dt is None:
- return None
- return self._find_comp(dt).tzoffsetto
- def dst(self, dt):
- comp = self._find_comp(dt)
- if comp.isdst:
- return comp.tzoffsetdiff
- else:
- return ZERO
- @tzname_in_python2
- def tzname(self, dt):
- return self._find_comp(dt).tzname
- def __repr__(self):
- return "<tzicalvtz %s>" % repr(self._tzid)
- __reduce__ = object.__reduce__
- class tzical(object):
- """
- This object is designed to parse an iCalendar-style ``VTIMEZONE`` structure
- as set out in `RFC 5545`_ Section 4.6.5 into one or more `tzinfo` objects.
- :param `fileobj`:
- A file or stream in iCalendar format, which should be UTF-8 encoded
- with CRLF endings.
- .. _`RFC 5545`: https://tools.ietf.org/html/rfc5545
- """
- def __init__(self, fileobj):
- global rrule
- from dateutil import rrule
- if isinstance(fileobj, string_types):
- self._s = fileobj
- # ical should be encoded in UTF-8 with CRLF
- fileobj = open(fileobj, 'r')
- else:
- self._s = getattr(fileobj, 'name', repr(fileobj))
- fileobj = _nullcontext(fileobj)
- self._vtz = {}
- with fileobj as fobj:
- self._parse_rfc(fobj.read())
- def keys(self):
- """
- Retrieves the available time zones as a list.
- """
- return list(self._vtz.keys())
- def get(self, tzid=None):
- """
- Retrieve a :py:class:`datetime.tzinfo` object by its ``tzid``.
- :param tzid:
- If there is exactly one time zone available, omitting ``tzid``
- or passing :py:const:`None` value returns it. Otherwise a valid
- key (which can be retrieved from :func:`keys`) is required.
- :raises ValueError:
- Raised if ``tzid`` is not specified but there are either more
- or fewer than 1 zone defined.
- :returns:
- Returns either a :py:class:`datetime.tzinfo` object representing
- the relevant time zone or :py:const:`None` if the ``tzid`` was
- not found.
- """
- if tzid is None:
- if len(self._vtz) == 0:
- raise ValueError("no timezones defined")
- elif len(self._vtz) > 1:
- raise ValueError("more than one timezone available")
- tzid = next(iter(self._vtz))
- return self._vtz.get(tzid)
- def _parse_offset(self, s):
- s = s.strip()
- if not s:
- raise ValueError("empty offset")
- if s[0] in ('+', '-'):
- signal = (-1, +1)[s[0] == '+']
- s = s[1:]
- else:
- signal = +1
- if len(s) == 4:
- return (int(s[:2]) * 3600 + int(s[2:]) * 60) * signal
- elif len(s) == 6:
- return (int(s[:2]) * 3600 + int(s[2:4]) * 60 + int(s[4:])) * signal
- else:
- raise ValueError("invalid offset: " + s)
- def _parse_rfc(self, s):
- lines = s.splitlines()
- if not lines:
- raise ValueError("empty string")
- # Unfold
- i = 0
- while i < len(lines):
- line = lines[i].rstrip()
- if not line:
- del lines[i]
- elif i > 0 and line[0] == " ":
- lines[i-1] += line[1:]
- del lines[i]
- else:
- i += 1
- tzid = None
- comps = []
- invtz = False
- comptype = None
- for line in lines:
- if not line:
- continue
- name, value = line.split(':', 1)
- parms = name.split(';')
- if not parms:
- raise ValueError("empty property name")
- name = parms[0].upper()
- parms = parms[1:]
- if invtz:
- if name == "BEGIN":
- if value in ("STANDARD", "DAYLIGHT"):
- # Process component
- pass
- else:
- raise ValueError("unknown component: "+value)
- comptype = value
- founddtstart = False
- tzoffsetfrom = None
- tzoffsetto = None
- rrulelines = []
- tzname = None
- elif name == "END":
- if value == "VTIMEZONE":
- if comptype:
- raise ValueError("component not closed: "+comptype)
- if not tzid:
- raise ValueError("mandatory TZID not found")
- if not comps:
- raise ValueError(
- "at least one component is needed")
- # Process vtimezone
- self._vtz[tzid] = _tzicalvtz(tzid, comps)
- invtz = False
- elif value == comptype:
- if not founddtstart:
- raise ValueError("mandatory DTSTART not found")
- if tzoffsetfrom is None:
- raise ValueError(
- "mandatory TZOFFSETFROM not found")
- if tzoffsetto is None:
- raise ValueError(
- "mandatory TZOFFSETFROM not found")
- # Process component
- rr = None
- if rrulelines:
- rr = rrule.rrulestr("\n".join(rrulelines),
- compatible=True,
- ignoretz=True,
- cache=True)
- comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto,
- (comptype == "DAYLIGHT"),
- tzname, rr)
- comps.append(comp)
- comptype = None
- else:
- raise ValueError("invalid component end: "+value)
- elif comptype:
- if name == "DTSTART":
- # DTSTART in VTIMEZONE takes a subset of valid RRULE
- # values under RFC 5545.
- for parm in parms:
- if parm != 'VALUE=DATE-TIME':
- msg = ('Unsupported DTSTART param in ' +
- 'VTIMEZONE: ' + parm)
- raise ValueError(msg)
- rrulelines.append(line)
- founddtstart = True
- elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"):
- rrulelines.append(line)
- elif name == "TZOFFSETFROM":
- if parms:
- raise ValueError(
- "unsupported %s parm: %s " % (name, parms[0]))
- tzoffsetfrom = self._parse_offset(value)
- elif name == "TZOFFSETTO":
- if parms:
- raise ValueError(
- "unsupported TZOFFSETTO parm: "+parms[0])
- tzoffsetto = self._parse_offset(value)
- elif name == "TZNAME":
- if parms:
- raise ValueError(
- "unsupported TZNAME parm: "+parms[0])
- tzname = value
- elif name == "COMMENT":
- pass
- else:
- raise ValueError("unsupported property: "+name)
- else:
- if name == "TZID":
- if parms:
- raise ValueError(
- "unsupported TZID parm: "+parms[0])
- tzid = value
- elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"):
- pass
- else:
- raise ValueError("unsupported property: "+name)
- elif name == "BEGIN" and value == "VTIMEZONE":
- tzid = None
- comps = []
- invtz = True
- def __repr__(self):
- return "%s(%s)" % (self.__class__.__name__, repr(self._s))
- if sys.platform != "win32":
- TZFILES = ["/etc/localtime", "localtime"]
- TZPATHS = ["/usr/share/zoneinfo",
- "/usr/lib/zoneinfo",
- "/usr/share/lib/zoneinfo",
- "/etc/zoneinfo"]
- else:
- TZFILES = []
- TZPATHS = []
- def __get_gettz():
- tzlocal_classes = (tzlocal,)
- if tzwinlocal is not None:
- tzlocal_classes += (tzwinlocal,)
- class GettzFunc(object):
- """
- Retrieve a time zone object from a string representation
- This function is intended to retrieve the :py:class:`tzinfo` subclass
- that best represents the time zone that would be used if a POSIX
- `TZ variable`_ were set to the same value.
- If no argument or an empty string is passed to ``gettz``, local time
- is returned:
- .. code-block:: python3
- >>> gettz()
- tzfile('/etc/localtime')
- This function is also the preferred way to map IANA tz database keys
- to :class:`tzfile` objects:
- .. code-block:: python3
- >>> gettz('Pacific/Kiritimati')
- tzfile('/usr/share/zoneinfo/Pacific/Kiritimati')
- On Windows, the standard is extended to include the Windows-specific
- zone names provided by the operating system:
- .. code-block:: python3
- >>> gettz('Egypt Standard Time')
- tzwin('Egypt Standard Time')
- Passing a GNU ``TZ`` style string time zone specification returns a
- :class:`tzstr` object:
- .. code-block:: python3
- >>> gettz('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3')
- tzstr('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3')
- :param name:
- A time zone name (IANA, or, on Windows, Windows keys), location of
- a ``tzfile(5)`` zoneinfo file or ``TZ`` variable style time zone
- specifier. An empty string, no argument or ``None`` is interpreted
- as local time.
- :return:
- Returns an instance of one of ``dateutil``'s :py:class:`tzinfo`
- subclasses.
- .. versionchanged:: 2.7.0
- After version 2.7.0, any two calls to ``gettz`` using the same
- input strings will return the same object:
- .. code-block:: python3
- >>> tz.gettz('America/Chicago') is tz.gettz('America/Chicago')
- True
- In addition to improving performance, this ensures that
- `"same zone" semantics`_ are used for datetimes in the same zone.
- .. _`TZ variable`:
- https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
- .. _`"same zone" semantics`:
- https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html
- """
- def __init__(self):
- self.__instances = weakref.WeakValueDictionary()
- self.__strong_cache_size = 8
- self.__strong_cache = OrderedDict()
- self._cache_lock = _thread.allocate_lock()
- def __call__(self, name=None):
- with self._cache_lock:
- rv = self.__instances.get(name, None)
- if rv is None:
- rv = self.nocache(name=name)
- if not (name is None
- or isinstance(rv, tzlocal_classes)
- or rv is None):
- # tzlocal is slightly more complicated than the other
- # time zone providers because it depends on environment
- # at construction time, so don't cache that.
- #
- # We also cannot store weak references to None, so we
- # will also not store that.
- self.__instances[name] = rv
- else:
- # No need for strong caching, return immediately
- return rv
- self.__strong_cache[name] = self.__strong_cache.pop(name, rv)
- if len(self.__strong_cache) > self.__strong_cache_size:
- self.__strong_cache.popitem(last=False)
- return rv
- def set_cache_size(self, size):
- with self._cache_lock:
- self.__strong_cache_size = size
- while len(self.__strong_cache) > size:
- self.__strong_cache.popitem(last=False)
- def cache_clear(self):
- with self._cache_lock:
- self.__instances = weakref.WeakValueDictionary()
- self.__strong_cache.clear()
- @staticmethod
- def nocache(name=None):
- """A non-cached version of gettz"""
- tz = None
- if not name:
- try:
- name = os.environ["TZ"]
- except KeyError:
- pass
- if name is None or name in ("", ":"):
- for filepath in TZFILES:
- if not os.path.isabs(filepath):
- filename = filepath
- for path in TZPATHS:
- filepath = os.path.join(path, filename)
- if os.path.isfile(filepath):
- break
- else:
- continue
- if os.path.isfile(filepath):
- try:
- tz = tzfile(filepath)
- break
- except (IOError, OSError, ValueError):
- pass
- else:
- tz = tzlocal()
- else:
- try:
- if name.startswith(":"):
- name = name[1:]
- except TypeError as e:
- if isinstance(name, bytes):
- new_msg = "gettz argument should be str, not bytes"
- six.raise_from(TypeError(new_msg), e)
- else:
- raise
- if os.path.isabs(name):
- if os.path.isfile(name):
- tz = tzfile(name)
- else:
- tz = None
- else:
- for path in TZPATHS:
- filepath = os.path.join(path, name)
- if not os.path.isfile(filepath):
- filepath = filepath.replace(' ', '_')
- if not os.path.isfile(filepath):
- continue
- try:
- tz = tzfile(filepath)
- break
- except (IOError, OSError, ValueError):
- pass
- else:
- tz = None
- if tzwin is not None:
- try:
- tz = tzwin(name)
- except (WindowsError, UnicodeEncodeError):
- # UnicodeEncodeError is for Python 2.7 compat
- tz = None
- if not tz:
- from dateutil.zoneinfo import get_zonefile_instance
- tz = get_zonefile_instance().get(name)
- if not tz:
- for c in name:
- # name is not a tzstr unless it has at least
- # one offset. For short values of "name", an
- # explicit for loop seems to be the fastest way
- # To determine if a string contains a digit
- if c in "0123456789":
- try:
- tz = tzstr(name)
- except ValueError:
- pass
- break
- else:
- if name in ("GMT", "UTC"):
- tz = UTC
- elif name in time.tzname:
- tz = tzlocal()
- return tz
- return GettzFunc()
- gettz = __get_gettz()
- del __get_gettz
- def datetime_exists(dt, tz=None):
- """
- Given a datetime and a time zone, determine whether or not a given datetime
- would fall in a gap.
- :param dt:
- A :class:`datetime.datetime` (whose time zone will be ignored if ``tz``
- is provided.)
- :param tz:
- A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If
- ``None`` or not provided, the datetime's own time zone will be used.
- :return:
- Returns a boolean value whether or not the "wall time" exists in
- ``tz``.
- .. versionadded:: 2.7.0
- """
- if tz is None:
- if dt.tzinfo is None:
- raise ValueError('Datetime is naive and no time zone provided.')
- tz = dt.tzinfo
- dt = dt.replace(tzinfo=None)
- # This is essentially a test of whether or not the datetime can survive
- # a round trip to UTC.
- dt_rt = dt.replace(tzinfo=tz).astimezone(UTC).astimezone(tz)
- dt_rt = dt_rt.replace(tzinfo=None)
- return dt == dt_rt
- def datetime_ambiguous(dt, tz=None):
- """
- Given a datetime and a time zone, determine whether or not a given datetime
- is ambiguous (i.e if there are two times differentiated only by their DST
- status).
- :param dt:
- A :class:`datetime.datetime` (whose time zone will be ignored if ``tz``
- is provided.)
- :param tz:
- A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If
- ``None`` or not provided, the datetime's own time zone will be used.
- :return:
- Returns a boolean value whether or not the "wall time" is ambiguous in
- ``tz``.
- .. versionadded:: 2.6.0
- """
- if tz is None:
- if dt.tzinfo is None:
- raise ValueError('Datetime is naive and no time zone provided.')
- tz = dt.tzinfo
- # If a time zone defines its own "is_ambiguous" function, we'll use that.
- is_ambiguous_fn = getattr(tz, 'is_ambiguous', None)
- if is_ambiguous_fn is not None:
- try:
- return tz.is_ambiguous(dt)
- except Exception:
- pass
- # If it doesn't come out and tell us it's ambiguous, we'll just check if
- # the fold attribute has any effect on this particular date and time.
- dt = dt.replace(tzinfo=tz)
- wall_0 = enfold(dt, fold=0)
- wall_1 = enfold(dt, fold=1)
- same_offset = wall_0.utcoffset() == wall_1.utcoffset()
- same_dst = wall_0.dst() == wall_1.dst()
- return not (same_offset and same_dst)
- def resolve_imaginary(dt):
- """
- Given a datetime that may be imaginary, return an existing datetime.
- This function assumes that an imaginary datetime represents what the
- wall time would be in a zone had the offset transition not occurred, so
- it will always fall forward by the transition's change in offset.
- .. doctest::
- >>> from dateutil import tz
- >>> from datetime import datetime
- >>> NYC = tz.gettz('America/New_York')
- >>> print(tz.resolve_imaginary(datetime(2017, 3, 12, 2, 30, tzinfo=NYC)))
- 2017-03-12 03:30:00-04:00
- >>> KIR = tz.gettz('Pacific/Kiritimati')
- >>> print(tz.resolve_imaginary(datetime(1995, 1, 1, 12, 30, tzinfo=KIR)))
- 1995-01-02 12:30:00+14:00
- As a note, :func:`datetime.astimezone` is guaranteed to produce a valid,
- existing datetime, so a round-trip to and from UTC is sufficient to get
- an extant datetime, however, this generally "falls back" to an earlier time
- rather than falling forward to the STD side (though no guarantees are made
- about this behavior).
- :param dt:
- A :class:`datetime.datetime` which may or may not exist.
- :return:
- Returns an existing :class:`datetime.datetime`. If ``dt`` was not
- imaginary, the datetime returned is guaranteed to be the same object
- passed to the function.
- .. versionadded:: 2.7.0
- """
- if dt.tzinfo is not None and not datetime_exists(dt):
- curr_offset = (dt + datetime.timedelta(hours=24)).utcoffset()
- old_offset = (dt - datetime.timedelta(hours=24)).utcoffset()
- dt += curr_offset - old_offset
- return dt
- def _datetime_to_timestamp(dt):
- """
- Convert a :class:`datetime.datetime` object to an epoch timestamp in
- seconds since January 1, 1970, ignoring the time zone.
- """
- return (dt.replace(tzinfo=None) - EPOCH).total_seconds()
- if sys.version_info >= (3, 6):
- def _get_supported_offset(second_offset):
- return second_offset
- else:
- def _get_supported_offset(second_offset):
- # For python pre-3.6, round to full-minutes if that's not the case.
- # Python's datetime doesn't accept sub-minute timezones. Check
- # http://python.org/sf/1447945 or https://bugs.python.org/issue5288
- # for some information.
- old_offset = second_offset
- calculated_offset = 60 * ((second_offset + 30) // 60)
- return calculated_offset
- try:
- # Python 3.7 feature
- from contextlib import nullcontext as _nullcontext
- except ImportError:
- class _nullcontext(object):
- """
- Class for wrapping contexts so that they are passed through in a
- with statement.
- """
- def __init__(self, context):
- self.context = context
- def __enter__(self):
- return self.context
- def __exit__(*args, **kwargs):
- pass
- # vim:ts=4:sw=4:et
|