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