Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tz.py 59KB

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