codekingpro/portable-devtools
114k
1# -*- coding: utf-8 -*-2"""3This module offers timezone implementations subclassing the abstract4:py:class:`datetime.tzinfo` type. There are classes to handle tzfile format5files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`,6etc), TZ environment string (in all known formats), given ranges (with help7from relative deltas), local machine timezone, fixed offset timezone, and UTC8timezone.9"""10import datetime11import struct12import time13import sys14import os15import bisect16import weakref17from collections import OrderedDict18 19import six20from six import string_types21from six.moves import _thread22from ._common import tzname_in_python2, _tzinfo23from ._common import tzrangebase, enfold24from ._common import _validate_fromutc_inputs25 26from ._factories import _TzSingleton, _TzOffsetFactory27from ._factories import _TzStrFactory28try:29 from .win import tzwin, tzwinlocal30except ImportError:31 tzwin = tzwinlocal = None32 33# For warning about rounding tzinfo34from warnings import warn35 36ZERO = datetime.timedelta(0)37EPOCH = datetime.datetime(1970, 1, 1, 0, 0)38EPOCHORDINAL = EPOCH.toordinal()39 40 41@six.add_metaclass(_TzSingleton)42class tzutc(datetime.tzinfo):43 """44 This is a tzinfo object that represents the UTC time zone.45 46 **Examples:**47 48 .. doctest::49 50 >>> from datetime import *51 >>> from dateutil.tz import *52 53 >>> datetime.now()54 datetime.datetime(2003, 9, 27, 9, 40, 1, 521290)55 56 >>> datetime.now(tzutc())57 datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc())58 59 >>> datetime.now(tzutc()).tzname()60 'UTC'61 62 .. versionchanged:: 2.7.063 ``tzutc()`` is now a singleton, so the result of ``tzutc()`` will64 always return the same object.65 66 .. doctest::67 68 >>> from dateutil.tz import tzutc, UTC69 >>> tzutc() is tzutc()70 True71 >>> tzutc() is UTC72 True73 """74 def utcoffset(self, dt):75 return ZERO76 77 def dst(self, dt):78 return ZERO79 80 @tzname_in_python281 def tzname(self, dt):82 return "UTC"83 84 def is_ambiguous(self, dt):85 """86 Whether or not the "wall time" of a given datetime is ambiguous in this87 zone.88 89 :param dt:90 A :py:class:`datetime.datetime`, naive or time zone aware.91 92 93 :return:94 Returns ``True`` if ambiguous, ``False`` otherwise.95 96 .. versionadded:: 2.6.097 """98 return False99 100 @_validate_fromutc_inputs101 def fromutc(self, dt):102 """103 Fast track version of fromutc() returns the original ``dt`` object for104 any valid :py:class:`datetime.datetime` object.105 """106 return dt107 108 def __eq__(self, other):109 if not isinstance(other, (tzutc, tzoffset)):110 return NotImplemented111 112 return (isinstance(other, tzutc) or113 (isinstance(other, tzoffset) and other._offset == ZERO))114 115 __hash__ = None116 117 def __ne__(self, other):118 return not (self == other)119 120 def __repr__(self):121 return "%s()" % self.__class__.__name__122 123 __reduce__ = object.__reduce__124 125 126#: Convenience constant providing a :class:`tzutc()` instance127#:128#: .. versionadded:: 2.7.0129UTC = tzutc()130 131 132@six.add_metaclass(_TzOffsetFactory)133class tzoffset(datetime.tzinfo):134 """135 A simple class for representing a fixed offset from UTC.136 137 :param name:138 The timezone name, to be returned when ``tzname()`` is called.139 :param offset:140 The time zone offset in seconds, or (since version 2.6.0, represented141 as a :py:class:`datetime.timedelta` object).142 """143 def __init__(self, name, offset):144 self._name = name145 146 try:147 # Allow a timedelta148 offset = offset.total_seconds()149 except (TypeError, AttributeError):150 pass151 152 self._offset = datetime.timedelta(seconds=_get_supported_offset(offset))153 154 def utcoffset(self, dt):155 return self._offset156 157 def dst(self, dt):158 return ZERO159 160 @tzname_in_python2161 def tzname(self, dt):162 return self._name163 164 @_validate_fromutc_inputs165 def fromutc(self, dt):166 return dt + self._offset167 168 def is_ambiguous(self, dt):169 """170 Whether or not the "wall time" of a given datetime is ambiguous in this171 zone.172 173 :param dt:174 A :py:class:`datetime.datetime`, naive or time zone aware.175 :return:176 Returns ``True`` if ambiguous, ``False`` otherwise.177 178 .. versionadded:: 2.6.0179 """180 return False181 182 def __eq__(self, other):183 if not isinstance(other, tzoffset):184 return NotImplemented185 186 return self._offset == other._offset187 188 __hash__ = None189 190 def __ne__(self, other):191 return not (self == other)192 193 def __repr__(self):194 return "%s(%s, %s)" % (self.__class__.__name__,195 repr(self._name),196 int(self._offset.total_seconds()))197 198 __reduce__ = object.__reduce__199 200 201class tzlocal(_tzinfo):202 """203 A :class:`tzinfo` subclass built around the ``time`` timezone functions.204 """205 def __init__(self):206 super(tzlocal, self).__init__()207 208 self._std_offset = datetime.timedelta(seconds=-time.timezone)209 if time.daylight:210 self._dst_offset = datetime.timedelta(seconds=-time.altzone)211 else:212 self._dst_offset = self._std_offset213 214 self._dst_saved = self._dst_offset - self._std_offset215 self._hasdst = bool(self._dst_saved)216 self._tznames = tuple(time.tzname)217 218 def utcoffset(self, dt):219 if dt is None and self._hasdst:220 return None221 222 if self._isdst(dt):223 return self._dst_offset224 else:225 return self._std_offset226 227 def dst(self, dt):228 if dt is None and self._hasdst:229 return None230 231 if self._isdst(dt):232 return self._dst_offset - self._std_offset233 else:234 return ZERO235 236 @tzname_in_python2237 def tzname(self, dt):238 return self._tznames[self._isdst(dt)]239 240 def is_ambiguous(self, dt):241 """242 Whether or not the "wall time" of a given datetime is ambiguous in this243 zone.244 245 :param dt:246 A :py:class:`datetime.datetime`, naive or time zone aware.247 248 249 :return:250 Returns ``True`` if ambiguous, ``False`` otherwise.251 252 .. versionadded:: 2.6.0253 """254 naive_dst = self._naive_is_dst(dt)255 return (not naive_dst and256 (naive_dst != self._naive_is_dst(dt - self._dst_saved)))257 258 def _naive_is_dst(self, dt):259 timestamp = _datetime_to_timestamp(dt)260 return time.localtime(timestamp + time.timezone).tm_isdst261 262 def _isdst(self, dt, fold_naive=True):263 # We can't use mktime here. It is unstable when deciding if264 # the hour near to a change is DST or not.265 #266 # timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour,267 # dt.minute, dt.second, dt.weekday(), 0, -1))268 # return time.localtime(timestamp).tm_isdst269 #270 # The code above yields the following result:271 #272 # >>> import tz, datetime273 # >>> t = tz.tzlocal()274 # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()275 # 'BRDT'276 # >>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()277 # 'BRST'278 # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()279 # 'BRST'280 # >>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()281 # 'BRDT'282 # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()283 # 'BRDT'284 #285 # Here is a more stable implementation:286 #287 if not self._hasdst:288 return False289 290 # Check for ambiguous times:291 dstval = self._naive_is_dst(dt)292 fold = getattr(dt, 'fold', None)293 294 if self.is_ambiguous(dt):295 if fold is not None:296 return not self._fold(dt)297 else:298 return True299 300 return dstval301 302 def __eq__(self, other):303 if isinstance(other, tzlocal):304 return (self._std_offset == other._std_offset and305 self._dst_offset == other._dst_offset)306 elif isinstance(other, tzutc):307 return (not self._hasdst and308 self._tznames[0] in {'UTC', 'GMT'} and309 self._std_offset == ZERO)310 elif isinstance(other, tzoffset):311 return (not self._hasdst and312 self._tznames[0] == other._name and313 self._std_offset == other._offset)314 else:315 return NotImplemented316 317 __hash__ = None318 319 def __ne__(self, other):320 return not (self == other)321 322 def __repr__(self):323 return "%s()" % self.__class__.__name__324 325 __reduce__ = object.__reduce__326 327 328class _ttinfo(object):329 __slots__ = ["offset", "delta", "isdst", "abbr",330 "isstd", "isgmt", "dstoffset"]331 332 def __init__(self):333 for attr in self.__slots__:334 setattr(self, attr, None)335 336 def __repr__(self):337 l = []338 for attr in self.__slots__:339 value = getattr(self, attr)340 if value is not None:341 l.append("%s=%s" % (attr, repr(value)))342 return "%s(%s)" % (self.__class__.__name__, ", ".join(l))343 344 def __eq__(self, other):345 if not isinstance(other, _ttinfo):346 return NotImplemented347 348 return (self.offset == other.offset and349 self.delta == other.delta and350 self.isdst == other.isdst and351 self.abbr == other.abbr and352 self.isstd == other.isstd and353 self.isgmt == other.isgmt and354 self.dstoffset == other.dstoffset)355 356 __hash__ = None357 358 def __ne__(self, other):359 return not (self == other)360 361 def __getstate__(self):362 state = {}363 for name in self.__slots__:364 state[name] = getattr(self, name, None)365 return state366 367 def __setstate__(self, state):368 for name in self.__slots__:369 if name in state:370 setattr(self, name, state[name])371 372 373class _tzfile(object):374 """375 Lightweight class for holding the relevant transition and time zone376 information read from binary tzfiles.377 """378 attrs = ['trans_list', 'trans_list_utc', 'trans_idx', 'ttinfo_list',379 'ttinfo_std', 'ttinfo_dst', 'ttinfo_before', 'ttinfo_first']380 381 def __init__(self, **kwargs):382 for attr in self.attrs:383 setattr(self, attr, kwargs.get(attr, None))384 385 386class tzfile(_tzinfo):387 """388 This is a ``tzinfo`` subclass that allows one to use the ``tzfile(5)``389 format timezone files to extract current and historical zone information.390 391 :param fileobj:392 This can be an opened file stream or a file name that the time zone393 information can be read from.394 395 :param filename:396 This is an optional parameter specifying the source of the time zone397 information in the event that ``fileobj`` is a file object. If omitted398 and ``fileobj`` is a file stream, this parameter will be set either to399 ``fileobj``'s ``name`` attribute or to ``repr(fileobj)``.400 401 See `Sources for Time Zone and Daylight Saving Time Data402 <https://data.iana.org/time-zones/tz-link.html>`_ for more information.403 Time zone files can be compiled from the `IANA Time Zone database files404 <https://www.iana.org/time-zones>`_ with the `zic time zone compiler405 <https://www.freebsd.org/cgi/man.cgi?query=zic&sektion=8>`_406 407 .. note::408 409 Only construct a ``tzfile`` directly if you have a specific timezone410 file on disk that you want to read into a Python ``tzinfo`` object.411 If you want to get a ``tzfile`` representing a specific IANA zone,412 (e.g. ``'America/New_York'``), you should call413 :func:`dateutil.tz.gettz` with the zone identifier.414 415 416 **Examples:**417 418 Using the US Eastern time zone as an example, we can see that a ``tzfile``419 provides time zone information for the standard Daylight Saving offsets:420 421 .. testsetup:: tzfile422 423 from dateutil.tz import gettz424 from datetime import datetime425 426 .. doctest:: tzfile427 428 >>> NYC = gettz('America/New_York')429 >>> NYC430 tzfile('/usr/share/zoneinfo/America/New_York')431 432 >>> print(datetime(2016, 1, 3, tzinfo=NYC)) # EST433 2016-01-03 00:00:00-05:00434 435 >>> print(datetime(2016, 7, 7, tzinfo=NYC)) # EDT436 2016-07-07 00:00:00-04:00437 438 439 The ``tzfile`` structure contains a fully history of the time zone,440 so historical dates will also have the right offsets. For example, before441 the adoption of the UTC standards, New York used local solar mean time:442 443 .. doctest:: tzfile444 445 >>> print(datetime(1901, 4, 12, tzinfo=NYC)) # LMT446 1901-04-12 00:00:00-04:56447 448 And during World War II, New York was on "Eastern War Time", which was a449 state of permanent daylight saving time:450 451 .. doctest:: tzfile452 453 >>> print(datetime(1944, 2, 7, tzinfo=NYC)) # EWT454 1944-02-07 00:00:00-04:00455 456 """457 458 def __init__(self, fileobj, filename=None):459 super(tzfile, self).__init__()460 461 file_opened_here = False462 if isinstance(fileobj, string_types):463 self._filename = fileobj464 fileobj = open(fileobj, 'rb')465 file_opened_here = True466 elif filename is not None:467 self._filename = filename468 elif hasattr(fileobj, "name"):469 self._filename = fileobj.name470 else:471 self._filename = repr(fileobj)472 473 if fileobj is not None:474 if not file_opened_here:475 fileobj = _nullcontext(fileobj)476 477 with fileobj as file_stream:478 tzobj = self._read_tzfile(file_stream)479 480 self._set_tzdata(tzobj)481 482 def _set_tzdata(self, tzobj):483 """ Set the time zone data of this object from a _tzfile object """484 # Copy the relevant attributes over as private attributes485 for attr in _tzfile.attrs:486 setattr(self, '_' + attr, getattr(tzobj, attr))487 488 def _read_tzfile(self, fileobj):489 out = _tzfile()490 491 # From tzfile(5):492 #493 # The time zone information files used by tzset(3)494 # begin with the magic characters "TZif" to identify495 # them as time zone information files, followed by496 # sixteen bytes reserved for future use, followed by497 # six four-byte values of type long, written in a498 # ``standard'' byte order (the high-order byte499 # of the value is written first).500 if fileobj.read(4).decode() != "TZif":501 raise ValueError("magic not found")502 503 fileobj.read(16)504 505 (506 # The number of UTC/local indicators stored in the file.507 ttisgmtcnt,508 509 # The number of standard/wall indicators stored in the file.510 ttisstdcnt,511 512 # The number of leap seconds for which data is513 # stored in the file.514 leapcnt,515 516 # The number of "transition times" for which data517 # is stored in the file.518 timecnt,519 520 # The number of "local time types" for which data521 # is stored in the file (must not be zero).522 typecnt,523 524 # The number of characters of "time zone525 # abbreviation strings" stored in the file.526 charcnt,527 528 ) = struct.unpack(">6l", fileobj.read(24))529 530 # The above header is followed by tzh_timecnt four-byte531 # values of type long, sorted in ascending order.532 # These values are written in ``standard'' byte order.533 # Each is used as a transition time (as returned by534 # time(2)) at which the rules for computing local time535 # change.536 537 if timecnt:538 out.trans_list_utc = list(struct.unpack(">%dl" % timecnt,539 fileobj.read(timecnt*4)))540 else:541 out.trans_list_utc = []542 543 # Next come tzh_timecnt one-byte values of type unsigned544 # char; each one tells which of the different types of545 # ``local time'' types described in the file is associated546 # with the same-indexed transition time. These values547 # serve as indices into an array of ttinfo structures that548 # appears next in the file.549 550 if timecnt:551 out.trans_idx = struct.unpack(">%dB" % timecnt,552 fileobj.read(timecnt))553 else:554 out.trans_idx = []555 556 # Each ttinfo structure is written as a four-byte value557 # for tt_gmtoff of type long, in a standard byte558 # order, followed by a one-byte value for tt_isdst559 # and a one-byte value for tt_abbrind. In each560 # structure, tt_gmtoff gives the number of561 # seconds to be added to UTC, tt_isdst tells whether562 # tm_isdst should be set by localtime(3), and563 # tt_abbrind serves as an index into the array of564 # time zone abbreviation characters that follow the565 # ttinfo structure(s) in the file.566 567 ttinfo = []568 569 for i in range(typecnt):570 ttinfo.append(struct.unpack(">lbb", fileobj.read(6)))571 572 abbr = fileobj.read(charcnt).decode()573 574 # Then there are tzh_leapcnt pairs of four-byte575 # values, written in standard byte order; the576 # first value of each pair gives the time (as577 # returned by time(2)) at which a leap second578 # occurs; the second gives the total number of579 # leap seconds to be applied after the given time.580 # The pairs of values are sorted in ascending order581 # by time.582 583 # Not used, for now (but seek for correct file position)584 if leapcnt:585 fileobj.seek(leapcnt * 8, os.SEEK_CUR)586 587 # Then there are tzh_ttisstdcnt standard/wall588 # indicators, each stored as a one-byte value;589 # they tell whether the transition times associated590 # with local time types were specified as standard591 # time or wall clock time, and are used when592 # a time zone file is used in handling POSIX-style593 # time zone environment variables.594 595 if ttisstdcnt:596 isstd = struct.unpack(">%db" % ttisstdcnt,597 fileobj.read(ttisstdcnt))598 599 # Finally, there are tzh_ttisgmtcnt UTC/local600 # indicators, each stored as a one-byte value;601 # they tell whether the transition times associated602 # with local time types were specified as UTC or603 # local time, and are used when a time zone file604 # is used in handling POSIX-style time zone envi-605 # ronment variables.606 607 if ttisgmtcnt:608 isgmt = struct.unpack(">%db" % ttisgmtcnt,609 fileobj.read(ttisgmtcnt))610 611 # Build ttinfo list612 out.ttinfo_list = []613 for i in range(typecnt):614 gmtoff, isdst, abbrind = ttinfo[i]615 gmtoff = _get_supported_offset(gmtoff)616 tti = _ttinfo()617 tti.offset = gmtoff618 tti.dstoffset = datetime.timedelta(0)619 tti.delta = datetime.timedelta(seconds=gmtoff)620 tti.isdst = isdst621 tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)]622 tti.isstd = (ttisstdcnt > i and isstd[i] != 0)623 tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0)624 out.ttinfo_list.append(tti)625 626 # Replace ttinfo indexes for ttinfo objects.627 out.trans_idx = [out.ttinfo_list[idx] for idx in out.trans_idx]628 629 # Set standard, dst, and before ttinfos. before will be630 # used when a given time is before any transitions,631 # and will be set to the first non-dst ttinfo, or to632 # the first dst, if all of them are dst.633 out.ttinfo_std = None634 out.ttinfo_dst = None635 out.ttinfo_before = None636 if out.ttinfo_list:637 if not out.trans_list_utc:638 out.ttinfo_std = out.ttinfo_first = out.ttinfo_list[0]639 else:640 for i in range(timecnt-1, -1, -1):641 tti = out.trans_idx[i]642 if not out.ttinfo_std and not tti.isdst:643 out.ttinfo_std = tti644 elif not out.ttinfo_dst and tti.isdst:645 out.ttinfo_dst = tti646 647 if out.ttinfo_std and out.ttinfo_dst:648 break649 else:650 if out.ttinfo_dst and not out.ttinfo_std:651 out.ttinfo_std = out.ttinfo_dst652 653 for tti in out.ttinfo_list:654 if not tti.isdst:655 out.ttinfo_before = tti656 break657 else:658 out.ttinfo_before = out.ttinfo_list[0]659 660 # Now fix transition times to become relative to wall time.661 #662 # I'm not sure about this. In my tests, the tz source file663 # is setup to wall time, and in the binary file isstd and664 # isgmt are off, so it should be in wall time. OTOH, it's665 # always in gmt time. Let me know if you have comments666 # about this.667 lastdst = None668 lastoffset = None669 lastdstoffset = None670 lastbaseoffset = None671 out.trans_list = []672 673 for i, tti in enumerate(out.trans_idx):674 offset = tti.offset675 dstoffset = 0676 677 if lastdst is not None:678 if tti.isdst:679 if not lastdst:680 dstoffset = offset - lastoffset681 682 if not dstoffset and lastdstoffset:683 dstoffset = lastdstoffset684 685 tti.dstoffset = datetime.timedelta(seconds=dstoffset)686 lastdstoffset = dstoffset687 688 # If a time zone changes its base offset during a DST transition,689 # then you need to adjust by the previous base offset to get the690 # transition time in local time. Otherwise you use the current691 # base offset. Ideally, I would have some mathematical proof of692 # why this is true, but I haven't really thought about it enough.693 baseoffset = offset - dstoffset694 adjustment = baseoffset695 if (lastbaseoffset is not None and baseoffset != lastbaseoffset696 and tti.isdst != lastdst):697 # The base DST has changed698 adjustment = lastbaseoffset699 700 lastdst = tti.isdst701 lastoffset = offset702 lastbaseoffset = baseoffset703 704 out.trans_list.append(out.trans_list_utc[i] + adjustment)705 706 out.trans_idx = tuple(out.trans_idx)707 out.trans_list = tuple(out.trans_list)708 out.trans_list_utc = tuple(out.trans_list_utc)709 710 return out711 712 def _find_last_transition(self, dt, in_utc=False):713 # If there's no list, there are no transitions to find714 if not self._trans_list:715 return None716 717 timestamp = _datetime_to_timestamp(dt)718 719 # Find where the timestamp fits in the transition list - if the720 # timestamp is a transition time, it's part of the "after" period.721 trans_list = self._trans_list_utc if in_utc else self._trans_list722 idx = bisect.bisect_right(trans_list, timestamp)723 724 # We want to know when the previous transition was, so subtract off 1725 return idx - 1726 727 def _get_ttinfo(self, idx):728 # For no list or after the last transition, default to _ttinfo_std729 if idx is None or (idx + 1) >= len(self._trans_list):730 return self._ttinfo_std731 732 # If there is a list and the time is before it, return _ttinfo_before733 if idx < 0:734 return self._ttinfo_before735 736 return self._trans_idx[idx]737 738 def _find_ttinfo(self, dt):739 idx = self._resolve_ambiguous_time(dt)740 741 return self._get_ttinfo(idx)742 743 def fromutc(self, dt):744 """745 The ``tzfile`` implementation of :py:func:`datetime.tzinfo.fromutc`.746 747 :param dt:748 A :py:class:`datetime.datetime` object.749 750 :raises TypeError:751 Raised if ``dt`` is not a :py:class:`datetime.datetime` object.752 753 :raises ValueError:754 Raised if this is called with a ``dt`` which does not have this755 ``tzinfo`` attached.756 757 :return:758 Returns a :py:class:`datetime.datetime` object representing the759 wall time in ``self``'s time zone.760 """761 # These isinstance checks are in datetime.tzinfo, so we'll preserve762 # them, even if we don't care about duck typing.763 if not isinstance(dt, datetime.datetime):764 raise TypeError("fromutc() requires a datetime argument")765 766 if dt.tzinfo is not self:767 raise ValueError("dt.tzinfo is not self")768 769 # First treat UTC as wall time and get the transition we're in.770 idx = self._find_last_transition(dt, in_utc=True)771 tti = self._get_ttinfo(idx)772 773 dt_out = dt + datetime.timedelta(seconds=tti.offset)774 775 fold = self.is_ambiguous(dt_out, idx=idx)776 777 return enfold(dt_out, fold=int(fold))778 779 def is_ambiguous(self, dt, idx=None):780 """781 Whether or not the "wall time" of a given datetime is ambiguous in this782 zone.783 784 :param dt:785 A :py:class:`datetime.datetime`, naive or time zone aware.786 787 788 :return:789 Returns ``True`` if ambiguous, ``False`` otherwise.790 791 .. versionadded:: 2.6.0792 """793 if idx is None:794 idx = self._find_last_transition(dt)795 796 # Calculate the difference in offsets from current to previous797 timestamp = _datetime_to_timestamp(dt)798 tti = self._get_ttinfo(idx)799 800 if idx is None or idx <= 0:801 return False802 803 od = self._get_ttinfo(idx - 1).offset - tti.offset804 tt = self._trans_list[idx] # Transition time805 806 return timestamp < tt + od807 808 def _resolve_ambiguous_time(self, dt):809 idx = self._find_last_transition(dt)810 811 # If we have no transitions, return the index812 _fold = self._fold(dt)813 if idx is None or idx == 0:814 return idx815 816 # If it's ambiguous and we're in a fold, shift to a different index.817 idx_offset = int(not _fold and self.is_ambiguous(dt, idx))818 819 return idx - idx_offset820 821 def utcoffset(self, dt):822 if dt is None:823 return None824 825 if not self._ttinfo_std:826 return ZERO827 828 return self._find_ttinfo(dt).delta829 830 def dst(self, dt):831 if dt is None:832 return None833 834 if not self._ttinfo_dst:835 return ZERO836 837 tti = self._find_ttinfo(dt)838 839 if not tti.isdst:840 return ZERO841 842 # The documentation says that utcoffset()-dst() must843 # be constant for every dt.844 return tti.dstoffset845 846 @tzname_in_python2847 def tzname(self, dt):848 if not self._ttinfo_std or dt is None:849 return None850 return self._find_ttinfo(dt).abbr851 852 def __eq__(self, other):853 if not isinstance(other, tzfile):854 return NotImplemented855 return (self._trans_list == other._trans_list and856 self._trans_idx == other._trans_idx and857 self._ttinfo_list == other._ttinfo_list)858 859 __hash__ = None860 861 def __ne__(self, other):862 return not (self == other)863 864 def __repr__(self):865 return "%s(%s)" % (self.__class__.__name__, repr(self._filename))866 867 def __reduce__(self):868 return self.__reduce_ex__(None)869 870 def __reduce_ex__(self, protocol):871 return (self.__class__, (None, self._filename), self.__dict__)872 873 874class tzrange(tzrangebase):875 """876 The ``tzrange`` object is a time zone specified by a set of offsets and877 abbreviations, equivalent to the way the ``TZ`` variable can be specified878 in POSIX-like systems, but using Python delta objects to specify DST879 start, end and offsets.880 881 :param stdabbr:882 The abbreviation for standard time (e.g. ``'EST'``).883 884 :param stdoffset:885 An integer or :class:`datetime.timedelta` object or equivalent886 specifying the base offset from UTC.887 888 If unspecified, +00:00 is used.889 890 :param dstabbr:891 The abbreviation for DST / "Summer" time (e.g. ``'EDT'``).892 893 If specified, with no other DST information, DST is assumed to occur894 and the default behavior or ``dstoffset``, ``start`` and ``end`` is895 used. If unspecified and no other DST information is specified, it896 is assumed that this zone has no DST.897 898 If this is unspecified and other DST information is *is* specified,899 DST occurs in the zone but the time zone abbreviation is left900 unchanged.901 902 :param dstoffset:903 A an integer or :class:`datetime.timedelta` object or equivalent904 specifying the UTC offset during DST. If unspecified and any other DST905 information is specified, it is assumed to be the STD offset +1 hour.906 907 :param start:908 A :class:`relativedelta.relativedelta` object or equivalent specifying909 the time and time of year that daylight savings time starts. To910 specify, for example, that DST starts at 2AM on the 2nd Sunday in911 March, pass:912 913 ``relativedelta(hours=2, month=3, day=1, weekday=SU(+2))``914 915 If unspecified and any other DST information is specified, the default916 value is 2 AM on the first Sunday in April.917 918 :param end:919 A :class:`relativedelta.relativedelta` object or equivalent920 representing the time and time of year that daylight savings time921 ends, with the same specification method as in ``start``. One note is922 that this should point to the first time in the *standard* zone, so if923 a transition occurs at 2AM in the DST zone and the clocks are set back924 1 hour to 1AM, set the ``hours`` parameter to +1.925 926 927 **Examples:**928 929 .. testsetup:: tzrange930 931 from dateutil.tz import tzrange, tzstr932 933 .. doctest:: tzrange934 935 >>> tzstr('EST5EDT') == tzrange("EST", -18000, "EDT")936 True937 938 >>> from dateutil.relativedelta import *939 >>> range1 = tzrange("EST", -18000, "EDT")940 >>> range2 = tzrange("EST", -18000, "EDT", -14400,941 ... relativedelta(hours=+2, month=4, day=1,942 ... weekday=SU(+1)),943 ... relativedelta(hours=+1, month=10, day=31,944 ... weekday=SU(-1)))945 >>> tzstr('EST5EDT') == range1 == range2946 True947 948 """949 def __init__(self, stdabbr, stdoffset=None,950 dstabbr=None, dstoffset=None,951 start=None, end=None):952 953 global relativedelta954 from dateutil import relativedelta955 956 self._std_abbr = stdabbr957 self._dst_abbr = dstabbr958 959 try:960 stdoffset = stdoffset.total_seconds()961 except (TypeError, AttributeError):962 pass963 964 try:965 dstoffset = dstoffset.total_seconds()966 except (TypeError, AttributeError):967 pass968 969 if stdoffset is not None:970 self._std_offset = datetime.timedelta(seconds=stdoffset)971 else:972 self._std_offset = ZERO973 974 if dstoffset is not None:975 self._dst_offset = datetime.timedelta(seconds=dstoffset)976 elif dstabbr and stdoffset is not None:977 self._dst_offset = self._std_offset + datetime.timedelta(hours=+1)978 else:979 self._dst_offset = ZERO980 981 if dstabbr and start is None:982 self._start_delta = relativedelta.relativedelta(983 hours=+2, month=4, day=1, weekday=relativedelta.SU(+1))984 else:985 self._start_delta = start986 987 if dstabbr and end is None:988 self._end_delta = relativedelta.relativedelta(989 hours=+1, month=10, day=31, weekday=relativedelta.SU(-1))990 else:991 self._end_delta = end992 993 self._dst_base_offset_ = self._dst_offset - self._std_offset994 self.hasdst = bool(self._start_delta)995 996 def transitions(self, year):997 """998 For a given year, get the DST on and off transition times, expressed999 always on the standard time side. For zones with no transitions, this1000 function returns ``None``.1001 1002 :param year:1003 The year whose transitions you would like to query.1004 1005 :return:1006 Returns a :class:`tuple` of :class:`datetime.datetime` objects,1007 ``(dston, dstoff)`` for zones with an annual DST transition, or1008 ``None`` for fixed offset zones.1009 """1010 if not self.hasdst:1011 return None1012 1013 base_year = datetime.datetime(year, 1, 1)1014 1015 start = base_year + self._start_delta1016 end = base_year + self._end_delta1017 1018 return (start, end)1019 1020 def __eq__(self, other):1021 if not isinstance(other, tzrange):1022 return NotImplemented1023 1024 return (self._std_abbr == other._std_abbr and1025 self._dst_abbr == other._dst_abbr and1026 self._std_offset == other._std_offset and1027 self._dst_offset == other._dst_offset and1028 self._start_delta == other._start_delta and1029 self._end_delta == other._end_delta)1030 1031 @property1032 def _dst_base_offset(self):1033 return self._dst_base_offset_1034 1035 1036@six.add_metaclass(_TzStrFactory)1037class tzstr(tzrange):1038 """1039 ``tzstr`` objects are time zone objects specified by a time-zone string as1040 it would be passed to a ``TZ`` variable on POSIX-style systems (see1041 the `GNU C Library: TZ Variable`_ for more details).1042 1043 There is one notable exception, which is that POSIX-style time zones use an1044 inverted offset format, so normally ``GMT+3`` would be parsed as an offset1045 3 hours *behind* GMT. The ``tzstr`` time zone object will parse this as an1046 offset 3 hours *ahead* of GMT. If you would like to maintain the POSIX1047 behavior, pass a ``True`` value to ``posix_offset``.1048 1049 The :class:`tzrange` object provides the same functionality, but is1050 specified using :class:`relativedelta.relativedelta` objects. rather than1051 strings.1052 1053 :param s:1054 A time zone string in ``TZ`` variable format. This can be a1055 :class:`bytes` (2.x: :class:`str`), :class:`str` (2.x:1056 :class:`unicode`) or a stream emitting unicode characters1057 (e.g. :class:`StringIO`).1058 1059 :param posix_offset:1060 Optional. If set to ``True``, interpret strings such as ``GMT+3`` or1061 ``UTC+3`` as being 3 hours *behind* UTC rather than ahead, per the1062 POSIX standard.1063 1064 .. caution::1065 1066 Prior to version 2.7.0, this function also supported time zones1067 in the format:1068 1069 * ``EST5EDT,4,0,6,7200,10,0,26,7200,3600``1070 * ``EST5EDT,4,1,0,7200,10,-1,0,7200,3600``1071 1072 This format is non-standard and has been deprecated; this function1073 will raise a :class:`DeprecatedTZFormatWarning` until1074 support is removed in a future version.1075 1076 .. _`GNU C Library: TZ Variable`:1077 https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html1078 """1079 def __init__(self, s, posix_offset=False):1080 global parser1081 from dateutil.parser import _parser as parser1082 1083 self._s = s1084 1085 res = parser._parsetz(s)1086 if res is None or res.any_unused_tokens:1087 raise ValueError("unknown string format")1088 1089 # Here we break the compatibility with the TZ variable handling.1090 # GMT-3 actually *means* the timezone -3.1091 if res.stdabbr in ("GMT", "UTC") and not posix_offset:1092 res.stdoffset *= -11093 1094 # We must initialize it first, since _delta() needs1095 # _std_offset and _dst_offset set. Use False in start/end1096 # to avoid building it two times.1097 tzrange.__init__(self, res.stdabbr, res.stdoffset,1098 res.dstabbr, res.dstoffset,1099 start=False, end=False)1100 1101 if not res.dstabbr:1102 self._start_delta = None1103 self._end_delta = None1104 else:1105 self._start_delta = self._delta(res.start)1106 if self._start_delta:1107 self._end_delta = self._delta(res.end, isend=1)1108 1109 self.hasdst = bool(self._start_delta)1110 1111 def _delta(self, x, isend=0):1112 from dateutil import relativedelta1113 kwargs = {}1114 if x.month is not None:1115 kwargs["month"] = x.month1116 if x.weekday is not None:1117 kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week)1118 if x.week > 0:1119 kwargs["day"] = 11120 else:1121 kwargs["day"] = 311122 elif x.day:1123 kwargs["day"] = x.day1124 elif x.yday is not None:1125 kwargs["yearday"] = x.yday1126 elif x.jyday is not None:1127 kwargs["nlyearday"] = x.jyday1128 if not kwargs:1129 # Default is to start on first sunday of april, and end1130 # on last sunday of october.1131 if not isend:1132 kwargs["month"] = 41133 kwargs["day"] = 11134 kwargs["weekday"] = relativedelta.SU(+1)1135 else:1136 kwargs["month"] = 101137 kwargs["day"] = 311138 kwargs["weekday"] = relativedelta.SU(-1)1139 if x.time is not None:1140 kwargs["seconds"] = x.time1141 else:1142 # Default is 2AM.1143 kwargs["seconds"] = 72001144 if isend:1145 # Convert to standard time, to follow the documented way1146 # of working with the extra hour. See the documentation1147 # of the tzinfo class.1148 delta = self._dst_offset - self._std_offset1149 kwargs["seconds"] -= delta.seconds + delta.days * 864001150 return relativedelta.relativedelta(**kwargs)1151 1152 def __repr__(self):1153 return "%s(%s)" % (self.__class__.__name__, repr(self._s))1154 1155 1156class _tzicalvtzcomp(object):1157 def __init__(self, tzoffsetfrom, tzoffsetto, isdst,1158 tzname=None, rrule=None):1159 self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom)1160 self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto)1161 self.tzoffsetdiff = self.tzoffsetto - self.tzoffsetfrom1162 self.isdst = isdst1163 self.tzname = tzname1164 self.rrule = rrule1165 1166 1167class _tzicalvtz(_tzinfo):1168 def __init__(self, tzid, comps=[]):1169 super(_tzicalvtz, self).__init__()1170 1171 self._tzid = tzid1172 self._comps = comps1173 self._cachedate = []1174 self._cachecomp = []1175 self._cache_lock = _thread.allocate_lock()1176 1177 def _find_comp(self, dt):1178 if len(self._comps) == 1:1179 return self._comps[0]1180 1181 dt = dt.replace(tzinfo=None)1182 1183 try:1184 with self._cache_lock:1185 return self._cachecomp[self._cachedate.index(1186 (dt, self._fold(dt)))]1187 except ValueError:1188 pass1189 1190 lastcompdt = None1191 lastcomp = None1192 1193 for comp in self._comps:1194 compdt = self._find_compdt(comp, dt)1195 1196 if compdt and (not lastcompdt or lastcompdt < compdt):1197 lastcompdt = compdt1198 lastcomp = comp1199 1200 if not lastcomp: