tz.py 61 KB

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