tz.py 49 KB

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