Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/dateutil/tz/tz.py @ 0:26e78fe6e8c4 draft
"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author | shellac |
---|---|
date | Sat, 02 May 2020 07:14:21 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:26e78fe6e8c4 |
---|---|
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 |