tz.py 61 KB

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