Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/dateutil/tz/tz.py @ 0:d30785e31577 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
| author | guerler |
|---|---|
| date | Fri, 31 Jul 2020 00:18:57 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:d30785e31577 |
|---|---|
| 1 # -*- coding: utf-8 -*- | |
| 2 """ | |
| 3 This module offers timezone implementations subclassing the abstract | |
| 4 :py:class:`datetime.tzinfo` type. There are classes to handle tzfile format | |
| 5 files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`, | |
| 6 etc), TZ environment string (in all known formats), given ranges (with help | |
| 7 from relative deltas), local machine timezone, fixed offset timezone, and UTC | |
| 8 timezone. | |
| 9 """ | |
| 10 import datetime | |
| 11 import struct | |
| 12 import time | |
| 13 import sys | |
| 14 import os | |
| 15 import bisect | |
| 16 import weakref | |
| 17 from collections import OrderedDict | |
| 18 | |
| 19 import six | |
| 20 from six import string_types | |
| 21 from six.moves import _thread | |
| 22 from ._common import tzname_in_python2, _tzinfo | |
| 23 from ._common import tzrangebase, enfold | |
| 24 from ._common import _validate_fromutc_inputs | |
| 25 | |
| 26 from ._factories import _TzSingleton, _TzOffsetFactory | |
| 27 from ._factories import _TzStrFactory | |
| 28 try: | |
| 29 from .win import tzwin, tzwinlocal | |
| 30 except ImportError: | |
| 31 tzwin = tzwinlocal = None | |
| 32 | |
| 33 # For warning about rounding tzinfo | |
| 34 from warnings import warn | |
| 35 | |
| 36 ZERO = datetime.timedelta(0) | |
| 37 EPOCH = datetime.datetime.utcfromtimestamp(0) | |
| 38 EPOCHORDINAL = EPOCH.toordinal() | |
| 39 | |
| 40 | |
| 41 @six.add_metaclass(_TzSingleton) | |
| 42 class 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 #: Convenience constant providing a :class:`tzutc()` instance | |
| 127 #: | |
| 128 #: .. versionadded:: 2.7.0 | |
| 129 UTC = tzutc() | |
| 130 | |
| 131 | |
| 132 @six.add_metaclass(_TzOffsetFactory) | |
| 133 class 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, represented | |
| 141 as a :py:class:`datetime.timedelta` object). | |
| 142 """ | |
| 143 def __init__(self, name, offset): | |
| 144 self._name = name | |
| 145 | |
| 146 try: | |
| 147 # Allow a timedelta | |
| 148 offset = offset.total_seconds() | |
| 149 except (TypeError, AttributeError): | |
| 150 pass | |
| 151 | |
| 152 self._offset = datetime.timedelta(seconds=_get_supported_offset(offset)) | |
| 153 | |
| 154 def utcoffset(self, dt): | |
| 155 return self._offset | |
| 156 | |
| 157 def dst(self, dt): | |
| 158 return ZERO | |
| 159 | |
| 160 @tzname_in_python2 | |
| 161 def tzname(self, dt): | |
| 162 return self._name | |
| 163 | |
| 164 @_validate_fromutc_inputs | |
| 165 def fromutc(self, dt): | |
| 166 return dt + self._offset | |
| 167 | |
| 168 def is_ambiguous(self, dt): | |
| 169 """ | |
| 170 Whether or not the "wall time" of a given datetime is ambiguous in this | |
| 171 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.0 | |
| 179 """ | |
| 180 return False | |
| 181 | |
| 182 def __eq__(self, other): | |
| 183 if not isinstance(other, tzoffset): | |
| 184 return NotImplemented | |
| 185 | |
| 186 return self._offset == other._offset | |
| 187 | |
| 188 __hash__ = None | |
| 189 | |
| 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 | |
| 201 class 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_offset | |
| 213 | |
| 214 self._dst_saved = self._dst_offset - self._std_offset | |
| 215 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 None | |
| 221 | |
| 222 if self._isdst(dt): | |
| 223 return self._dst_offset | |
| 224 else: | |
| 225 return self._std_offset | |
| 226 | |
| 227 def dst(self, dt): | |
| 228 if dt is None and self._hasdst: | |
| 229 return None | |
| 230 | |
| 231 if self._isdst(dt): | |
| 232 return self._dst_offset - self._std_offset | |
| 233 else: | |
| 234 return ZERO | |
| 235 | |
| 236 @tzname_in_python2 | |
| 237 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 this | |
| 243 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.0 | |
| 253 """ | |
| 254 naive_dst = self._naive_is_dst(dt) | |
| 255 return (not naive_dst and | |
| 256 (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_isdst | |
| 261 | |
| 262 def _isdst(self, dt, fold_naive=True): | |
| 263 # We can't use mktime here. It is unstable when deciding if | |
| 264 # 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_isdst | |
| 269 # | |
| 270 # The code above yields the following result: | |
| 271 # | |
| 272 # >>> import tz, datetime | |
| 273 # >>> 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 False | |
| 289 | |
| 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 True | |
| 299 | |
| 300 return dstval | |
| 301 | |
| 302 def __eq__(self, other): | |
| 303 if isinstance(other, tzlocal): | |
| 304 return (self._std_offset == other._std_offset and | |
| 305 self._dst_offset == other._dst_offset) | |
| 306 elif isinstance(other, tzutc): | |
| 307 return (not self._hasdst and | |
| 308 self._tznames[0] in {'UTC', 'GMT'} and | |
| 309 self._std_offset == ZERO) | |
| 310 elif isinstance(other, tzoffset): | |
| 311 return (not self._hasdst and | |
| 312 self._tznames[0] == other._name and | |
| 313 self._std_offset == other._offset) | |
| 314 else: | |
| 315 return NotImplemented | |
| 316 | |
| 317 __hash__ = None | |
| 318 | |
| 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 | |
| 328 class _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 NotImplemented | |
| 347 | |
| 348 return (self.offset == other.offset and | |
| 349 self.delta == other.delta and | |
| 350 self.isdst == other.isdst and | |
| 351 self.abbr == other.abbr and | |
| 352 self.isstd == other.isstd and | |
| 353 self.isgmt == other.isgmt and | |
| 354 self.dstoffset == other.dstoffset) | |
| 355 | |
| 356 __hash__ = None | |
| 357 | |
| 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 state | |
| 366 | |
| 367 def __setstate__(self, state): | |
| 368 for name in self.__slots__: | |
| 369 if name in state: | |
| 370 setattr(self, name, state[name]) | |
| 371 | |
| 372 | |
| 373 class _tzfile(object): | |
| 374 """ | |
| 375 Lightweight class for holding the relevant transition and time zone | |
| 376 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 | |
| 386 class 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 zone | |
| 393 information can be read from. | |
| 394 | |
| 395 :param filename: | |
| 396 This is an optional parameter specifying the source of the time zone | |
| 397 information in the event that ``fileobj`` is a file object. If omitted | |
| 398 and ``fileobj`` is a file stream, this parameter will be set either to | |
| 399 ``fileobj``'s ``name`` attribute or to ``repr(fileobj)``. | |
| 400 | |
| 401 See `Sources for Time Zone and Daylight Saving Time Data | |
| 402 <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 files | |
| 404 <https://www.iana.org/time-zones>`_ with the `zic time zone compiler | |
| 405 <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 timezone | |
| 410 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 call | |
| 413 :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:: tzfile | |
| 422 | |
| 423 from dateutil.tz import gettz | |
| 424 from datetime import datetime | |
| 425 | |
| 426 .. doctest:: tzfile | |
| 427 | |
| 428 >>> NYC = gettz('America/New_York') | |
| 429 >>> NYC | |
| 430 tzfile('/usr/share/zoneinfo/America/New_York') | |
| 431 | |
| 432 >>> print(datetime(2016, 1, 3, tzinfo=NYC)) # EST | |
| 433 2016-01-03 00:00:00-05:00 | |
| 434 | |
| 435 >>> print(datetime(2016, 7, 7, tzinfo=NYC)) # EDT | |
| 436 2016-07-07 00:00:00-04:00 | |
| 437 | |
| 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, before | |
| 441 the adoption of the UTC standards, New York used local solar mean time: | |
| 442 | |
| 443 .. doctest:: tzfile | |
| 444 | |
| 445 >>> print(datetime(1901, 4, 12, tzinfo=NYC)) # LMT | |
| 446 1901-04-12 00:00:00-04:56 | |
| 447 | |
| 448 And during World War II, New York was on "Eastern War Time", which was a | |
| 449 state of permanent daylight saving time: | |
| 450 | |
| 451 .. doctest:: tzfile | |
| 452 | |
| 453 >>> print(datetime(1944, 2, 7, tzinfo=NYC)) # EWT | |
| 454 1944-02-07 00:00:00-04:00 | |
| 455 | |
| 456 """ | |
| 457 | |
| 458 def __init__(self, fileobj, filename=None): | |
| 459 super(tzfile, self).__init__() | |
| 460 | |
| 461 file_opened_here = False | |
| 462 if isinstance(fileobj, string_types): | |
| 463 self._filename = fileobj | |
| 464 fileobj = open(fileobj, 'rb') | |
| 465 file_opened_here = True | |
| 466 elif filename is not None: | |
| 467 self._filename = filename | |
| 468 elif hasattr(fileobj, "name"): | |
| 469 self._filename = fileobj.name | |
| 470 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 attributes | |
| 485 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 identify | |
| 495 # them as time zone information files, followed by | |
| 496 # sixteen bytes reserved for future use, followed by | |
| 497 # six four-byte values of type long, written in a | |
| 498 # ``standard'' byte order (the high-order byte | |
| 499 # 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 is | |
| 513 # stored in the file. | |
| 514 leapcnt, | |
| 515 | |
| 516 # The number of "transition times" for which data | |
| 517 # is stored in the file. | |
| 518 timecnt, | |
| 519 | |
| 520 # The number of "local time types" for which data | |
| 521 # is stored in the file (must not be zero). | |
| 522 typecnt, | |
| 523 | |
| 524 # The number of characters of "time zone | |
| 525 # 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-byte | |
| 531 # 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 by | |
| 534 # time(2)) at which the rules for computing local time | |
| 535 # 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 unsigned | |
| 544 # char; each one tells which of the different types of | |
| 545 # ``local time'' types described in the file is associated | |
| 546 # with the same-indexed transition time. These values | |
| 547 # serve as indices into an array of ttinfo structures that | |
| 548 # 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 value | |
| 557 # for tt_gmtoff of type long, in a standard byte | |
| 558 # order, followed by a one-byte value for tt_isdst | |
| 559 # and a one-byte value for tt_abbrind. In each | |
| 560 # structure, tt_gmtoff gives the number of | |
| 561 # seconds to be added to UTC, tt_isdst tells whether | |
| 562 # tm_isdst should be set by localtime(3), and | |
| 563 # tt_abbrind serves as an index into the array of | |
| 564 # time zone abbreviation characters that follow the | |
| 565 # 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-byte | |
| 575 # values, written in standard byte order; the | |
| 576 # first value of each pair gives the time (as | |
| 577 # returned by time(2)) at which a leap second | |
| 578 # occurs; the second gives the total number of | |
| 579 # leap seconds to be applied after the given time. | |
| 580 # The pairs of values are sorted in ascending order | |
| 581 # 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/wall | |
| 588 # indicators, each stored as a one-byte value; | |
| 589 # they tell whether the transition times associated | |
| 590 # with local time types were specified as standard | |
| 591 # time or wall clock time, and are used when | |
| 592 # a time zone file is used in handling POSIX-style | |
| 593 # 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/local | |
| 600 # indicators, each stored as a one-byte value; | |
| 601 # they tell whether the transition times associated | |
| 602 # with local time types were specified as UTC or | |
| 603 # local time, and are used when a time zone file | |
| 604 # 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 list | |
| 612 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 = gmtoff | |
| 618 tti.dstoffset = datetime.timedelta(0) | |
| 619 tti.delta = datetime.timedelta(seconds=gmtoff) | |
| 620 tti.isdst = isdst | |
| 621 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 be | |
| 630 # used when a given time is before any transitions, | |
| 631 # and will be set to the first non-dst ttinfo, or to | |
| 632 # the first dst, if all of them are dst. | |
| 633 out.ttinfo_std = None | |
| 634 out.ttinfo_dst = None | |
| 635 out.ttinfo_before = None | |
| 636 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 = tti | |
| 644 elif not out.ttinfo_dst and tti.isdst: | |
| 645 out.ttinfo_dst = tti | |
| 646 | |
| 647 if out.ttinfo_std and out.ttinfo_dst: | |
| 648 break | |
| 649 else: | |
| 650 if out.ttinfo_dst and not out.ttinfo_std: | |
| 651 out.ttinfo_std = out.ttinfo_dst | |
| 652 | |
| 653 for tti in out.ttinfo_list: | |
| 654 if not tti.isdst: | |
| 655 out.ttinfo_before = tti | |
| 656 break | |
| 657 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 file | |
| 663 # is setup to wall time, and in the binary file isstd and | |
| 664 # isgmt are off, so it should be in wall time. OTOH, it's | |
| 665 # always in gmt time. Let me know if you have comments | |
| 666 # about this. | |
| 667 lastdst = None | |
| 668 lastoffset = None | |
| 669 lastdstoffset = None | |
| 670 lastbaseoffset = None | |
| 671 out.trans_list = [] | |
| 672 | |
| 673 for i, tti in enumerate(out.trans_idx): | |
| 674 offset = tti.offset | |
| 675 dstoffset = 0 | |
| 676 | |
| 677 if lastdst is not None: | |
| 678 if tti.isdst: | |
| 679 if not lastdst: | |
| 680 dstoffset = offset - lastoffset | |
| 681 | |
| 682 if not dstoffset and lastdstoffset: | |
| 683 dstoffset = lastdstoffset | |
| 684 | |
| 685 tti.dstoffset = datetime.timedelta(seconds=dstoffset) | |
| 686 lastdstoffset = dstoffset | |
| 687 | |
| 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 the | |
| 690 # transition time in local time. Otherwise you use the current | |
| 691 # base offset. Ideally, I would have some mathematical proof of | |
| 692 # why this is true, but I haven't really thought about it enough. | |
| 693 baseoffset = offset - dstoffset | |
| 694 adjustment = baseoffset | |
| 695 if (lastbaseoffset is not None and baseoffset != lastbaseoffset | |
| 696 and tti.isdst != lastdst): | |
| 697 # The base DST has changed | |
| 698 adjustment = lastbaseoffset | |
| 699 | |
| 700 lastdst = tti.isdst | |
| 701 lastoffset = offset | |
| 702 lastbaseoffset = baseoffset | |
| 703 | |
| 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 out | |
| 711 | |
| 712 def _find_last_transition(self, dt, in_utc=False): | |
| 713 # If there's no list, there are no transitions to find | |
| 714 if not self._trans_list: | |
| 715 return None | |
| 716 | |
| 717 timestamp = _datetime_to_timestamp(dt) | |
| 718 | |
| 719 # Find where the timestamp fits in the transition list - if the | |
| 720 # 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_list | |
| 722 idx = bisect.bisect_right(trans_list, timestamp) | |
| 723 | |
| 724 # We want to know when the previous transition was, so subtract off 1 | |
| 725 return idx - 1 | |
| 726 | |
| 727 def _get_ttinfo(self, idx): | |
| 728 # For no list or after the last transition, default to _ttinfo_std | |
| 729 if idx is None or (idx + 1) >= len(self._trans_list): | |
| 730 return self._ttinfo_std | |
| 731 | |
| 732 # If there is a list and the time is before it, return _ttinfo_before | |
| 733 if idx < 0: | |
| 734 return self._ttinfo_before | |
| 735 | |
| 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 this | |
| 755 ``tzinfo`` attached. | |
| 756 | |
| 757 :return: | |
| 758 Returns a :py:class:`datetime.datetime` object representing the | |
| 759 wall time in ``self``'s time zone. | |
| 760 """ | |
| 761 # These isinstance checks are in datetime.tzinfo, so we'll preserve | |
| 762 # 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 this | |
| 782 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.0 | |
| 792 """ | |
| 793 if idx is None: | |
| 794 idx = self._find_last_transition(dt) | |
| 795 | |
| 796 # Calculate the difference in offsets from current to previous | |
| 797 timestamp = _datetime_to_timestamp(dt) | |
| 798 tti = self._get_ttinfo(idx) | |
| 799 | |
| 800 if idx is None or idx <= 0: | |
| 801 return False | |
| 802 | |
| 803 od = self._get_ttinfo(idx - 1).offset - tti.offset | |
| 804 tt = self._trans_list[idx] # Transition time | |
| 805 | |
| 806 return timestamp < tt + od | |
| 807 | |
| 808 def _resolve_ambiguous_time(self, dt): | |
| 809 idx = self._find_last_transition(dt) | |
| 810 | |
| 811 # If we have no transitions, return the index | |
| 812 _fold = self._fold(dt) | |
| 813 if idx is None or idx == 0: | |
| 814 return idx | |
| 815 | |
| 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_offset | |
| 820 | |
| 821 def utcoffset(self, dt): | |
| 822 if dt is None: | |
| 823 return None | |
| 824 | |
| 825 if not self._ttinfo_std: | |
| 826 return ZERO | |
| 827 | |
| 828 return self._find_ttinfo(dt).delta | |
| 829 | |
| 830 def dst(self, dt): | |
| 831 if dt is None: | |
| 832 return None | |
| 833 | |
| 834 if not self._ttinfo_dst: | |
| 835 return ZERO | |
| 836 | |
| 837 tti = self._find_ttinfo(dt) | |
| 838 | |
| 839 if not tti.isdst: | |
| 840 return ZERO | |
| 841 | |
| 842 # The documentation says that utcoffset()-dst() must | |
| 843 # be constant for every dt. | |
| 844 return tti.dstoffset | |
| 845 | |
| 846 @tzname_in_python2 | |
| 847 def tzname(self, dt): | |
| 848 if not self._ttinfo_std or dt is None: | |
| 849 return None | |
| 850 return self._find_ttinfo(dt).abbr | |
| 851 | |
| 852 def __eq__(self, other): | |
| 853 if not isinstance(other, tzfile): | |
| 854 return NotImplemented | |
| 855 return (self._trans_list == other._trans_list and | |
| 856 self._trans_idx == other._trans_idx and | |
| 857 self._ttinfo_list == other._ttinfo_list) | |
| 858 | |
| 859 __hash__ = None | |
| 860 | |
| 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 | |
| 874 class tzrange(tzrangebase): | |
| 875 """ | |
| 876 The ``tzrange`` object is a time zone specified by a set of offsets and | |
| 877 abbreviations, equivalent to the way the ``TZ`` variable can be specified | |
| 878 in POSIX-like systems, but using Python delta objects to specify DST | |
| 879 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 equivalent | |
| 886 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 occur | |
| 894 and the default behavior or ``dstoffset``, ``start`` and ``end`` is | |
| 895 used. If unspecified and no other DST information is specified, it | |
| 896 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 left | |
| 900 unchanged. | |
| 901 | |
| 902 :param dstoffset: | |
| 903 A an integer or :class:`datetime.timedelta` object or equivalent | |
| 904 specifying the UTC offset during DST. If unspecified and any other DST | |
| 905 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 specifying | |
| 909 the time and time of year that daylight savings time starts. To | |
| 910 specify, for example, that DST starts at 2AM on the 2nd Sunday in | |
| 911 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 default | |
| 916 value is 2 AM on the first Sunday in April. | |
| 917 | |
| 918 :param end: | |
| 919 A :class:`relativedelta.relativedelta` object or equivalent | |
| 920 representing the time and time of year that daylight savings time | |
| 921 ends, with the same specification method as in ``start``. One note is | |
| 922 that this should point to the first time in the *standard* zone, so if | |
| 923 a transition occurs at 2AM in the DST zone and the clocks are set back | |
| 924 1 hour to 1AM, set the ``hours`` parameter to +1. | |
| 925 | |
| 926 | |
| 927 **Examples:** | |
| 928 | |
| 929 .. testsetup:: tzrange | |
| 930 | |
| 931 from dateutil.tz import tzrange, tzstr | |
| 932 | |
| 933 .. doctest:: tzrange | |
| 934 | |
| 935 >>> tzstr('EST5EDT') == tzrange("EST", -18000, "EDT") | |
| 936 True | |
| 937 | |
| 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 == range2 | |
| 946 True | |
| 947 | |
| 948 """ | |
| 949 def __init__(self, stdabbr, stdoffset=None, | |
| 950 dstabbr=None, dstoffset=None, | |
| 951 start=None, end=None): | |
| 952 | |
| 953 global relativedelta | |
| 954 from dateutil import relativedelta | |
| 955 | |
| 956 self._std_abbr = stdabbr | |
| 957 self._dst_abbr = dstabbr | |
| 958 | |
| 959 try: | |
| 960 stdoffset = stdoffset.total_seconds() | |
| 961 except (TypeError, AttributeError): | |
| 962 pass | |
| 963 | |
| 964 try: | |
| 965 dstoffset = dstoffset.total_seconds() | |
| 966 except (TypeError, AttributeError): | |
| 967 pass | |
| 968 | |
| 969 if stdoffset is not None: | |
| 970 self._std_offset = datetime.timedelta(seconds=stdoffset) | |
| 971 else: | |
| 972 self._std_offset = ZERO | |
| 973 | |
| 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 = ZERO | |
| 980 | |
| 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 = start | |
| 986 | |
| 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 = end | |
| 992 | |
| 993 self._dst_base_offset_ = self._dst_offset - self._std_offset | |
| 994 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, expressed | |
| 999 always on the standard time side. For zones with no transitions, this | |
| 1000 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, or | |
| 1008 ``None`` for fixed offset zones. | |
| 1009 """ | |
| 1010 if not self.hasdst: | |
| 1011 return None | |
| 1012 | |
| 1013 base_year = datetime.datetime(year, 1, 1) | |
| 1014 | |
| 1015 start = base_year + self._start_delta | |
| 1016 end = base_year + self._end_delta | |
| 1017 | |
| 1018 return (start, end) | |
| 1019 | |
| 1020 def __eq__(self, other): | |
| 1021 if not isinstance(other, tzrange): | |
| 1022 return NotImplemented | |
| 1023 | |
| 1024 return (self._std_abbr == other._std_abbr and | |
| 1025 self._dst_abbr == other._dst_abbr and | |
| 1026 self._std_offset == other._std_offset and | |
| 1027 self._dst_offset == other._dst_offset and | |
| 1028 self._start_delta == other._start_delta and | |
| 1029 self._end_delta == other._end_delta) | |
| 1030 | |
| 1031 @property | |
| 1032 def _dst_base_offset(self): | |
| 1033 return self._dst_base_offset_ | |
| 1034 | |
| 1035 | |
| 1036 @six.add_metaclass(_TzStrFactory) | |
| 1037 class tzstr(tzrange): | |
| 1038 """ | |
| 1039 ``tzstr`` objects are time zone objects specified by a time-zone string as | |
| 1040 it would be passed to a ``TZ`` variable on POSIX-style systems (see | |
| 1041 the `GNU C Library: TZ Variable`_ for more details). | |
| 1042 | |
| 1043 There is one notable exception, which is that POSIX-style time zones use an | |
| 1044 inverted offset format, so normally ``GMT+3`` would be parsed as an offset | |
| 1045 3 hours *behind* GMT. The ``tzstr`` time zone object will parse this as an | |
| 1046 offset 3 hours *ahead* of GMT. If you would like to maintain the POSIX | |
| 1047 behavior, pass a ``True`` value to ``posix_offset``. | |
| 1048 | |
| 1049 The :class:`tzrange` object provides the same functionality, but is | |
| 1050 specified using :class:`relativedelta.relativedelta` objects. rather than | |
| 1051 strings. | |
| 1052 | |
| 1053 :param s: | |
| 1054 A time zone string in ``TZ`` variable format. This can be a | |
| 1055 :class:`bytes` (2.x: :class:`str`), :class:`str` (2.x: | |
| 1056 :class:`unicode`) or a stream emitting unicode characters | |
| 1057 (e.g. :class:`StringIO`). | |
| 1058 | |
| 1059 :param posix_offset: | |
| 1060 Optional. If set to ``True``, interpret strings such as ``GMT+3`` or | |
| 1061 ``UTC+3`` as being 3 hours *behind* UTC rather than ahead, per the | |
| 1062 POSIX standard. | |
| 1063 | |
| 1064 .. caution:: | |
| 1065 | |
| 1066 Prior to version 2.7.0, this function also supported time zones | |
| 1067 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 function | |
| 1073 will raise a :class:`DeprecatedTZFormatWarning` until | |
| 1074 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.html | |
| 1078 """ | |
| 1079 def __init__(self, s, posix_offset=False): | |
| 1080 global parser | |
| 1081 from dateutil.parser import _parser as parser | |
| 1082 | |
| 1083 self._s = s | |
| 1084 | |
| 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 *= -1 | |
| 1093 | |
| 1094 # We must initialize it first, since _delta() needs | |
| 1095 # _std_offset and _dst_offset set. Use False in start/end | |
| 1096 # 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 = None | |
| 1103 self._end_delta = None | |
| 1104 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 relativedelta | |
| 1113 kwargs = {} | |
| 1114 if x.month is not None: | |
| 1115 kwargs["month"] = x.month | |
| 1116 if x.weekday is not None: | |
| 1117 kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week) | |
| 1118 if x.week > 0: | |
| 1119 kwargs["day"] = 1 | |
| 1120 else: | |
| 1121 kwargs["day"] = 31 | |
| 1122 elif x.day: | |
| 1123 kwargs["day"] = x.day | |
| 1124 elif x.yday is not None: | |
| 1125 kwargs["yearday"] = x.yday | |
| 1126 elif x.jyday is not None: | |
| 1127 kwargs["nlyearday"] = x.jyday | |
| 1128 if not kwargs: | |
| 1129 # Default is to start on first sunday of april, and end | |
| 1130 # on last sunday of october. | |
| 1131 if not isend: | |
| 1132 kwargs["month"] = 4 | |
| 1133 kwargs["day"] = 1 | |
| 1134 kwargs["weekday"] = relativedelta.SU(+1) | |
| 1135 else: | |
| 1136 kwargs["month"] = 10 | |
| 1137 kwargs["day"] = 31 | |
| 1138 kwargs["weekday"] = relativedelta.SU(-1) | |
| 1139 if x.time is not None: | |
| 1140 kwargs["seconds"] = x.time | |
| 1141 else: | |
| 1142 # Default is 2AM. | |
| 1143 kwargs["seconds"] = 7200 | |
| 1144 if isend: | |
| 1145 # Convert to standard time, to follow the documented way | |
| 1146 # of working with the extra hour. See the documentation | |
| 1147 # of the tzinfo class. | |
| 1148 delta = self._dst_offset - self._std_offset | |
| 1149 kwargs["seconds"] -= delta.seconds + delta.days * 86400 | |
| 1150 return relativedelta.relativedelta(**kwargs) | |
| 1151 | |
| 1152 def __repr__(self): | |
| 1153 return "%s(%s)" % (self.__class__.__name__, repr(self._s)) | |
| 1154 | |
| 1155 | |
| 1156 class _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.tzoffsetfrom | |
| 1162 self.isdst = isdst | |
| 1163 self.tzname = tzname | |
| 1164 self.rrule = rrule | |
| 1165 | |
| 1166 | |
| 1167 class _tzicalvtz(_tzinfo): | |
| 1168 def __init__(self, tzid, comps=[]): | |
| 1169 super(_tzicalvtz, self).__init__() | |
| 1170 | |
| 1171 self._tzid = tzid | |
| 1172 self._comps = comps | |
| 1173 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 pass | |
| 1189 | |
| 1190 lastcompdt = None | |
| 1191 lastcomp = None | |
| 1192 | |
| 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 = compdt | |
| 1198 lastcomp = comp | |
| 1199 | |
| 1200 if not lastcomp: | |
| 1201 # RFC says nothing about what to do when a given | |
| 1202 # time is before the first onset date. We'll look for the | |
| 1203 # first standard component, or the first component, if | |
| 1204 # none is found. | |
| 1205 for comp in self._comps: | |
| 1206 if not comp.isdst: | |
| 1207 lastcomp = comp | |
| 1208 break | |
| 1209 else: | |
| 1210 lastcomp = comp[0] | |
| 1211 | |
| 1212 with self._cache_lock: | |
| 1213 self._cachedate.insert(0, (dt, self._fold(dt))) | |
| 1214 self._cachecomp.insert(0, lastcomp) | |
| 1215 | |
| 1216 if len(self._cachedate) > 10: | |
| 1217 self._cachedate.pop() | |
| 1218 self._cachecomp.pop() | |
| 1219 | |
| 1220 return lastcomp | |
| 1221 | |
| 1222 def _find_compdt(self, comp, dt): | |
| 1223 if comp.tzoffsetdiff < ZERO and self._fold(dt): | |
| 1224 dt -= comp.tzoffsetdiff | |
| 1225 | |
| 1226 compdt = comp.rrule.before(dt, inc=True) | |
| 1227 | |
| 1228 return compdt | |
| 1229 | |
| 1230 def utcoffset(self, dt): | |
| 1231 if dt is None: | |
| 1232 return None | |
| 1233 | |
| 1234 return self._find_comp(dt).tzoffsetto | |
| 1235 | |
| 1236 def dst(self, dt): | |
| 1237 comp = self._find_comp(dt) | |
| 1238 if comp.isdst: | |
| 1239 return comp.tzoffsetdiff | |
| 1240 else: | |
| 1241 return ZERO | |
| 1242 | |
| 1243 @tzname_in_python2 | |
| 1244 def tzname(self, dt): | |
| 1245 return self._find_comp(dt).tzname | |
| 1246 | |
| 1247 def __repr__(self): | |
| 1248 return "<tzicalvtz %s>" % repr(self._tzid) | |
| 1249 | |
| 1250 __reduce__ = object.__reduce__ | |
| 1251 | |
| 1252 | |
| 1253 class tzical(object): | |
| 1254 """ | |
| 1255 This object is designed to parse an iCalendar-style ``VTIMEZONE`` structure | |
| 1256 as set out in `RFC 5545`_ Section 4.6.5 into one or more `tzinfo` objects. | |
| 1257 | |
| 1258 :param `fileobj`: | |
| 1259 A file or stream in iCalendar format, which should be UTF-8 encoded | |
| 1260 with CRLF endings. | |
| 1261 | |
| 1262 .. _`RFC 5545`: https://tools.ietf.org/html/rfc5545 | |
| 1263 """ | |
| 1264 def __init__(self, fileobj): | |
| 1265 global rrule | |
| 1266 from dateutil import rrule | |
| 1267 | |
| 1268 if isinstance(fileobj, string_types): | |
| 1269 self._s = fileobj | |
| 1270 # ical should be encoded in UTF-8 with CRLF | |
| 1271 fileobj = open(fileobj, 'r') | |
| 1272 else: | |
| 1273 self._s = getattr(fileobj, 'name', repr(fileobj)) | |
| 1274 fileobj = _nullcontext(fileobj) | |
| 1275 | |
| 1276 self._vtz = {} | |
| 1277 | |
| 1278 with fileobj as fobj: | |
| 1279 self._parse_rfc(fobj.read()) | |
| 1280 | |
| 1281 def keys(self): | |
| 1282 """ | |
| 1283 Retrieves the available time zones as a list. | |
| 1284 """ | |
| 1285 return list(self._vtz.keys()) | |
| 1286 | |
| 1287 def get(self, tzid=None): | |
| 1288 """ | |
| 1289 Retrieve a :py:class:`datetime.tzinfo` object by its ``tzid``. | |
| 1290 | |
| 1291 :param tzid: | |
| 1292 If there is exactly one time zone available, omitting ``tzid`` | |
| 1293 or passing :py:const:`None` value returns it. Otherwise a valid | |
| 1294 key (which can be retrieved from :func:`keys`) is required. | |
| 1295 | |
| 1296 :raises ValueError: | |
| 1297 Raised if ``tzid`` is not specified but there are either more | |
| 1298 or fewer than 1 zone defined. | |
| 1299 | |
| 1300 :returns: | |
| 1301 Returns either a :py:class:`datetime.tzinfo` object representing | |
| 1302 the relevant time zone or :py:const:`None` if the ``tzid`` was | |
| 1303 not found. | |
| 1304 """ | |
| 1305 if tzid is None: | |
| 1306 if len(self._vtz) == 0: | |
| 1307 raise ValueError("no timezones defined") | |
| 1308 elif len(self._vtz) > 1: | |
| 1309 raise ValueError("more than one timezone available") | |
| 1310 tzid = next(iter(self._vtz)) | |
| 1311 | |
| 1312 return self._vtz.get(tzid) | |
| 1313 | |
| 1314 def _parse_offset(self, s): | |
| 1315 s = s.strip() | |
| 1316 if not s: | |
| 1317 raise ValueError("empty offset") | |
| 1318 if s[0] in ('+', '-'): | |
| 1319 signal = (-1, +1)[s[0] == '+'] | |
| 1320 s = s[1:] | |
| 1321 else: | |
| 1322 signal = +1 | |
| 1323 if len(s) == 4: | |
| 1324 return (int(s[:2]) * 3600 + int(s[2:]) * 60) * signal | |
| 1325 elif len(s) == 6: | |
| 1326 return (int(s[:2]) * 3600 + int(s[2:4]) * 60 + int(s[4:])) * signal | |
| 1327 else: | |
| 1328 raise ValueError("invalid offset: " + s) | |
| 1329 | |
| 1330 def _parse_rfc(self, s): | |
| 1331 lines = s.splitlines() | |
| 1332 if not lines: | |
| 1333 raise ValueError("empty string") | |
| 1334 | |
| 1335 # Unfold | |
| 1336 i = 0 | |
| 1337 while i < len(lines): | |
| 1338 line = lines[i].rstrip() | |
| 1339 if not line: | |
| 1340 del lines[i] | |
| 1341 elif i > 0 and line[0] == " ": | |
| 1342 lines[i-1] += line[1:] | |
| 1343 del lines[i] | |
| 1344 else: | |
| 1345 i += 1 | |
| 1346 | |
| 1347 tzid = None | |
| 1348 comps = [] | |
| 1349 invtz = False | |
| 1350 comptype = None | |
| 1351 for line in lines: | |
| 1352 if not line: | |
| 1353 continue | |
| 1354 name, value = line.split(':', 1) | |
| 1355 parms = name.split(';') | |
| 1356 if not parms: | |
| 1357 raise ValueError("empty property name") | |
| 1358 name = parms[0].upper() | |
| 1359 parms = parms[1:] | |
| 1360 if invtz: | |
| 1361 if name == "BEGIN": | |
| 1362 if value in ("STANDARD", "DAYLIGHT"): | |
| 1363 # Process component | |
| 1364 pass | |
| 1365 else: | |
| 1366 raise ValueError("unknown component: "+value) | |
| 1367 comptype = value | |
| 1368 founddtstart = False | |
| 1369 tzoffsetfrom = None | |
| 1370 tzoffsetto = None | |
| 1371 rrulelines = [] | |
| 1372 tzname = None | |
| 1373 elif name == "END": | |
| 1374 if value == "VTIMEZONE": | |
| 1375 if comptype: | |
| 1376 raise ValueError("component not closed: "+comptype) | |
| 1377 if not tzid: | |
| 1378 raise ValueError("mandatory TZID not found") | |
| 1379 if not comps: | |
| 1380 raise ValueError( | |
| 1381 "at least one component is needed") | |
| 1382 # Process vtimezone | |
| 1383 self._vtz[tzid] = _tzicalvtz(tzid, comps) | |
| 1384 invtz = False | |
| 1385 elif value == comptype: | |
| 1386 if not founddtstart: | |
| 1387 raise ValueError("mandatory DTSTART not found") | |
| 1388 if tzoffsetfrom is None: | |
| 1389 raise ValueError( | |
| 1390 "mandatory TZOFFSETFROM not found") | |
| 1391 if tzoffsetto is None: | |
| 1392 raise ValueError( | |
| 1393 "mandatory TZOFFSETFROM not found") | |
| 1394 # Process component | |
| 1395 rr = None | |
| 1396 if rrulelines: | |
| 1397 rr = rrule.rrulestr("\n".join(rrulelines), | |
| 1398 compatible=True, | |
| 1399 ignoretz=True, | |
| 1400 cache=True) | |
| 1401 comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto, | |
| 1402 (comptype == "DAYLIGHT"), | |
| 1403 tzname, rr) | |
| 1404 comps.append(comp) | |
| 1405 comptype = None | |
| 1406 else: | |
| 1407 raise ValueError("invalid component end: "+value) | |
| 1408 elif comptype: | |
| 1409 if name == "DTSTART": | |
| 1410 # DTSTART in VTIMEZONE takes a subset of valid RRULE | |
| 1411 # values under RFC 5545. | |
| 1412 for parm in parms: | |
| 1413 if parm != 'VALUE=DATE-TIME': | |
| 1414 msg = ('Unsupported DTSTART param in ' + | |
| 1415 'VTIMEZONE: ' + parm) | |
| 1416 raise ValueError(msg) | |
| 1417 rrulelines.append(line) | |
| 1418 founddtstart = True | |
| 1419 elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"): | |
| 1420 rrulelines.append(line) | |
| 1421 elif name == "TZOFFSETFROM": | |
| 1422 if parms: | |
| 1423 raise ValueError( | |
| 1424 "unsupported %s parm: %s " % (name, parms[0])) | |
| 1425 tzoffsetfrom = self._parse_offset(value) | |
| 1426 elif name == "TZOFFSETTO": | |
| 1427 if parms: | |
| 1428 raise ValueError( | |
| 1429 "unsupported TZOFFSETTO parm: "+parms[0]) | |
| 1430 tzoffsetto = self._parse_offset(value) | |
| 1431 elif name == "TZNAME": | |
| 1432 if parms: | |
| 1433 raise ValueError( | |
| 1434 "unsupported TZNAME parm: "+parms[0]) | |
| 1435 tzname = value | |
| 1436 elif name == "COMMENT": | |
| 1437 pass | |
| 1438 else: | |
| 1439 raise ValueError("unsupported property: "+name) | |
| 1440 else: | |
| 1441 if name == "TZID": | |
| 1442 if parms: | |
| 1443 raise ValueError( | |
| 1444 "unsupported TZID parm: "+parms[0]) | |
| 1445 tzid = value | |
| 1446 elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"): | |
| 1447 pass | |
| 1448 else: | |
| 1449 raise ValueError("unsupported property: "+name) | |
| 1450 elif name == "BEGIN" and value == "VTIMEZONE": | |
| 1451 tzid = None | |
| 1452 comps = [] | |
| 1453 invtz = True | |
| 1454 | |
| 1455 def __repr__(self): | |
| 1456 return "%s(%s)" % (self.__class__.__name__, repr(self._s)) | |
| 1457 | |
| 1458 | |
| 1459 if sys.platform != "win32": | |
| 1460 TZFILES = ["/etc/localtime", "localtime"] | |
| 1461 TZPATHS = ["/usr/share/zoneinfo", | |
| 1462 "/usr/lib/zoneinfo", | |
| 1463 "/usr/share/lib/zoneinfo", | |
| 1464 "/etc/zoneinfo"] | |
| 1465 else: | |
| 1466 TZFILES = [] | |
| 1467 TZPATHS = [] | |
| 1468 | |
| 1469 | |
| 1470 def __get_gettz(): | |
| 1471 tzlocal_classes = (tzlocal,) | |
| 1472 if tzwinlocal is not None: | |
| 1473 tzlocal_classes += (tzwinlocal,) | |
| 1474 | |
| 1475 class GettzFunc(object): | |
| 1476 """ | |
| 1477 Retrieve a time zone object from a string representation | |
| 1478 | |
| 1479 This function is intended to retrieve the :py:class:`tzinfo` subclass | |
| 1480 that best represents the time zone that would be used if a POSIX | |
| 1481 `TZ variable`_ were set to the same value. | |
| 1482 | |
| 1483 If no argument or an empty string is passed to ``gettz``, local time | |
| 1484 is returned: | |
| 1485 | |
| 1486 .. code-block:: python3 | |
| 1487 | |
| 1488 >>> gettz() | |
| 1489 tzfile('/etc/localtime') | |
| 1490 | |
| 1491 This function is also the preferred way to map IANA tz database keys | |
| 1492 to :class:`tzfile` objects: | |
| 1493 | |
| 1494 .. code-block:: python3 | |
| 1495 | |
| 1496 >>> gettz('Pacific/Kiritimati') | |
| 1497 tzfile('/usr/share/zoneinfo/Pacific/Kiritimati') | |
| 1498 | |
| 1499 On Windows, the standard is extended to include the Windows-specific | |
| 1500 zone names provided by the operating system: | |
| 1501 | |
| 1502 .. code-block:: python3 | |
| 1503 | |
| 1504 >>> gettz('Egypt Standard Time') | |
| 1505 tzwin('Egypt Standard Time') | |
| 1506 | |
| 1507 Passing a GNU ``TZ`` style string time zone specification returns a | |
| 1508 :class:`tzstr` object: | |
| 1509 | |
| 1510 .. code-block:: python3 | |
| 1511 | |
| 1512 >>> gettz('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3') | |
| 1513 tzstr('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3') | |
| 1514 | |
| 1515 :param name: | |
| 1516 A time zone name (IANA, or, on Windows, Windows keys), location of | |
| 1517 a ``tzfile(5)`` zoneinfo file or ``TZ`` variable style time zone | |
| 1518 specifier. An empty string, no argument or ``None`` is interpreted | |
| 1519 as local time. | |
| 1520 | |
| 1521 :return: | |
| 1522 Returns an instance of one of ``dateutil``'s :py:class:`tzinfo` | |
| 1523 subclasses. | |
| 1524 | |
| 1525 .. versionchanged:: 2.7.0 | |
| 1526 | |
| 1527 After version 2.7.0, any two calls to ``gettz`` using the same | |
| 1528 input strings will return the same object: | |
| 1529 | |
| 1530 .. code-block:: python3 | |
| 1531 | |
| 1532 >>> tz.gettz('America/Chicago') is tz.gettz('America/Chicago') | |
| 1533 True | |
| 1534 | |
| 1535 In addition to improving performance, this ensures that | |
| 1536 `"same zone" semantics`_ are used for datetimes in the same zone. | |
| 1537 | |
| 1538 | |
| 1539 .. _`TZ variable`: | |
| 1540 https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html | |
| 1541 | |
| 1542 .. _`"same zone" semantics`: | |
| 1543 https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html | |
| 1544 """ | |
| 1545 def __init__(self): | |
| 1546 | |
| 1547 self.__instances = weakref.WeakValueDictionary() | |
| 1548 self.__strong_cache_size = 8 | |
| 1549 self.__strong_cache = OrderedDict() | |
| 1550 self._cache_lock = _thread.allocate_lock() | |
| 1551 | |
| 1552 def __call__(self, name=None): | |
| 1553 with self._cache_lock: | |
| 1554 rv = self.__instances.get(name, None) | |
| 1555 | |
| 1556 if rv is None: | |
| 1557 rv = self.nocache(name=name) | |
| 1558 if not (name is None | |
| 1559 or isinstance(rv, tzlocal_classes) | |
| 1560 or rv is None): | |
| 1561 # tzlocal is slightly more complicated than the other | |
| 1562 # time zone providers because it depends on environment | |
| 1563 # at construction time, so don't cache that. | |
| 1564 # | |
| 1565 # We also cannot store weak references to None, so we | |
| 1566 # will also not store that. | |
| 1567 self.__instances[name] = rv | |
| 1568 else: | |
| 1569 # No need for strong caching, return immediately | |
| 1570 return rv | |
| 1571 | |
| 1572 self.__strong_cache[name] = self.__strong_cache.pop(name, rv) | |
| 1573 | |
| 1574 if len(self.__strong_cache) > self.__strong_cache_size: | |
| 1575 self.__strong_cache.popitem(last=False) | |
| 1576 | |
| 1577 return rv | |
| 1578 | |
| 1579 def set_cache_size(self, size): | |
| 1580 with self._cache_lock: | |
| 1581 self.__strong_cache_size = size | |
| 1582 while len(self.__strong_cache) > size: | |
| 1583 self.__strong_cache.popitem(last=False) | |
| 1584 | |
| 1585 def cache_clear(self): | |
| 1586 with self._cache_lock: | |
| 1587 self.__instances = weakref.WeakValueDictionary() | |
| 1588 self.__strong_cache.clear() | |
| 1589 | |
| 1590 @staticmethod | |
| 1591 def nocache(name=None): | |
| 1592 """A non-cached version of gettz""" | |
| 1593 tz = None | |
| 1594 if not name: | |
| 1595 try: | |
| 1596 name = os.environ["TZ"] | |
| 1597 except KeyError: | |
| 1598 pass | |
| 1599 if name is None or name == ":": | |
| 1600 for filepath in TZFILES: | |
| 1601 if not os.path.isabs(filepath): | |
| 1602 filename = filepath | |
| 1603 for path in TZPATHS: | |
| 1604 filepath = os.path.join(path, filename) | |
| 1605 if os.path.isfile(filepath): | |
| 1606 break | |
| 1607 else: | |
| 1608 continue | |
| 1609 if os.path.isfile(filepath): | |
| 1610 try: | |
| 1611 tz = tzfile(filepath) | |
| 1612 break | |
| 1613 except (IOError, OSError, ValueError): | |
| 1614 pass | |
| 1615 else: | |
| 1616 tz = tzlocal() | |
| 1617 else: | |
| 1618 try: | |
| 1619 if name.startswith(":"): | |
| 1620 name = name[1:] | |
| 1621 except TypeError as e: | |
| 1622 if isinstance(name, bytes): | |
| 1623 new_msg = "gettz argument should be str, not bytes" | |
| 1624 six.raise_from(TypeError(new_msg), e) | |
| 1625 else: | |
| 1626 raise | |
| 1627 if os.path.isabs(name): | |
| 1628 if os.path.isfile(name): | |
| 1629 tz = tzfile(name) | |
| 1630 else: | |
| 1631 tz = None | |
| 1632 else: | |
| 1633 for path in TZPATHS: | |
| 1634 filepath = os.path.join(path, name) | |
| 1635 if not os.path.isfile(filepath): | |
| 1636 filepath = filepath.replace(' ', '_') | |
| 1637 if not os.path.isfile(filepath): | |
| 1638 continue | |
| 1639 try: | |
| 1640 tz = tzfile(filepath) | |
| 1641 break | |
| 1642 except (IOError, OSError, ValueError): | |
| 1643 pass | |
| 1644 else: | |
| 1645 tz = None | |
| 1646 if tzwin is not None: | |
| 1647 try: | |
| 1648 tz = tzwin(name) | |
| 1649 except (WindowsError, UnicodeEncodeError): | |
| 1650 # UnicodeEncodeError is for Python 2.7 compat | |
| 1651 tz = None | |
| 1652 | |
| 1653 if not tz: | |
| 1654 from dateutil.zoneinfo import get_zonefile_instance | |
| 1655 tz = get_zonefile_instance().get(name) | |
| 1656 | |
| 1657 if not tz: | |
| 1658 for c in name: | |
| 1659 # name is not a tzstr unless it has at least | |
| 1660 # one offset. For short values of "name", an | |
| 1661 # explicit for loop seems to be the fastest way | |
| 1662 # To determine if a string contains a digit | |
| 1663 if c in "0123456789": | |
| 1664 try: | |
| 1665 tz = tzstr(name) | |
| 1666 except ValueError: | |
| 1667 pass | |
| 1668 break | |
| 1669 else: | |
| 1670 if name in ("GMT", "UTC"): | |
| 1671 tz = UTC | |
| 1672 elif name in time.tzname: | |
| 1673 tz = tzlocal() | |
| 1674 return tz | |
| 1675 | |
| 1676 return GettzFunc() | |
| 1677 | |
| 1678 | |
| 1679 gettz = __get_gettz() | |
| 1680 del __get_gettz | |
| 1681 | |
| 1682 | |
| 1683 def datetime_exists(dt, tz=None): | |
| 1684 """ | |
| 1685 Given a datetime and a time zone, determine whether or not a given datetime | |
| 1686 would fall in a gap. | |
| 1687 | |
| 1688 :param dt: | |
| 1689 A :class:`datetime.datetime` (whose time zone will be ignored if ``tz`` | |
| 1690 is provided.) | |
| 1691 | |
| 1692 :param tz: | |
| 1693 A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If | |
| 1694 ``None`` or not provided, the datetime's own time zone will be used. | |
| 1695 | |
| 1696 :return: | |
| 1697 Returns a boolean value whether or not the "wall time" exists in | |
| 1698 ``tz``. | |
| 1699 | |
| 1700 .. versionadded:: 2.7.0 | |
| 1701 """ | |
| 1702 if tz is None: | |
| 1703 if dt.tzinfo is None: | |
| 1704 raise ValueError('Datetime is naive and no time zone provided.') | |
| 1705 tz = dt.tzinfo | |
| 1706 | |
| 1707 dt = dt.replace(tzinfo=None) | |
| 1708 | |
| 1709 # This is essentially a test of whether or not the datetime can survive | |
| 1710 # a round trip to UTC. | |
| 1711 dt_rt = dt.replace(tzinfo=tz).astimezone(UTC).astimezone(tz) | |
| 1712 dt_rt = dt_rt.replace(tzinfo=None) | |
| 1713 | |
| 1714 return dt == dt_rt | |
| 1715 | |
| 1716 | |
| 1717 def datetime_ambiguous(dt, tz=None): | |
| 1718 """ | |
| 1719 Given a datetime and a time zone, determine whether or not a given datetime | |
| 1720 is ambiguous (i.e if there are two times differentiated only by their DST | |
| 1721 status). | |
| 1722 | |
| 1723 :param dt: | |
| 1724 A :class:`datetime.datetime` (whose time zone will be ignored if ``tz`` | |
| 1725 is provided.) | |
| 1726 | |
| 1727 :param tz: | |
| 1728 A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If | |
| 1729 ``None`` or not provided, the datetime's own time zone will be used. | |
| 1730 | |
| 1731 :return: | |
| 1732 Returns a boolean value whether or not the "wall time" is ambiguous in | |
| 1733 ``tz``. | |
| 1734 | |
| 1735 .. versionadded:: 2.6.0 | |
| 1736 """ | |
| 1737 if tz is None: | |
| 1738 if dt.tzinfo is None: | |
| 1739 raise ValueError('Datetime is naive and no time zone provided.') | |
| 1740 | |
| 1741 tz = dt.tzinfo | |
| 1742 | |
| 1743 # If a time zone defines its own "is_ambiguous" function, we'll use that. | |
| 1744 is_ambiguous_fn = getattr(tz, 'is_ambiguous', None) | |
| 1745 if is_ambiguous_fn is not None: | |
| 1746 try: | |
| 1747 return tz.is_ambiguous(dt) | |
| 1748 except Exception: | |
| 1749 pass | |
| 1750 | |
| 1751 # If it doesn't come out and tell us it's ambiguous, we'll just check if | |
| 1752 # the fold attribute has any effect on this particular date and time. | |
| 1753 dt = dt.replace(tzinfo=tz) | |
| 1754 wall_0 = enfold(dt, fold=0) | |
| 1755 wall_1 = enfold(dt, fold=1) | |
| 1756 | |
| 1757 same_offset = wall_0.utcoffset() == wall_1.utcoffset() | |
| 1758 same_dst = wall_0.dst() == wall_1.dst() | |
| 1759 | |
| 1760 return not (same_offset and same_dst) | |
| 1761 | |
| 1762 | |
| 1763 def resolve_imaginary(dt): | |
| 1764 """ | |
| 1765 Given a datetime that may be imaginary, return an existing datetime. | |
| 1766 | |
| 1767 This function assumes that an imaginary datetime represents what the | |
| 1768 wall time would be in a zone had the offset transition not occurred, so | |
| 1769 it will always fall forward by the transition's change in offset. | |
| 1770 | |
| 1771 .. doctest:: | |
| 1772 | |
| 1773 >>> from dateutil import tz | |
| 1774 >>> from datetime import datetime | |
| 1775 >>> NYC = tz.gettz('America/New_York') | |
| 1776 >>> print(tz.resolve_imaginary(datetime(2017, 3, 12, 2, 30, tzinfo=NYC))) | |
| 1777 2017-03-12 03:30:00-04:00 | |
| 1778 | |
| 1779 >>> KIR = tz.gettz('Pacific/Kiritimati') | |
| 1780 >>> print(tz.resolve_imaginary(datetime(1995, 1, 1, 12, 30, tzinfo=KIR))) | |
| 1781 1995-01-02 12:30:00+14:00 | |
| 1782 | |
| 1783 As a note, :func:`datetime.astimezone` is guaranteed to produce a valid, | |
| 1784 existing datetime, so a round-trip to and from UTC is sufficient to get | |
| 1785 an extant datetime, however, this generally "falls back" to an earlier time | |
| 1786 rather than falling forward to the STD side (though no guarantees are made | |
| 1787 about this behavior). | |
| 1788 | |
| 1789 :param dt: | |
| 1790 A :class:`datetime.datetime` which may or may not exist. | |
| 1791 | |
| 1792 :return: | |
| 1793 Returns an existing :class:`datetime.datetime`. If ``dt`` was not | |
| 1794 imaginary, the datetime returned is guaranteed to be the same object | |
| 1795 passed to the function. | |
| 1796 | |
| 1797 .. versionadded:: 2.7.0 | |
| 1798 """ | |
| 1799 if dt.tzinfo is not None and not datetime_exists(dt): | |
| 1800 | |
| 1801 curr_offset = (dt + datetime.timedelta(hours=24)).utcoffset() | |
| 1802 old_offset = (dt - datetime.timedelta(hours=24)).utcoffset() | |
| 1803 | |
| 1804 dt += curr_offset - old_offset | |
| 1805 | |
| 1806 return dt | |
| 1807 | |
| 1808 | |
| 1809 def _datetime_to_timestamp(dt): | |
| 1810 """ | |
| 1811 Convert a :class:`datetime.datetime` object to an epoch timestamp in | |
| 1812 seconds since January 1, 1970, ignoring the time zone. | |
| 1813 """ | |
| 1814 return (dt.replace(tzinfo=None) - EPOCH).total_seconds() | |
| 1815 | |
| 1816 | |
| 1817 if sys.version_info >= (3, 6): | |
| 1818 def _get_supported_offset(second_offset): | |
| 1819 return second_offset | |
| 1820 else: | |
| 1821 def _get_supported_offset(second_offset): | |
| 1822 # For python pre-3.6, round to full-minutes if that's not the case. | |
| 1823 # Python's datetime doesn't accept sub-minute timezones. Check | |
| 1824 # http://python.org/sf/1447945 or https://bugs.python.org/issue5288 | |
| 1825 # for some information. | |
| 1826 old_offset = second_offset | |
| 1827 calculated_offset = 60 * ((second_offset + 30) // 60) | |
| 1828 return calculated_offset | |
| 1829 | |
| 1830 | |
| 1831 try: | |
| 1832 # Python 3.7 feature | |
| 1833 from contextlib import nullcontext as _nullcontext | |
| 1834 except ImportError: | |
| 1835 class _nullcontext(object): | |
| 1836 """ | |
| 1837 Class for wrapping contexts so that they are passed through in a | |
| 1838 with statement. | |
| 1839 """ | |
| 1840 def __init__(self, context): | |
| 1841 self.context = context | |
| 1842 | |
| 1843 def __enter__(self): | |
| 1844 return self.context | |
| 1845 | |
| 1846 def __exit__(*args, **kwargs): | |
| 1847 pass | |
| 1848 | |
| 1849 # vim:ts=4:sw=4:et |
