Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/dateutil/tz/tz.py: 38%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2This module offers timezone implementations subclassing the abstract
3:py:class:`datetime.tzinfo` type. There are classes to handle tzfile format
4files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`,
5etc), TZ environment string (in all known formats), given ranges (with help
6from relative deltas), local machine timezone, fixed offset timezone, and UTC
7timezone.
8"""
10import _thread
11import contextlib
12import datetime
13import os
14import time
15import warnings
16import weakref
17from collections import OrderedDict
19from .. import _tzdata_impl
20from . import _tzpath
21from ._common import _tzinfo, _validate_fromutc_inputs, enfold, tzrangebase
22from ._factories import _TzOffsetFactory, _TzSingleton, _TzStrFactory
23from ._tzfile import tzfile
25try:
26 from .win import tzwin, tzwinlocal
27except ImportError:
28 tzwin = tzwinlocal = None
30ZERO = datetime.timedelta(0)
31EPOCH = datetime.datetime(1970, 1, 1, 0, 0)
34class tzutc(datetime.tzinfo, metaclass=_TzSingleton):
35 """
36 This is a tzinfo object that represents the UTC time zone.
38 **Examples:**
40 .. doctest::
42 >>> from datetime import *
43 >>> from dateutil.tz import *
45 >>> datetime.now()
46 datetime.datetime(2003, 9, 27, 9, 40, 1, 521290)
48 >>> datetime.now(tzutc())
49 datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc())
51 >>> datetime.now(tzutc()).tzname()
52 'UTC'
54 .. versionchanged:: 2.7.0
55 ``tzutc()`` is now a singleton, so the result of ``tzutc()`` will
56 always return the same object.
58 .. doctest::
60 >>> from dateutil.tz import tzutc, UTC
61 >>> tzutc() is tzutc()
62 True
63 >>> tzutc() is UTC
64 True
65 """
66 def utcoffset(self, dt):
67 return ZERO
69 def dst(self, dt):
70 return ZERO
72 def tzname(self, dt):
73 return "UTC"
75 def is_ambiguous(self, dt):
76 """
77 Whether or not the "wall time" of a given datetime is ambiguous in this
78 zone.
80 :param dt:
81 A :py:class:`datetime.datetime`, naive or time zone aware.
84 :return:
85 Returns ``True`` if ambiguous, ``False`` otherwise.
87 .. versionadded:: 2.6.0
88 """
89 return False
91 @_validate_fromutc_inputs
92 def fromutc(self, dt):
93 """
94 Fast track version of fromutc() returns the original ``dt`` object for
95 any valid :py:class:`datetime.datetime` object.
96 """
97 return dt
99 def __eq__(self, other):
100 if not isinstance(other, (tzutc, tzoffset)):
101 return NotImplemented
103 return (isinstance(other, tzutc) or
104 (isinstance(other, tzoffset) and other._offset == ZERO))
106 __hash__ = None
108 def __repr__(self):
109 return "%s()" % self.__class__.__name__
111 __reduce__ = object.__reduce__
114#: Convenience constant providing a :class:`tzutc()` instance
115#:
116#: .. versionadded:: 2.7.0
117UTC = tzutc()
120class tzoffset(datetime.tzinfo, metaclass=_TzOffsetFactory):
121 """
122 A simple class for representing a fixed offset from UTC.
124 :param name:
125 The timezone name, to be returned when ``tzname()`` is called.
126 :param offset:
127 The time zone offset in seconds, or (since version 2.6.0, represented
128 as a :py:class:`datetime.timedelta` object).
129 """
130 def __init__(self, name, offset):
131 self._name = name
133 try:
134 # Allow a timedelta
135 offset = offset.total_seconds()
136 except (TypeError, AttributeError):
137 pass
139 self._offset = datetime.timedelta(seconds=offset)
141 def utcoffset(self, dt):
142 return self._offset
144 def dst(self, dt):
145 return ZERO
147 def tzname(self, dt):
148 return self._name
150 @_validate_fromutc_inputs
151 def fromutc(self, dt):
152 return dt + self._offset
154 def is_ambiguous(self, dt):
155 """
156 Whether or not the "wall time" of a given datetime is ambiguous in this
157 zone.
159 :param dt:
160 A :py:class:`datetime.datetime`, naive or time zone aware.
161 :return:
162 Returns ``True`` if ambiguous, ``False`` otherwise.
164 .. versionadded:: 2.6.0
165 """
166 return False
168 def __eq__(self, other):
169 if not isinstance(other, tzoffset):
170 return NotImplemented
172 return self._offset == other._offset
174 __hash__ = None
176 def __repr__(self):
177 return "%s(%s, %s)" % (self.__class__.__name__,
178 repr(self._name),
179 int(self._offset.total_seconds()))
181 __reduce__ = object.__reduce__
184class tzlocal(_tzinfo):
185 """
186 A :class:`tzinfo` subclass built around the ``time`` timezone functions.
187 """
188 def __init__(self):
189 super().__init__()
191 self._std_offset = datetime.timedelta(seconds=-time.timezone)
192 if time.daylight:
193 self._dst_offset = datetime.timedelta(seconds=-time.altzone)
194 else:
195 self._dst_offset = self._std_offset
197 self._dst_saved = self._dst_offset - self._std_offset
198 self._hasdst = bool(self._dst_saved)
199 self._tznames = tuple(time.tzname)
201 def utcoffset(self, dt):
202 if dt is None and self._hasdst:
203 return None
205 if self._isdst(dt):
206 return self._dst_offset
207 else:
208 return self._std_offset
210 def dst(self, dt):
211 if dt is None and self._hasdst:
212 return None
214 if self._isdst(dt):
215 return self._dst_offset - self._std_offset
216 else:
217 return ZERO
219 def tzname(self, dt):
220 return self._tznames[self._isdst(dt)]
222 def is_ambiguous(self, dt):
223 """
224 Whether or not the "wall time" of a given datetime is ambiguous in this
225 zone.
227 :param dt:
228 A :py:class:`datetime.datetime`, naive or time zone aware.
231 :return:
232 Returns ``True`` if ambiguous, ``False`` otherwise.
234 .. versionadded:: 2.6.0
235 """
236 naive_dst = self._naive_is_dst(dt)
237 return (not naive_dst and
238 (naive_dst != self._naive_is_dst(dt - self._dst_saved)))
240 def _naive_is_dst(self, dt):
241 timestamp = _datetime_to_timestamp(dt)
242 return time.localtime(timestamp + time.timezone).tm_isdst
244 def _isdst(self, dt, fold_naive=True):
245 # We can't use mktime here. It is unstable when deciding if
246 # the hour near to a change is DST or not.
247 #
248 # timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour,
249 # dt.minute, dt.second, dt.weekday(), 0, -1))
250 # return time.localtime(timestamp).tm_isdst
251 #
252 # The code above yields the following result:
253 #
254 # >>> import tz, datetime
255 # >>> t = tz.tzlocal()
256 # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
257 # 'BRDT'
258 # >>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()
259 # 'BRST'
260 # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
261 # 'BRST'
262 # >>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()
263 # 'BRDT'
264 # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
265 # 'BRDT'
266 #
267 # Here is a more stable implementation:
268 #
269 if not self._hasdst:
270 return False
272 # Check for ambiguous times:
273 dstval = self._naive_is_dst(dt)
275 if self.is_ambiguous(dt):
276 return not dt.fold
278 return dstval
280 def __eq__(self, other):
281 if isinstance(other, tzlocal):
282 return (self._std_offset == other._std_offset and
283 self._dst_offset == other._dst_offset)
284 elif isinstance(other, tzutc):
285 return (not self._hasdst and
286 self._tznames[0] in {'UTC', 'GMT'} and
287 self._std_offset == ZERO)
288 elif isinstance(other, tzoffset):
289 return (not self._hasdst and
290 self._tznames[0] == other._name and
291 self._std_offset == other._offset)
292 else:
293 return NotImplemented
295 __hash__ = None
297 def __repr__(self):
298 return "%s()" % self.__class__.__name__
300 __reduce__ = object.__reduce__
303class tzrange(tzrangebase):
304 """
305 The ``tzrange`` object is a time zone specified by a set of offsets and
306 abbreviations, equivalent to the way the ``TZ`` variable can be specified
307 in POSIX-like systems, but using Python delta objects to specify DST
308 start, end and offsets.
310 :param stdabbr:
311 The abbreviation for standard time (e.g. ``'EST'``).
313 :param stdoffset:
314 An integer or :class:`datetime.timedelta` object or equivalent
315 specifying the base offset from UTC.
317 If unspecified, +00:00 is used.
319 :param dstabbr:
320 The abbreviation for DST / "Summer" time (e.g. ``'EDT'``).
322 If specified, with no other DST information, DST is assumed to occur
323 and the default behavior or ``dstoffset``, ``start`` and ``end`` is
324 used. If unspecified and no other DST information is specified, it
325 is assumed that this zone has no DST.
327 If this is unspecified and other DST information is *is* specified,
328 DST occurs in the zone but the time zone abbreviation is left
329 unchanged.
331 :param dstoffset:
332 A an integer or :class:`datetime.timedelta` object or equivalent
333 specifying the UTC offset during DST. If unspecified and any other DST
334 information is specified, it is assumed to be the STD offset +1 hour.
336 :param start:
337 A :class:`relativedelta.relativedelta` object or equivalent specifying
338 the time and time of year that daylight savings time starts. To
339 specify, for example, that DST starts at 2AM on the 2nd Sunday in
340 March, pass:
342 ``relativedelta(hours=2, month=3, day=1, weekday=SU(+2))``
344 If unspecified and any other DST information is specified, the default
345 value is 2 AM on the first Sunday in April.
347 :param end:
348 A :class:`relativedelta.relativedelta` object or equivalent
349 representing the time and time of year that daylight savings time
350 ends, with the same specification method as in ``start``. One note is
351 that this should point to the first time in the *standard* zone, so if
352 a transition occurs at 2AM in the DST zone and the clocks are set back
353 1 hour to 1AM, set the ``hours`` parameter to +1.
356 **Examples:**
358 .. testsetup:: tzrange
360 from dateutil.tz import tzrange, tzstr
362 .. doctest:: tzrange
364 >>> tzstr('EST5EDT') == tzrange("EST", -18000, "EDT")
365 True
367 >>> from dateutil.relativedelta import *
368 >>> range1 = tzrange("EST", -18000, "EDT")
369 >>> range2 = tzrange("EST", -18000, "EDT", -14400,
370 ... relativedelta(hours=+2, month=4, day=1,
371 ... weekday=SU(+1)),
372 ... relativedelta(hours=+1, month=10, day=31,
373 ... weekday=SU(-1)))
374 >>> tzstr('EST5EDT') == range1 == range2
375 True
377 """
378 def __init__(self, stdabbr, stdoffset=None,
379 dstabbr=None, dstoffset=None,
380 start=None, end=None):
382 global relativedelta
383 from dateutil import relativedelta
385 self._std_abbr = stdabbr
386 self._dst_abbr = dstabbr
388 try:
389 stdoffset = stdoffset.total_seconds()
390 except (TypeError, AttributeError):
391 pass
393 try:
394 dstoffset = dstoffset.total_seconds()
395 except (TypeError, AttributeError):
396 pass
398 if stdoffset is not None:
399 self._std_offset = datetime.timedelta(seconds=stdoffset)
400 else:
401 self._std_offset = ZERO
403 if dstoffset is not None:
404 self._dst_offset = datetime.timedelta(seconds=dstoffset)
405 elif dstabbr and stdoffset is not None:
406 self._dst_offset = self._std_offset + datetime.timedelta(hours=+1)
407 else:
408 self._dst_offset = ZERO
410 if dstabbr and start is None:
411 self._start_delta = relativedelta.relativedelta(
412 hours=+2, month=4, day=1, weekday=relativedelta.SU(+1))
413 else:
414 self._start_delta = start
416 if dstabbr and end is None:
417 self._end_delta = relativedelta.relativedelta(
418 hours=+1, month=10, day=31, weekday=relativedelta.SU(-1))
419 else:
420 self._end_delta = end
422 self._dst_base_offset_ = self._dst_offset - self._std_offset
423 self.hasdst = bool(self._start_delta)
425 def transitions(self, year):
426 """
427 For a given year, get the DST on and off transition times, expressed
428 always on the standard time side. For zones with no transitions, this
429 function returns ``None``.
431 :param year:
432 The year whose transitions you would like to query.
434 :return:
435 Returns a :class:`tuple` of :class:`datetime.datetime` objects,
436 ``(dston, dstoff)`` for zones with an annual DST transition, or
437 ``None`` for fixed offset zones.
438 """
439 if not self.hasdst:
440 return None
442 base_year = datetime.datetime(year, 1, 1)
444 start = base_year + self._start_delta
445 end = base_year + self._end_delta
447 return (start, end)
449 def __eq__(self, other):
450 if not isinstance(other, tzrange):
451 return NotImplemented
453 return (self._std_abbr == other._std_abbr and
454 self._dst_abbr == other._dst_abbr and
455 self._std_offset == other._std_offset and
456 self._dst_offset == other._dst_offset and
457 self._start_delta == other._start_delta and
458 self._end_delta == other._end_delta)
460 @property
461 def _dst_base_offset(self):
462 return self._dst_base_offset_
465class tzstr(tzrange, metaclass=_TzStrFactory):
466 """
467 ``tzstr`` objects are time zone objects specified by a time-zone string as
468 it would be passed to a ``TZ`` variable on POSIX-style systems (see
469 the `GNU C Library: TZ Variable`_ for more details).
471 There is one notable exception, which is that POSIX-style time zones use an
472 inverted offset format, so normally ``GMT+3`` would be parsed as an offset
473 3 hours *behind* GMT. The ``tzstr`` time zone object will parse this as an
474 offset 3 hours *ahead* of GMT. If you would like to maintain the POSIX
475 behavior, pass a ``True`` value to ``posix_offset``.
477 The :class:`tzrange` object provides the same functionality, but is
478 specified using :class:`relativedelta.relativedelta` objects. rather than
479 strings.
481 :param s:
482 A time zone string in ``TZ`` variable format. This can be a
483 :class:`bytes`, :class:`str` or a stream emitting Unicode characters
484 (e.g. :class:`StringIO`).
486 :param posix_offset:
487 Optional. If set to ``True``, interpret strings such as ``GMT+3`` or
488 ``UTC+3`` as being 3 hours *behind* UTC rather than ahead, per the
489 POSIX standard.
491 .. caution::
493 Prior to version 2.7.0, this function also supported time zones
494 in the format:
496 * ``EST5EDT,4,0,6,7200,10,0,26,7200,3600``
497 * ``EST5EDT,4,1,0,7200,10,-1,0,7200,3600``
499 This format is non-standard and has been deprecated; this function
500 will raise a :class:`DeprecatedTZFormatWarning` until
501 support is removed in a future version.
503 .. _`GNU C Library: TZ Variable`:
504 https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
505 """
506 def __init__(self, s, posix_offset=False):
507 global parser
508 from dateutil.parser import _parser as parser
510 self._s = s
512 res = parser._parsetz(s)
513 if res is None or res.any_unused_tokens:
514 raise ValueError("unknown string format")
516 # Here we break the compatibility with the TZ variable handling.
517 # GMT-3 actually *means* the timezone -3.
518 if res.stdabbr in ("GMT", "UTC") and not posix_offset:
519 res.stdoffset *= -1
521 # We must initialize it first, since _delta() needs
522 # _std_offset and _dst_offset set. Use False in start/end
523 # to avoid building it two times.
524 tzrange.__init__(self, res.stdabbr, res.stdoffset,
525 res.dstabbr, res.dstoffset,
526 start=False, end=False)
528 if not res.dstabbr:
529 self._start_delta = None
530 self._end_delta = None
531 else:
532 self._start_delta = self._delta(res.start)
533 if self._start_delta:
534 self._end_delta = self._delta(res.end, isend=1)
536 self.hasdst = bool(self._start_delta)
538 def _delta(self, x, isend=0):
539 from dateutil import relativedelta
540 kwargs = {}
541 if x.month is not None:
542 kwargs["month"] = x.month
543 if x.weekday is not None:
544 kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week)
545 if x.week > 0:
546 kwargs["day"] = 1
547 else:
548 kwargs["day"] = 31
549 elif x.day:
550 kwargs["day"] = x.day
551 elif x.yday is not None:
552 kwargs["yearday"] = x.yday
553 elif x.jyday is not None:
554 kwargs["nlyearday"] = x.jyday
555 if not kwargs:
556 # Default is to start on first sunday of april, and end
557 # on last sunday of october.
558 if not isend:
559 kwargs["month"] = 4
560 kwargs["day"] = 1
561 kwargs["weekday"] = relativedelta.SU(+1)
562 else:
563 kwargs["month"] = 10
564 kwargs["day"] = 31
565 kwargs["weekday"] = relativedelta.SU(-1)
566 if x.time is not None:
567 kwargs["seconds"] = x.time
568 else:
569 # Default is 2AM.
570 kwargs["seconds"] = 7200
571 if isend:
572 # Convert to standard time, to follow the documented way
573 # of working with the extra hour. See the documentation
574 # of the tzinfo class.
575 delta = self._dst_offset - self._std_offset
576 kwargs["seconds"] -= delta.seconds + delta.days * 86400
577 return relativedelta.relativedelta(**kwargs)
579 def __repr__(self):
580 return "%s(%s)" % (self.__class__.__name__, repr(self._s))
583class _tzicalvtzcomp:
584 def __init__(self, tzoffsetfrom, tzoffsetto, isdst,
585 tzname=None, rrule=None):
586 self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom)
587 self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto)
588 self.tzoffsetdiff = self.tzoffsetto - self.tzoffsetfrom
589 self.isdst = isdst
590 self.tzname = tzname
591 self.rrule = rrule
594class _tzicalvtz(_tzinfo):
595 def __init__(self, tzid, comps=[]):
596 super().__init__()
598 self._tzid = tzid
599 self._comps = comps
600 self._cachedate = []
601 self._cachecomp = []
602 self._cache_lock = _thread.allocate_lock()
604 def _find_comp(self, dt):
605 if len(self._comps) == 1:
606 return self._comps[0]
608 dt = dt.replace(tzinfo=None)
610 try:
611 with self._cache_lock:
612 return self._cachecomp[self._cachedate.index((dt, dt.fold))]
613 except ValueError:
614 pass
616 lastcompdt = None
617 lastcomp = None
619 for comp in self._comps:
620 compdt = self._find_compdt(comp, dt)
622 if compdt and (not lastcompdt or lastcompdt < compdt):
623 lastcompdt = compdt
624 lastcomp = comp
626 if not lastcomp:
627 # RFC says nothing about what to do when a given
628 # time is before the first onset date. We'll look for the
629 # first standard component, or the first component, if
630 # none is found.
631 for comp in self._comps:
632 if not comp.isdst:
633 lastcomp = comp
634 break
635 else:
636 lastcomp = comp[0]
638 with self._cache_lock:
639 self._cachedate.insert(0, (dt, dt.fold))
640 self._cachecomp.insert(0, lastcomp)
642 if len(self._cachedate) > 10:
643 self._cachedate.pop()
644 self._cachecomp.pop()
646 return lastcomp
648 def _find_compdt(self, comp, dt):
649 if comp.tzoffsetdiff < ZERO and dt.fold:
650 dt -= comp.tzoffsetdiff
652 compdt = comp.rrule.before(dt, inc=True)
654 return compdt
656 def utcoffset(self, dt):
657 if dt is None:
658 return None
660 return self._find_comp(dt).tzoffsetto
662 def dst(self, dt):
663 comp = self._find_comp(dt)
664 if comp.isdst:
665 return comp.tzoffsetdiff
666 else:
667 return ZERO
669 def tzname(self, dt):
670 return self._find_comp(dt).tzname
672 def __repr__(self):
673 return "<tzicalvtz %s>" % repr(self._tzid)
675 __reduce__ = object.__reduce__
678class tzical:
679 """
680 This object is designed to parse an iCalendar-style ``VTIMEZONE`` structure
681 as set out in `RFC 5545`_ Section 4.6.5 into one or more `tzinfo` objects.
683 :param `fileobj`:
684 A file or stream in iCalendar format, which should be UTF-8 encoded
685 with CRLF endings.
687 .. _`RFC 5545`: https://tools.ietf.org/html/rfc5545
688 """
689 def __init__(self, fileobj):
690 global rrule
691 from dateutil import rrule
693 if isinstance(fileobj, str):
694 self._s = fileobj
695 # ical should be encoded in UTF-8 with CRLF
696 fileobj = open(fileobj, 'r')
697 else:
698 self._s = getattr(fileobj, 'name', repr(fileobj))
699 fileobj = contextlib.nullcontext(fileobj)
701 self._vtz = {}
703 with fileobj as fobj:
704 self._parse_rfc(fobj.read())
706 def keys(self):
707 """
708 Retrieves the available time zones as a list.
709 """
710 return list(self._vtz.keys())
712 def get(self, tzid=None):
713 """
714 Retrieve a :py:class:`datetime.tzinfo` object by its ``tzid``.
716 :param tzid:
717 If there is exactly one time zone available, omitting ``tzid``
718 or passing :py:const:`None` value returns it. Otherwise a valid
719 key (which can be retrieved from :func:`keys`) is required.
721 :raises ValueError:
722 Raised if ``tzid`` is not specified but there are either more
723 or fewer than 1 zone defined.
725 :returns:
726 Returns either a :py:class:`datetime.tzinfo` object representing
727 the relevant time zone or :py:const:`None` if the ``tzid`` was
728 not found.
729 """
730 if tzid is None:
731 if len(self._vtz) == 0:
732 raise ValueError("no timezones defined")
733 elif len(self._vtz) > 1:
734 raise ValueError("more than one timezone available")
735 tzid = next(iter(self._vtz))
737 return self._vtz.get(tzid)
739 def _parse_offset(self, s):
740 s = s.strip()
741 if not s:
742 raise ValueError("empty offset")
743 if s[0] in ('+', '-'):
744 signal = (-1, +1)[s[0] == '+']
745 s = s[1:]
746 else:
747 signal = +1
748 if len(s) == 4:
749 return (int(s[:2]) * 3600 + int(s[2:]) * 60) * signal
750 elif len(s) == 6:
751 return (int(s[:2]) * 3600 + int(s[2:4]) * 60 + int(s[4:])) * signal
752 else:
753 raise ValueError("invalid offset: " + s)
755 def _parse_rfc(self, s):
756 lines = s.splitlines()
757 if not lines:
758 raise ValueError("empty string")
760 # Unfold
761 i = 0
762 while i < len(lines):
763 line = lines[i].rstrip()
764 if not line:
765 del lines[i]
766 elif i > 0 and line[0] == " ":
767 lines[i-1] += line[1:]
768 del lines[i]
769 else:
770 i += 1
772 tzid = None
773 comps = []
774 invtz = False
775 comptype = None
776 for line in lines:
777 if not line:
778 continue
779 name, value = line.split(':', 1)
780 parms = name.split(';')
781 if not parms:
782 raise ValueError("empty property name")
783 name = parms[0].upper()
784 parms = parms[1:]
785 if invtz:
786 if name == "BEGIN":
787 if value in ("STANDARD", "DAYLIGHT"):
788 # Process component
789 pass
790 else:
791 raise ValueError("unknown component: "+value)
792 comptype = value
793 founddtstart = False
794 tzoffsetfrom = None
795 tzoffsetto = None
796 rrulelines = []
797 tzname = None
798 elif name == "END":
799 if value == "VTIMEZONE":
800 if comptype:
801 raise ValueError("component not closed: "+comptype)
802 if not tzid:
803 raise ValueError("mandatory TZID not found")
804 if not comps:
805 raise ValueError(
806 "at least one component is needed")
807 # Process vtimezone
808 self._vtz[tzid] = _tzicalvtz(tzid, comps)
809 invtz = False
810 elif value == comptype:
811 if not founddtstart:
812 raise ValueError("mandatory DTSTART not found")
813 if tzoffsetfrom is None:
814 raise ValueError(
815 "mandatory TZOFFSETFROM not found")
816 if tzoffsetto is None:
817 raise ValueError(
818 "mandatory TZOFFSETFROM not found")
819 # Process component
820 rr = None
821 if rrulelines:
822 rr = rrule.rrulestr("\n".join(rrulelines),
823 compatible=True,
824 ignoretz=True,
825 cache=True)
826 comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto,
827 (comptype == "DAYLIGHT"),
828 tzname, rr)
829 comps.append(comp)
830 comptype = None
831 else:
832 raise ValueError("invalid component end: "+value)
833 elif comptype:
834 if name == "DTSTART":
835 # DTSTART in VTIMEZONE takes a subset of valid RRULE
836 # values under RFC 5545.
837 for parm in parms:
838 if parm != 'VALUE=DATE-TIME':
839 msg = ('Unsupported DTSTART param in ' +
840 'VTIMEZONE: ' + parm)
841 raise ValueError(msg)
842 rrulelines.append(line)
843 founddtstart = True
844 elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"):
845 rrulelines.append(line)
846 elif name == "TZOFFSETFROM":
847 if parms:
848 raise ValueError(
849 "unsupported %s parm: %s " % (name, parms[0]))
850 tzoffsetfrom = self._parse_offset(value)
851 elif name == "TZOFFSETTO":
852 if parms:
853 raise ValueError(
854 "unsupported TZOFFSETTO parm: "+parms[0])
855 tzoffsetto = self._parse_offset(value)
856 elif name == "TZNAME":
857 if parms:
858 raise ValueError(
859 "unsupported TZNAME parm: "+parms[0])
860 tzname = value
861 elif name == "COMMENT":
862 pass
863 else:
864 raise ValueError("unsupported property: "+name)
865 else:
866 if name == "TZID":
867 if parms:
868 raise ValueError(
869 "unsupported TZID parm: "+parms[0])
870 tzid = value
871 elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"):
872 pass
873 else:
874 raise ValueError("unsupported property: "+name)
875 elif name == "BEGIN" and value == "VTIMEZONE":
876 tzid = None
877 comps = []
878 invtz = True
880 def __repr__(self):
881 return "%s(%s)" % (self.__class__.__name__, repr(self._s))
884TZFILES = ["/etc/localtime", "localtime"]
886def __get_gettz():
887 tzlocal_classes = (tzlocal,)
888 if tzwinlocal is not None:
889 tzlocal_classes += (tzwinlocal,)
891 class GettzFunc:
892 """
893 Retrieve a time zone object from a string representation
895 This function is intended to retrieve the :py:class:`tzinfo` subclass
896 that best represents the time zone that would be used if a POSIX
897 `TZ variable`_ were set to the same value.
899 If no argument or an empty string is passed to ``gettz``, local time
900 is returned:
902 .. code-block:: python3
904 >>> gettz()
905 tzfile('/etc/localtime')
907 This function is also the preferred way to map IANA tz database keys
908 to :class:`tzfile` objects. It searches for these keys in the
909 following order:
911 1. On the search path defined in :data:`TZPATH`.
912 2. In the `tzdata <https://pypi.org/project/tzdata/>`_ Python package.
913 3. Using the system-specific mechanisms (e.g. the Windows registry).
915 .. code-block:: python3
917 >>> gettz('Pacific/Kiritimati')
918 tzfile('/usr/share/zoneinfo/Pacific/Kiritimati')
920 On Windows, the standard is extended to include the Windows-specific
921 zone names provided by the operating system:
923 .. code-block:: python3
925 >>> gettz('Egypt Standard Time')
926 tzwin('Egypt Standard Time')
928 Passing a GNU ``TZ`` style string time zone specification returns a
929 :class:`tzstr` object:
931 .. code-block:: python3
933 >>> gettz('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3')
934 tzstr('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3')
936 :param name:
937 A time zone name (IANA, or, on Windows, Windows keys), location of
938 a ``tzfile(5)`` zoneinfo file or ``TZ`` variable style time zone
939 specifier. An empty string, no argument or ``None`` is interpreted
940 as local time.
942 :return:
943 Returns an instance of one of ``dateutil``'s :py:class:`tzinfo`
944 subclasses.
946 .. versionchanged:: 2.7.0
948 After version 2.7.0, any two calls to ``gettz`` using the same
949 input strings will return the same object:
951 .. code-block:: python3
953 >>> tz.gettz('America/Chicago') is tz.gettz('America/Chicago')
954 True
956 In addition to improving performance, this ensures that
957 `"same zone" semantics`_ are used for datetimes in the same zone.
960 .. _`TZ variable`:
961 https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
963 .. _`"same zone" semantics`:
964 https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html
965 """
966 def __init__(self):
968 self.__instances = weakref.WeakValueDictionary()
969 self.__strong_cache_size = 8
970 self.__strong_cache = OrderedDict()
971 self._cache_lock = _thread.allocate_lock()
973 def __call__(self, name=None):
974 with self._cache_lock:
975 rv = self.__instances.get(name, None)
977 if rv is None:
978 # TODO: This holds the cache lock while nocache() reads
979 # the file, so a slow lookup (e.g. walking TZPATH on a
980 # network file system) blocks every other gettz call,
981 # including cache hits for unrelated keys.
982 rv = self.nocache(name=name)
983 if not (name is None
984 or isinstance(rv, tzlocal_classes)
985 or rv is None):
986 # tzlocal is slightly more complicated than the other
987 # time zone providers because it depends on environment
988 # at construction time, so don't cache that.
989 #
990 # We also cannot store weak references to None, so we
991 # will also not store that.
992 self.__instances[name] = rv
993 else:
994 # No need for strong caching, return immediately
995 return rv
997 self.__strong_cache[name] = self.__strong_cache.pop(name, rv)
999 if len(self.__strong_cache) > self.__strong_cache_size:
1000 self.__strong_cache.popitem(last=False)
1002 return rv
1004 def set_cache_size(self, size):
1005 with self._cache_lock:
1006 self.__strong_cache_size = size
1007 while len(self.__strong_cache) > size:
1008 self.__strong_cache.popitem(last=False)
1010 def cache_clear(self):
1011 with self._cache_lock:
1012 self.__instances = weakref.WeakValueDictionary()
1013 self.__strong_cache.clear()
1015 @staticmethod
1016 def nocache(name=None):
1017 """A non-cached version of gettz"""
1018 tz = None
1019 if not name:
1020 try:
1021 name = os.environ["TZ"]
1022 except KeyError:
1023 pass
1024 if name is None or name in ("", ":"):
1025 for filepath in TZFILES:
1026 if not os.path.isabs(filepath):
1027 filename = filepath
1028 for path in _tzpath.TZPATH:
1029 filepath = os.path.join(path, filename)
1030 if os.path.isfile(filepath):
1031 break
1032 else:
1033 continue
1034 if os.path.isfile(filepath):
1035 try:
1036 tz = tzfile(filepath)
1037 break
1038 except (OSError, ValueError):
1039 pass
1040 else:
1041 tz = tzlocal()
1042 else:
1043 try:
1044 if name.startswith(":"):
1045 name = name[1:]
1046 except TypeError as e:
1047 if isinstance(name, bytes):
1048 new_msg = "gettz argument should be str, not bytes"
1049 raise TypeError(new_msg) from e
1050 else:
1051 raise
1053 # A NUL byte can never be part of a key or a path, and
1054 # depending on the platform and Python version it raises
1055 # ValueError from os.path, winreg or open(), so reject it
1056 # up front rather than catching it in every branch.
1057 if "\x00" in name:
1058 return None
1060 if os.path.isabs(name):
1061 if os.path.isfile(name):
1062 tz = tzfile(name)
1063 else:
1064 tz = None
1065 else:
1066 if _escapes_tzpath(name):
1067 _warn_tzpath_escape(name)
1069 for path in _tzpath.TZPATH:
1070 filepath = os.path.join(path, name)
1071 if not os.path.isfile(filepath):
1072 filepath = filepath.replace(' ', '_')
1073 if not os.path.isfile(filepath):
1074 continue
1075 try:
1076 tz = tzfile(filepath, key=name)
1077 break
1078 except (OSError, ValueError):
1079 # TODO: A file that exists but can't be read or
1080 # isn't a valid TZif file is silently skipped,
1081 # and the key is looked up in the next TZPATH
1082 # entry and then in tzdata. Consider warning
1083 # here, since this usually means the system time
1084 # zone data is broken.
1085 pass
1086 else:
1087 tz = None
1088 # As with the search path lookup above, also try
1089 # the name with spaces replaced by underscores.
1090 candidates = [name]
1091 if " " in name:
1092 candidates.append(name.replace(" ", "_"))
1094 for candidate in candidates:
1095 try:
1096 with _tzdata_impl._load_tzdata(candidate) as f:
1097 return tzfile(f, key=name)
1098 except ValueError:
1099 # TZFileNotFound is a ValueError, and so is
1100 # the error raised for files in tzdata that
1101 # are not TZif files (e.g. "zone.tab"),
1102 # which are skipped just as on TZPATH.
1103 pass
1105 if tzwin is not None:
1106 try:
1107 tz = tzwin(name)
1108 except OSError:
1109 tz = None
1111 if not tz:
1112 for c in name:
1113 # name is not a tzstr unless it has at least
1114 # one offset. For short values of "name", an
1115 # explicit for loop seems to be the fastest way
1116 # To determine if a string contains a digit
1117 if c in "0123456789":
1118 try:
1119 tz = tzstr(name)
1120 except ValueError:
1121 pass
1122 break
1123 else:
1124 if name in ("GMT", "UTC"):
1125 tz = UTC
1126 elif name in time.tzname:
1127 tz = tzlocal()
1128 return tz
1130 return GettzFunc()
1133gettz = __get_gettz()
1134del __get_gettz
1137class DeprecatedTzKeyWarning(FutureWarning):
1138 """Warning raised when :func:`gettz` is given a key outside of TZPATH.
1140 Relative keys like ``"../../etc/localtime"`` resolve to files outside of
1141 every directory on :data:`TZPATH`. Support for these is deprecated, and a
1142 warnings filter turns this warning into an error by default. To keep the
1143 old behavior for now, filter it out, e.g.::
1145 warnings.filterwarnings(
1146 "ignore", category=dateutil.tz.DeprecatedTzKeyWarning
1147 )
1149 Absolute paths to TZif files are not affected.
1150 """
1153def _escapes_tzpath(name):
1154 # A relative key stays inside every TZPATH entry unless, once normalized
1155 # (which also turns os.altsep into os.sep), it starts by going up a
1156 # directory.
1157 return os.path.normpath(name).split(os.sep, 1)[0] == os.pardir
1160def _warn_tzpath_escape(name):
1161 # As in dateutil.zoneinfo.rebuild, the filter is appended at call time,
1162 # so any filter the user has installed for this category wins.
1163 warnings.filterwarnings(
1164 "error", category=DeprecatedTzKeyWarning, append=True
1165 )
1166 warnings.warn(
1167 "gettz(%r) refers to a file outside of TZPATH; support for keys "
1168 "like this is deprecated and will be removed in a future version. "
1169 "Use an absolute path instead." % (name,),
1170 DeprecatedTzKeyWarning,
1171 stacklevel=4,
1172 )
1175def available_iana_timezones():
1176 """Returns a set containing all available IANA time zones keys.
1178 These are suitable for use with :func:`gettz`. This is essentially a direct
1179 port of :py:func:`zoneinfo.available_timezones`.
1181 The search includes time zones found on :data:`TZPATH` as well as any
1182 time zones provided by the `tzdata <https://pypi.org/project/tzdata/>`_
1183 package if it is installed.
1185 :return:
1186 Returns a mutable set containing strings of time zones. Each call will
1187 return a fresh set.
1189 .. caution::
1191 This may attempt to open a large number of files, since the best way to
1192 determine if a given file on the time zone search path is to open it
1193 and check for the "magic string" at the beginning.
1195 .. note::
1197 These values are not designed to be exposed to end-users; for
1198 user-facing elements, applications should use something like CLDR (the
1199 Unicode Common Locale Data Repository) to get more user-friendly
1200 strings.
1201 """
1202 # Start with loading from the tzdata package if it exists: this has a
1203 # pre-assembled list of zones that only requires opening one file.
1204 try:
1205 valid_zones = set(_tzdata_impl._load_tzdata_keys())
1206 except ImportError:
1207 valid_zones = set()
1209 def valid_key(fpath):
1210 try:
1211 with open(fpath, "rb") as f:
1212 return f.read(4) == b"TZif"
1213 except Exception: # pragma: nocover
1214 return False
1216 for tz_root in _tzpath.TZPATH:
1217 if not os.path.exists(tz_root):
1218 continue
1220 for root, dirnames, files in os.walk(tz_root):
1221 if root == tz_root:
1222 # right/ and posix/ are special directories and shouldn't be
1223 # included in the output of available zones
1224 if "right" in dirnames:
1225 dirnames.remove("right")
1226 if "posix" in dirnames:
1227 dirnames.remove("posix")
1229 for file in files:
1230 fpath = os.path.join(root, file)
1232 key = os.path.relpath(fpath, start=tz_root)
1233 if os.sep != "/": # pragma: nocover
1234 key = key.replace(os.sep, "/")
1236 if not key or key in valid_zones:
1237 continue
1239 if valid_key(fpath):
1240 valid_zones.add(key)
1242 if "posixrules" in valid_zones:
1243 # posixrules is a special symlink-only time zone where it exists, it
1244 # should not be included in the output
1245 valid_zones.remove("posixrules")
1246 if "localtime" in valid_zones:
1247 # localtime is a special symlink-only time zone where it exists, it
1248 # should not be included in the output
1249 valid_zones.remove("localtime")
1251 return valid_zones
1254def datetime_exists(dt, tz=None):
1255 """
1256 Given a datetime and a time zone, determine whether or not a given datetime
1257 would fall in a gap.
1259 :param dt:
1260 A :class:`datetime.datetime` (whose time zone will be ignored if ``tz``
1261 is provided.)
1263 :param tz:
1264 A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If
1265 ``None`` or not provided, the datetime's own time zone will be used.
1267 :return:
1268 Returns a boolean value whether or not the "wall time" exists in
1269 ``tz``.
1271 .. versionadded:: 2.7.0
1272 """
1273 if tz is None:
1274 if dt.tzinfo is None:
1275 raise ValueError('Datetime is naive and no time zone provided.')
1276 tz = dt.tzinfo
1278 dt = dt.replace(tzinfo=None)
1280 # This is essentially a test of whether or not the datetime can survive
1281 # a round trip to UTC.
1282 dt_rt = dt.replace(tzinfo=tz).astimezone(UTC).astimezone(tz)
1283 dt_rt = dt_rt.replace(tzinfo=None)
1285 return dt == dt_rt
1288def datetime_ambiguous(dt, tz=None):
1289 """
1290 Given a datetime and a time zone, determine whether or not a given datetime
1291 is ambiguous (i.e if there are two times differentiated only by their DST
1292 status).
1294 :param dt:
1295 A :class:`datetime.datetime` (whose time zone will be ignored if ``tz``
1296 is provided.)
1298 :param tz:
1299 A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If
1300 ``None`` or not provided, the datetime's own time zone will be used.
1302 :return:
1303 Returns a boolean value whether or not the "wall time" is ambiguous in
1304 ``tz``.
1306 .. versionadded:: 2.6.0
1307 """
1308 if tz is None:
1309 if dt.tzinfo is None:
1310 raise ValueError('Datetime is naive and no time zone provided.')
1312 tz = dt.tzinfo
1314 # If a time zone defines its own "is_ambiguous" function, we'll use that.
1315 is_ambiguous_fn = getattr(tz, 'is_ambiguous', None)
1316 if is_ambiguous_fn is not None:
1317 try:
1318 return tz.is_ambiguous(dt)
1319 except Exception:
1320 pass
1322 # If it doesn't come out and tell us it's ambiguous, we'll just check if
1323 # the fold attribute has any effect on this particular date and time.
1324 dt = dt.replace(tzinfo=tz)
1325 wall_0 = enfold(dt, fold=0)
1326 wall_1 = enfold(dt, fold=1)
1328 same_offset = wall_0.utcoffset() == wall_1.utcoffset()
1329 same_dst = wall_0.dst() == wall_1.dst()
1331 return not (same_offset and same_dst)
1334def resolve_imaginary(dt):
1335 """
1336 Given a datetime that may be imaginary, return an existing datetime.
1338 This function assumes that an imaginary datetime represents what the
1339 wall time would be in a zone had the offset transition not occurred, so
1340 it will always fall forward by the transition's change in offset.
1342 .. doctest::
1344 >>> from dateutil import tz
1345 >>> from datetime import datetime
1346 >>> NYC = tz.gettz('America/New_York')
1347 >>> print(tz.resolve_imaginary(datetime(2017, 3, 12, 2, 30, tzinfo=NYC)))
1348 2017-03-12 03:30:00-04:00
1350 >>> KIR = tz.gettz('Pacific/Kiritimati')
1351 >>> print(tz.resolve_imaginary(datetime(1995, 1, 1, 12, 30, tzinfo=KIR)))
1352 1995-01-02 12:30:00+14:00
1354 As a note, :func:`datetime.astimezone` is guaranteed to produce a valid,
1355 existing datetime, so a round-trip to and from UTC is sufficient to get
1356 an extant datetime, however, this generally "falls back" to an earlier time
1357 rather than falling forward to the STD side (though no guarantees are made
1358 about this behavior).
1360 :param dt:
1361 A :class:`datetime.datetime` which may or may not exist.
1363 :return:
1364 Returns an existing :class:`datetime.datetime`. If ``dt`` was not
1365 imaginary, the datetime returned is guaranteed to be the same object
1366 passed to the function.
1368 .. versionadded:: 2.7.0
1369 """
1370 if dt.tzinfo is not None and not datetime_exists(dt):
1372 curr_offset = (dt + datetime.timedelta(hours=24)).utcoffset()
1373 old_offset = (dt - datetime.timedelta(hours=24)).utcoffset()
1375 dt += curr_offset - old_offset
1377 return dt
1380def _datetime_to_timestamp(dt):
1381 """
1382 Convert a :class:`datetime.datetime` object to an epoch timestamp in
1383 seconds since January 1, 1970, ignoring the time zone.
1384 """
1385 return (dt.replace(tzinfo=None) - EPOCH).total_seconds()
1388# vim:ts=4:sw=4:et