mirror of
https://github.com/SickGear/SickGear.git
synced 2024-12-02 17:33:37 +00:00
Update DateUtil 2.7.2 (ff03c0f) → 2.7.2 (49690ee)
This commit is contained in:
parent
74c524bc92
commit
86086b900f
10 changed files with 488 additions and 157 deletions
|
@ -364,13 +364,23 @@ class parserinfo(object):
|
|||
return self.TZOFFSET.get(name)
|
||||
|
||||
def convertyear(self, year, century_specified=False):
|
||||
"""
|
||||
Converts two-digit years to year within [-50, 49]
|
||||
range of self._year (current local time)
|
||||
"""
|
||||
|
||||
# Function contract is that the year is always positive
|
||||
assert year >= 0
|
||||
|
||||
if year < 100 and not century_specified:
|
||||
# assume current century to start
|
||||
year += self._century
|
||||
if abs(year - self._year) >= 50:
|
||||
if year < self._year:
|
||||
year += 100
|
||||
else:
|
||||
|
||||
if year >= self._year + 50: # if too far in future
|
||||
year -= 100
|
||||
elif year < self._year - 50: # if too far in past
|
||||
year += 100
|
||||
|
||||
return year
|
||||
|
||||
def validate(self, res):
|
||||
|
@ -448,10 +458,37 @@ class _ymd(list):
|
|||
raise ValueError('Year is already set')
|
||||
self.ystridx = len(self) - 1
|
||||
|
||||
def _resolve_from_stridxs(self, strids):
|
||||
"""
|
||||
Try to resolve the identities of year/month/day elements using
|
||||
ystridx, mstridx, and dstridx, if enough of these are specified.
|
||||
"""
|
||||
if len(self) == 3 and len(strids) == 2:
|
||||
# we can back out the remaining stridx value
|
||||
missing = [x for x in range(3) if x not in strids.values()]
|
||||
key = [x for x in ['y', 'm', 'd'] if x not in strids]
|
||||
assert len(missing) == len(key) == 1
|
||||
key = key[0]
|
||||
val = missing[0]
|
||||
strids[key] = val
|
||||
|
||||
assert len(self) == len(strids) # otherwise this should not be called
|
||||
out = {key: self[strids[key]] for key in strids}
|
||||
return (out.get('y'), out.get('m'), out.get('d'))
|
||||
|
||||
def resolve_ymd(self, yearfirst, dayfirst):
|
||||
len_ymd = len(self)
|
||||
year, month, day = (None, None, None)
|
||||
|
||||
strids = (('y', self.ystridx),
|
||||
('m', self.mstridx),
|
||||
('d', self.dstridx))
|
||||
|
||||
strids = {key: val for key, val in strids if val is not None}
|
||||
if (len(self) == len(strids) > 0 or
|
||||
(len(self) == 3 and len(strids) == 2)):
|
||||
return self._resolve_from_stridxs(strids)
|
||||
|
||||
mstridx = self.mstridx
|
||||
|
||||
if len_ymd > 3:
|
||||
|
@ -460,13 +497,17 @@ class _ymd(list):
|
|||
# One member, or two members with a month string
|
||||
if mstridx is not None:
|
||||
month = self[mstridx]
|
||||
del self[mstridx]
|
||||
# since mstridx is 0 or 1, self[mstridx-1] always
|
||||
# looks up the other element
|
||||
other = self[mstridx - 1]
|
||||
else:
|
||||
other = self[0]
|
||||
|
||||
if len_ymd > 1 or mstridx is None:
|
||||
if self[0] > 31:
|
||||
year = self[0]
|
||||
if other > 31:
|
||||
year = other
|
||||
else:
|
||||
day = self[0]
|
||||
day = other
|
||||
|
||||
elif len_ymd == 2:
|
||||
# Two members with numbers
|
||||
|
@ -1115,16 +1156,14 @@ class parser(object):
|
|||
tzdata = tzinfos(tzname, tzoffset)
|
||||
else:
|
||||
tzdata = tzinfos.get(tzname)
|
||||
|
||||
if isinstance(tzdata, datetime.tzinfo):
|
||||
# handle case where tzinfo is paased an options that returns None
|
||||
# eg tzinfos = {'BRST' : None}
|
||||
if isinstance(tzdata, datetime.tzinfo) or tzdata is None:
|
||||
tzinfo = tzdata
|
||||
elif isinstance(tzdata, text_type):
|
||||
tzinfo = tz.tzstr(tzdata)
|
||||
elif isinstance(tzdata, integer_types):
|
||||
tzinfo = tz.tzoffset(tzname, tzdata)
|
||||
else:
|
||||
raise ValueError("Offset must be tzinfo subclass, "
|
||||
"tz string, or int offset.")
|
||||
return tzinfo
|
||||
|
||||
def _build_tzaware(self, naive, res, tzinfos):
|
||||
|
@ -1160,7 +1199,7 @@ class parser(object):
|
|||
warnings.warn("tzname {tzname} identified but not understood. "
|
||||
"Pass `tzinfos` argument in order to correctly "
|
||||
"return a timezone-aware datetime. In a future "
|
||||
"version, this raise an "
|
||||
"version, this will raise an "
|
||||
"exception.".format(tzname=res.tzname),
|
||||
category=UnknownTimezoneWarning)
|
||||
aware = naive
|
||||
|
@ -1202,10 +1241,15 @@ class parser(object):
|
|||
|
||||
def _to_decimal(self, val):
|
||||
try:
|
||||
return Decimal(val)
|
||||
decimal_value = Decimal(val)
|
||||
# See GH 662, edge case, infinite value should not be converted via `_to_decimal`
|
||||
if not decimal_value.is_finite():
|
||||
raise ValueError("Converted decimal value is infinite or NaN")
|
||||
except Exception as e:
|
||||
msg = "Could not convert %s to decimal" % val
|
||||
six.raise_from(ValueError(msg), e)
|
||||
else:
|
||||
return decimal_value
|
||||
|
||||
|
||||
DEFAULTPARSER = parser()
|
||||
|
|
|
@ -4,6 +4,8 @@ This module offers a parser for ISO-8601 strings
|
|||
|
||||
It is intended to support all valid date, time and datetime formats per the
|
||||
ISO-8601 specification.
|
||||
|
||||
..versionadded:: 2.7.0
|
||||
"""
|
||||
from datetime import datetime, timedelta, time, date
|
||||
import calendar
|
||||
|
@ -86,10 +88,12 @@ class isoparser(object):
|
|||
- ``hh``
|
||||
- ``hh:mm`` or ``hhmm``
|
||||
- ``hh:mm:ss`` or ``hhmmss``
|
||||
- ``hh:mm:ss.sss`` or ``hh:mm:ss.ssssss`` (3-6 sub-second digits)
|
||||
- ``hh:mm:ss.ssssss`` (Up to 6 sub-second digits)
|
||||
|
||||
Midnight is a special case for `hh`, as the standard supports both
|
||||
00:00 and 24:00 as a representation.
|
||||
00:00 and 24:00 as a representation. The decimal separator can be
|
||||
either a dot or a comma.
|
||||
|
||||
|
||||
.. caution::
|
||||
|
||||
|
@ -124,6 +128,8 @@ class isoparser(object):
|
|||
currently fail (e.g. ``2017-01-01T00:00+00:00:00``) are not
|
||||
guaranteed to continue failing in future versions if they encode
|
||||
a valid date.
|
||||
|
||||
.. versionadded:: 2.7.0
|
||||
"""
|
||||
components, pos = self._parse_isodate(dt_str)
|
||||
|
||||
|
@ -133,6 +139,10 @@ class isoparser(object):
|
|||
else:
|
||||
raise ValueError('String contains unknown ISO components')
|
||||
|
||||
if len(components) > 3 and components[3] == 24:
|
||||
components[3] = 0
|
||||
return datetime(*components) + timedelta(days=1)
|
||||
|
||||
return datetime(*components)
|
||||
|
||||
@_takes_ascii
|
||||
|
@ -163,7 +173,10 @@ class isoparser(object):
|
|||
:return:
|
||||
Returns a :class:`datetime.time` object
|
||||
"""
|
||||
return time(*self._parse_isotime(timestr))
|
||||
components = self._parse_isotime(timestr)
|
||||
if components[0] == 24:
|
||||
components[0] = 0
|
||||
return time(*components)
|
||||
|
||||
@_takes_ascii
|
||||
def parse_tzstr(self, tzstr, zero_as_utc=True):
|
||||
|
@ -186,10 +199,9 @@ class isoparser(object):
|
|||
return self._parse_tzstr(tzstr, zero_as_utc=zero_as_utc)
|
||||
|
||||
# Constants
|
||||
_MICROSECOND_END_REGEX = re.compile(b'[-+Z]+')
|
||||
_DATE_SEP = b'-'
|
||||
_TIME_SEP = b':'
|
||||
_MICRO_SEP = b'.'
|
||||
_FRACTION_REGEX = re.compile(b'[\\.,]([0-9]+)')
|
||||
|
||||
def _parse_isodate(self, dt_str):
|
||||
try:
|
||||
|
@ -344,16 +356,14 @@ class isoparser(object):
|
|||
pos += 1
|
||||
|
||||
if comp == 3:
|
||||
# Microsecond
|
||||
if timestr[pos:pos + 1] != self._MICRO_SEP:
|
||||
# Fraction of a second
|
||||
frac = self._FRACTION_REGEX.match(timestr[pos:])
|
||||
if not frac:
|
||||
continue
|
||||
|
||||
pos += 1
|
||||
us_str = self._MICROSECOND_END_REGEX.split(timestr[pos:pos + 6],
|
||||
1)[0]
|
||||
|
||||
us_str = frac.group(1)[:6] # Truncate to microseconds
|
||||
components[comp] = int(us_str) * 10**(6 - len(us_str))
|
||||
pos += len(us_str)
|
||||
pos += len(frac.group())
|
||||
|
||||
if pos < len_str:
|
||||
raise ValueError('Unused components in ISO string')
|
||||
|
@ -362,7 +372,6 @@ class isoparser(object):
|
|||
# Standard supports 00:00 and 24:00 as representations of midnight
|
||||
if any(component != 0 for component in components[1:4]):
|
||||
raise ValueError('Hour may only be 24 at 24:00:00.000')
|
||||
components[0] = 0
|
||||
|
||||
return components
|
||||
|
||||
|
|
|
@ -17,8 +17,12 @@ __all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
|
|||
|
||||
class relativedelta(object):
|
||||
"""
|
||||
The relativedelta type is based on the specification of the excellent
|
||||
work done by M.-A. Lemburg in his
|
||||
The relativedelta type is designed to be applied to an existing datetime and
|
||||
can replace specific components of that datetime, or represents an interval
|
||||
of time.
|
||||
|
||||
It is based on the specification of the excellent work done by M.-A. Lemburg
|
||||
in his
|
||||
`mx.DateTime <https://www.egenix.com/products/python/mxBase/mxDateTime/>`_ extension.
|
||||
However, notice that this type does *NOT* implement the same algorithm as
|
||||
his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.
|
||||
|
@ -45,11 +49,15 @@ class relativedelta(object):
|
|||
with the information in the relativedelta.
|
||||
|
||||
weekday:
|
||||
One of the weekday instances (MO, TU, etc). These instances may
|
||||
receive a parameter N, specifying the Nth weekday, which could
|
||||
be positive or negative (like MO(+1) or MO(-2). Not specifying
|
||||
it is the same as specifying +1. You can also use an integer,
|
||||
where 0=MO.
|
||||
One of the weekday instances (MO, TU, etc) available in the
|
||||
relativedelta module. These instances may receive a parameter N,
|
||||
specifying the Nth weekday, which could be positive or negative
|
||||
(like MO(+1) or MO(-2)). Not specifying it is the same as specifying
|
||||
+1. You can also use an integer, where 0=MO. This argument is always
|
||||
relative e.g. if the calculated date is already Monday, using MO(1)
|
||||
or MO(-1) won't change the day. To effectively make it absolute, use
|
||||
it in combination with the day argument (e.g. day=1, MO(1) for first
|
||||
Monday of the month).
|
||||
|
||||
leapdays:
|
||||
Will add given days to the date found, if year is a leap
|
||||
|
@ -59,33 +67,39 @@ class relativedelta(object):
|
|||
Set the yearday or the non-leap year day (jump leap days).
|
||||
These are converted to day/month/leapdays information.
|
||||
|
||||
Here is the behavior of operations with relativedelta:
|
||||
There are relative and absolute forms of the keyword
|
||||
arguments. The plural is relative, and the singular is
|
||||
absolute. For each argument in the order below, the absolute form
|
||||
is applied first (by setting each attribute to that value) and
|
||||
then the relative form (by adding the value to the attribute).
|
||||
|
||||
1. Calculate the absolute year, using the 'year' argument, or the
|
||||
original datetime year, if the argument is not present.
|
||||
The order of attributes considered when this relativedelta is
|
||||
added to a datetime is:
|
||||
|
||||
2. Add the relative 'years' argument to the absolute year.
|
||||
1. Year
|
||||
2. Month
|
||||
3. Day
|
||||
4. Hours
|
||||
5. Minutes
|
||||
6. Seconds
|
||||
7. Microseconds
|
||||
|
||||
3. Do steps 1 and 2 for month/months.
|
||||
Finally, weekday is applied, using the rule described above.
|
||||
|
||||
4. Calculate the absolute day, using the 'day' argument, or the
|
||||
original datetime day, if the argument is not present. Then,
|
||||
subtract from the day until it fits in the year and month
|
||||
found after their operations.
|
||||
For example
|
||||
|
||||
5. Add the relative 'days' argument to the absolute day. Notice
|
||||
that the 'weeks' argument is multiplied by 7 and added to
|
||||
'days'.
|
||||
>>> from datetime import datetime
|
||||
>>> from dateutil.relativedelta import relativedelta, MO
|
||||
>>> dt = datetime(2018, 4, 9, 13, 37, 0)
|
||||
>>> delta = relativedelta(hours=25, day=1, weekday=MO(1))
|
||||
>>> dt + delta
|
||||
datetime.datetime(2018, 4, 2, 14, 37)
|
||||
|
||||
6. Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds,
|
||||
microsecond/microseconds.
|
||||
First, the day is set to 1 (the first of the month), then 25 hours
|
||||
are added, to get to the 2nd day and 14th hour, finally the
|
||||
weekday is applied, but since the 2nd is already a Monday there is
|
||||
no effect.
|
||||
|
||||
7. If the 'weekday' argument is present, calculate the weekday,
|
||||
with the given (wday, nth) tuple. wday is the index of the
|
||||
weekday (0-6, 0=Mon), and nth is the number of weeks to add
|
||||
forward or backward, depending on its signal. Notice that if
|
||||
the calculated date is already Monday, for example, using
|
||||
(0, 1) or (0, -1) won't change the day.
|
||||
"""
|
||||
|
||||
def __init__(self, dt1=None, dt2=None,
|
||||
|
@ -271,7 +285,7 @@ class relativedelta(object):
|
|||
values for the relative attributes.
|
||||
|
||||
>>> relativedelta(days=1.5, hours=2).normalized()
|
||||
relativedelta(days=1, hours=14)
|
||||
relativedelta(days=+1, hours=+14)
|
||||
|
||||
:return:
|
||||
Returns a :class:`dateutil.relativedelta.relativedelta` object.
|
||||
|
|
|
@ -337,10 +337,6 @@ class rrule(rrulebase):
|
|||
|
||||
Additionally, it supports the following keyword arguments:
|
||||
|
||||
:param cache:
|
||||
If given, it must be a boolean value specifying to enable or disable
|
||||
caching of results. If you will use the same rrule instance multiple
|
||||
times, enabling caching will improve the performance considerably.
|
||||
:param dtstart:
|
||||
The recurrence start. Besides being the base for the recurrence,
|
||||
missing parameters in the final recurrence instances will also be
|
||||
|
@ -357,20 +353,26 @@ class rrule(rrulebase):
|
|||
from calendar.firstweekday(), and may be modified by
|
||||
calendar.setfirstweekday().
|
||||
:param count:
|
||||
How many occurrences will be generated.
|
||||
If given, this determines how many occurrences will be generated.
|
||||
|
||||
.. note::
|
||||
As of version 2.5.0, the use of the ``until`` keyword together
|
||||
with the ``count`` keyword is deprecated per RFC-5545 Sec. 3.3.10.
|
||||
As of version 2.5.0, the use of the keyword ``until`` in conjunction
|
||||
with ``count`` is deprecated, to make sure ``dateutil`` is fully
|
||||
compliant with `RFC-5545 Sec. 3.3.10 <https://tools.ietf.org/
|
||||
html/rfc5545#section-3.3.10>`_. Therefore, ``until`` and ``count``
|
||||
**must not** occur in the same call to ``rrule``.
|
||||
:param until:
|
||||
If given, this must be a datetime instance, that will specify the
|
||||
If given, this must be a datetime instance specifying the upper-bound
|
||||
limit of the recurrence. The last recurrence in the rule is the greatest
|
||||
datetime that is less than or equal to the value specified in the
|
||||
``until`` parameter.
|
||||
|
||||
.. note::
|
||||
As of version 2.5.0, the use of the ``until`` keyword together
|
||||
with the ``count`` keyword is deprecated per RFC-5545 Sec. 3.3.10.
|
||||
As of version 2.5.0, the use of the keyword ``until`` in conjunction
|
||||
with ``count`` is deprecated, to make sure ``dateutil`` is fully
|
||||
compliant with `RFC-5545 Sec. 3.3.10 <https://tools.ietf.org/
|
||||
html/rfc5545#section-3.3.10>`_. Therefore, ``until`` and ``count``
|
||||
**must not** occur in the same call to ``rrule``.
|
||||
:param bysetpos:
|
||||
If given, it must be either an integer, or a sequence of integers,
|
||||
positive or negative. Each given integer will specify an occurrence
|
||||
|
@ -387,6 +389,11 @@ class rrule(rrulebase):
|
|||
:param byyearday:
|
||||
If given, it must be either an integer, or a sequence of integers,
|
||||
meaning the year days to apply the recurrence to.
|
||||
:param byeaster:
|
||||
If given, it must be either an integer, or a sequence of integers,
|
||||
positive or negative. Each integer will define an offset from the
|
||||
Easter Sunday. Passing the offset 0 to byeaster will yield the Easter
|
||||
Sunday itself. This is an extension to the RFC specification.
|
||||
:param byweekno:
|
||||
If given, it must be either an integer, or a sequence of integers,
|
||||
meaning the week numbers to apply the recurrence to. Week numbers
|
||||
|
@ -412,11 +419,10 @@ class rrule(rrulebase):
|
|||
:param bysecond:
|
||||
If given, it must be either an integer, or a sequence of integers,
|
||||
meaning the seconds to apply the recurrence to.
|
||||
:param byeaster:
|
||||
If given, it must be either an integer, or a sequence of integers,
|
||||
positive or negative. Each integer will define an offset from the
|
||||
Easter Sunday. Passing the offset 0 to byeaster will yield the Easter
|
||||
Sunday itself. This is an extension to the RFC specification.
|
||||
:param cache:
|
||||
If given, it must be a boolean value specifying to enable or disable
|
||||
caching of results. If you will use the same rrule instance multiple
|
||||
times, enabling caching will improve the performance considerably.
|
||||
"""
|
||||
def __init__(self, freq, dtstart=None,
|
||||
interval=1, wkst=None, count=None, until=None, bysetpos=None,
|
||||
|
@ -427,6 +433,9 @@ class rrule(rrulebase):
|
|||
super(rrule, self).__init__(cache)
|
||||
global easter
|
||||
if not dtstart:
|
||||
if until and until.tzinfo:
|
||||
dtstart = datetime.datetime.now(tz=until.tzinfo).replace(microsecond=0)
|
||||
else:
|
||||
dtstart = datetime.datetime.now().replace(microsecond=0)
|
||||
elif not isinstance(dtstart, datetime.datetime):
|
||||
dtstart = datetime.datetime.fromordinal(dtstart.toordinal())
|
||||
|
@ -1404,6 +1413,49 @@ class rruleset(rrulebase):
|
|||
|
||||
|
||||
class _rrulestr(object):
|
||||
""" Parses a string representation of a recurrence rule or set of
|
||||
recurrence rules.
|
||||
|
||||
:param s:
|
||||
Required, a string defining one or more recurrence rules.
|
||||
|
||||
:param dtstart:
|
||||
If given, used as the default recurrence start if not specified in the
|
||||
rule string.
|
||||
|
||||
:param cache:
|
||||
If set ``True`` caching of results will be enabled, improving
|
||||
performance of multiple queries considerably.
|
||||
|
||||
:param unfold:
|
||||
If set ``True`` indicates that a rule string is split over more
|
||||
than one line and should be joined before processing.
|
||||
|
||||
:param forceset:
|
||||
If set ``True`` forces a :class:`dateutil.rrule.rruleset` to
|
||||
be returned.
|
||||
|
||||
:param compatible:
|
||||
If set ``True`` forces ``unfold`` and ``forceset`` to be ``True``.
|
||||
|
||||
:param ignoretz:
|
||||
If set ``True``, time zones in parsed strings are ignored and a naive
|
||||
:class:`datetime.datetime` object is returned.
|
||||
|
||||
:param tzids:
|
||||
If given, a callable or mapping used to retrieve a
|
||||
:class:`datetime.tzinfo` from a string representation.
|
||||
Defaults to :func:`dateutil.tz.gettz`.
|
||||
|
||||
:param tzinfos:
|
||||
Additional time zone names / aliases which may be present in a string
|
||||
representation. See :func:`dateutil.parser.parse` for more
|
||||
information.
|
||||
|
||||
:return:
|
||||
Returns a :class:`dateutil.rrule.rruleset` or
|
||||
:class:`dateutil.rrule.rrule`
|
||||
"""
|
||||
|
||||
_freq_map = {"YEARLY": YEARLY,
|
||||
"MONTHLY": MONTHLY,
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from .tz import *
|
||||
from .tz import __doc__
|
||||
|
||||
#: Convenience constant providing a :class:`tzutc()` instance
|
||||
#:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from six import PY3
|
||||
from six import PY2
|
||||
|
||||
from functools import wraps
|
||||
|
||||
|
@ -16,14 +16,18 @@ def tzname_in_python2(namefunc):
|
|||
tzname() API changed in Python 3. It used to return bytes, but was changed
|
||||
to unicode strings
|
||||
"""
|
||||
if PY2:
|
||||
@wraps(namefunc)
|
||||
def adjust_encoding(*args, **kwargs):
|
||||
name = namefunc(*args, **kwargs)
|
||||
if name is not None and not PY3:
|
||||
if name is not None:
|
||||
name = name.encode()
|
||||
|
||||
return name
|
||||
|
||||
return adjust_encoding
|
||||
else:
|
||||
return namefunc
|
||||
|
||||
|
||||
# The following is adapted from Alexander Belopolsky's tz library
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from datetime import timedelta
|
||||
|
||||
import weakref
|
||||
|
||||
class _TzSingleton(type):
|
||||
def __init__(cls, *args, **kwargs):
|
||||
|
@ -19,7 +19,7 @@ class _TzFactory(type):
|
|||
|
||||
class _TzOffsetFactory(_TzFactory):
|
||||
def __init__(cls, *args, **kwargs):
|
||||
cls.__instances = {}
|
||||
cls.__instances = weakref.WeakValueDictionary()
|
||||
|
||||
def __call__(cls, name, offset):
|
||||
if isinstance(offset, timedelta):
|
||||
|
@ -36,7 +36,7 @@ class _TzOffsetFactory(_TzFactory):
|
|||
|
||||
class _TzStrFactory(_TzFactory):
|
||||
def __init__(cls, *args, **kwargs):
|
||||
cls.__instances = {}
|
||||
cls.__instances = weakref.WeakValueDictionary()
|
||||
|
||||
def __call__(cls, s, posix_offset=False):
|
||||
key = (s, posix_offset)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This module offers timezone implementations subclassing the abstract
|
||||
:py:`datetime.tzinfo` type. There are classes to handle tzfile format files
|
||||
(usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`, etc), TZ
|
||||
environment string (in all known formats), given ranges (with help from
|
||||
relative deltas), local machine timezone, fixed offset timezone, and UTC
|
||||
:py:class:`datetime.tzinfo` type. There are classes to handle tzfile format
|
||||
files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`,
|
||||
etc), TZ environment string (in all known formats), given ranges (with help
|
||||
from relative deltas), local machine timezone, fixed offset timezone, and UTC
|
||||
timezone.
|
||||
"""
|
||||
import datetime
|
||||
|
@ -13,6 +13,7 @@ import time
|
|||
import sys
|
||||
import os
|
||||
import bisect
|
||||
import weakref
|
||||
|
||||
import six
|
||||
from six import string_types
|
||||
|
@ -28,6 +29,9 @@ try:
|
|||
except ImportError:
|
||||
tzwin = tzwinlocal = None
|
||||
|
||||
# For warning about rounding tzinfo
|
||||
from warnings import warn
|
||||
|
||||
ZERO = datetime.timedelta(0)
|
||||
EPOCH = datetime.datetime.utcfromtimestamp(0)
|
||||
EPOCHORDINAL = EPOCH.toordinal()
|
||||
|
@ -137,7 +141,8 @@ class tzoffset(datetime.tzinfo):
|
|||
offset = offset.total_seconds()
|
||||
except (TypeError, AttributeError):
|
||||
pass
|
||||
self._offset = datetime.timedelta(seconds=offset)
|
||||
|
||||
self._offset = datetime.timedelta(seconds=_get_supported_offset(offset))
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return self._offset
|
||||
|
@ -387,10 +392,60 @@ class tzfile(_tzinfo):
|
|||
``fileobj``'s ``name`` attribute or to ``repr(fileobj)``.
|
||||
|
||||
See `Sources for Time Zone and Daylight Saving Time Data
|
||||
<https://data.iana.org/time-zones/tz-link.html>`_ for more information. Time
|
||||
zone files can be compiled from the `IANA Time Zone database files
|
||||
<https://data.iana.org/time-zones/tz-link.html>`_ for more information.
|
||||
Time zone files can be compiled from the `IANA Time Zone database files
|
||||
<https://www.iana.org/time-zones>`_ with the `zic time zone compiler
|
||||
<https://www.freebsd.org/cgi/man.cgi?query=zic&sektion=8>`_
|
||||
|
||||
.. note::
|
||||
|
||||
Only construct a ``tzfile`` directly if you have a specific timezone
|
||||
file on disk that you want to read into a Python ``tzinfo`` object.
|
||||
If you want to get a ``tzfile`` representing a specific IANA zone,
|
||||
(e.g. ``'America/New_York'``), you should call
|
||||
:func:`dateutil.tz.gettz` with the zone identifier.
|
||||
|
||||
|
||||
**Examples:**
|
||||
|
||||
Using the US Eastern time zone as an example, we can see that a ``tzfile``
|
||||
provides time zone information for the standard Daylight Saving offsets:
|
||||
|
||||
.. testsetup:: tzfile
|
||||
|
||||
from dateutil.tz import gettz
|
||||
from datetime import datetime
|
||||
|
||||
.. doctest:: tzfile
|
||||
|
||||
>>> NYC = gettz('America/New_York')
|
||||
>>> NYC
|
||||
tzfile('/usr/share/zoneinfo/America/New_York')
|
||||
|
||||
>>> print(datetime(2016, 1, 3, tzinfo=NYC)) # EST
|
||||
2016-01-03 00:00:00-05:00
|
||||
|
||||
>>> print(datetime(2016, 7, 7, tzinfo=NYC)) # EDT
|
||||
2016-07-07 00:00:00-04:00
|
||||
|
||||
|
||||
The ``tzfile`` structure contains a fully history of the time zone,
|
||||
so historical dates will also have the right offsets. For example, before
|
||||
the adoption of the UTC standards, New York used local solar mean time:
|
||||
|
||||
.. doctest:: tzfile
|
||||
|
||||
>>> print(datetime(1901, 4, 12, tzinfo=NYC)) # LMT
|
||||
1901-04-12 00:00:00-04:56
|
||||
|
||||
And during World War II, New York was on "Eastern War Time", which was a
|
||||
state of permanent daylight saving time:
|
||||
|
||||
.. doctest:: tzfile
|
||||
|
||||
>>> print(datetime(1944, 2, 7, tzinfo=NYC)) # EWT
|
||||
1944-02-07 00:00:00-04:00
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, fileobj, filename=None):
|
||||
|
@ -410,7 +465,7 @@ class tzfile(_tzinfo):
|
|||
|
||||
if fileobj is not None:
|
||||
if not file_opened_here:
|
||||
fileobj = _ContextWrapper(fileobj)
|
||||
fileobj = _nullcontext(fileobj)
|
||||
|
||||
with fileobj as file_stream:
|
||||
tzobj = self._read_tzfile(file_stream)
|
||||
|
@ -550,10 +605,7 @@ class tzfile(_tzinfo):
|
|||
out.ttinfo_list = []
|
||||
for i in range(typecnt):
|
||||
gmtoff, isdst, abbrind = ttinfo[i]
|
||||
# Round to full-minutes if that's not the case. Python's
|
||||
# datetime doesn't accept sub-minute timezones. Check
|
||||
# http://python.org/sf/1447945 for some information.
|
||||
gmtoff = 60 * ((gmtoff + 30) // 60)
|
||||
gmtoff = _get_supported_offset(gmtoff)
|
||||
tti = _ttinfo()
|
||||
tti.offset = gmtoff
|
||||
tti.dstoffset = datetime.timedelta(0)
|
||||
|
@ -605,37 +657,44 @@ class tzfile(_tzinfo):
|
|||
# isgmt are off, so it should be in wall time. OTOH, it's
|
||||
# always in gmt time. Let me know if you have comments
|
||||
# about this.
|
||||
laststdoffset = None
|
||||
lastdst = None
|
||||
lastoffset = None
|
||||
lastdstoffset = None
|
||||
lastbaseoffset = None
|
||||
out.trans_list = []
|
||||
|
||||
for i, tti in enumerate(out.trans_idx):
|
||||
if not tti.isdst:
|
||||
offset = tti.offset
|
||||
laststdoffset = offset
|
||||
else:
|
||||
if laststdoffset is not None:
|
||||
# Store the DST offset as well and update it in the list
|
||||
tti.dstoffset = tti.offset - laststdoffset
|
||||
out.trans_idx[i] = tti
|
||||
dstoffset = 0
|
||||
|
||||
offset = laststdoffset or 0
|
||||
|
||||
out.trans_list.append(out.trans_list_utc[i] + offset)
|
||||
|
||||
# In case we missed any DST offsets on the way in for some reason, make
|
||||
# a second pass over the list, looking for the /next/ DST offset.
|
||||
laststdoffset = None
|
||||
for i in reversed(range(len(out.trans_idx))):
|
||||
tti = out.trans_idx[i]
|
||||
if lastdst is not None:
|
||||
if tti.isdst:
|
||||
if not (tti.dstoffset or laststdoffset is None):
|
||||
tti.dstoffset = tti.offset - laststdoffset
|
||||
else:
|
||||
laststdoffset = tti.offset
|
||||
if not lastdst:
|
||||
dstoffset = offset - lastoffset
|
||||
|
||||
if not isinstance(tti.dstoffset, datetime.timedelta):
|
||||
tti.dstoffset = datetime.timedelta(seconds=tti.dstoffset)
|
||||
if not dstoffset and lastdstoffset:
|
||||
dstoffset = lastdstoffset
|
||||
|
||||
out.trans_idx[i] = tti
|
||||
tti.dstoffset = datetime.timedelta(seconds=dstoffset)
|
||||
lastdstoffset = dstoffset
|
||||
|
||||
# If a time zone changes its base offset during a DST transition,
|
||||
# then you need to adjust by the previous base offset to get the
|
||||
# transition time in local time. Otherwise you use the current
|
||||
# base offset. Ideally, I would have some mathematical proof of
|
||||
# why this is true, but I haven't really thought about it enough.
|
||||
baseoffset = offset - dstoffset
|
||||
adjustment = baseoffset
|
||||
if (lastbaseoffset is not None and baseoffset != lastbaseoffset
|
||||
and tti.isdst != lastdst):
|
||||
# The base DST has changed
|
||||
adjustment = lastbaseoffset
|
||||
|
||||
lastdst = tti.isdst
|
||||
lastoffset = offset
|
||||
lastbaseoffset = baseoffset
|
||||
|
||||
out.trans_list.append(out.trans_list_utc[i] + adjustment)
|
||||
|
||||
out.trans_idx = tuple(out.trans_idx)
|
||||
out.trans_list = tuple(out.trans_list)
|
||||
|
@ -840,8 +899,9 @@ class tzrange(tzrangebase):
|
|||
|
||||
:param start:
|
||||
A :class:`relativedelta.relativedelta` object or equivalent specifying
|
||||
the time and time of year that daylight savings time starts. To specify,
|
||||
for example, that DST starts at 2AM on the 2nd Sunday in March, pass:
|
||||
the time and time of year that daylight savings time starts. To
|
||||
specify, for example, that DST starts at 2AM on the 2nd Sunday in
|
||||
March, pass:
|
||||
|
||||
``relativedelta(hours=2, month=3, day=1, weekday=SU(+2))``
|
||||
|
||||
|
@ -849,12 +909,12 @@ class tzrange(tzrangebase):
|
|||
value is 2 AM on the first Sunday in April.
|
||||
|
||||
:param end:
|
||||
A :class:`relativedelta.relativedelta` object or equivalent representing
|
||||
the time and time of year that daylight savings time ends, with the
|
||||
same specification method as in ``start``. One note is that this should
|
||||
point to the first time in the *standard* zone, so if a transition
|
||||
occurs at 2AM in the DST zone and the clocks are set back 1 hour to 1AM,
|
||||
set the `hours` parameter to +1.
|
||||
A :class:`relativedelta.relativedelta` object or equivalent
|
||||
representing the time and time of year that daylight savings time
|
||||
ends, with the same specification method as in ``start``. One note is
|
||||
that this should point to the first time in the *standard* zone, so if
|
||||
a transition occurs at 2AM in the DST zone and the clocks are set back
|
||||
1 hour to 1AM, set the ``hours`` parameter to +1.
|
||||
|
||||
|
||||
**Examples:**
|
||||
|
@ -985,8 +1045,9 @@ class tzstr(tzrange):
|
|||
|
||||
:param s:
|
||||
A time zone string in ``TZ`` variable format. This can be a
|
||||
:class:`bytes` (2.x: :class:`str`), :class:`str` (2.x: :class:`unicode`)
|
||||
or a stream emitting unicode characters (e.g. :class:`StringIO`).
|
||||
:class:`bytes` (2.x: :class:`str`), :class:`str` (2.x:
|
||||
:class:`unicode`) or a stream emitting unicode characters
|
||||
(e.g. :class:`StringIO`).
|
||||
|
||||
:param posix_offset:
|
||||
Optional. If set to ``True``, interpret strings such as ``GMT+3`` or
|
||||
|
@ -1203,7 +1264,7 @@ class tzical(object):
|
|||
fileobj = open(fileobj, 'r')
|
||||
else:
|
||||
self._s = getattr(fileobj, 'name', repr(fileobj))
|
||||
fileobj = _ContextWrapper(fileobj)
|
||||
fileobj = _nullcontext(fileobj)
|
||||
|
||||
self._vtz = {}
|
||||
|
||||
|
@ -1398,15 +1459,85 @@ else:
|
|||
TZFILES = []
|
||||
TZPATHS = []
|
||||
|
||||
|
||||
def __get_gettz(name, zoneinfo_priority=False):
|
||||
tzlocal_classes = (tzlocal,)
|
||||
if tzwinlocal is not None:
|
||||
tzlocal_classes += (tzwinlocal,)
|
||||
|
||||
class GettzFunc(object):
|
||||
"""
|
||||
Retrieve a time zone object from a string representation
|
||||
|
||||
This function is intended to retrieve the :py:class:`tzinfo` subclass
|
||||
that best represents the time zone that would be used if a POSIX
|
||||
`TZ variable`_ were set to the same value.
|
||||
|
||||
If no argument or an empty string is passed to ``gettz``, local time
|
||||
is returned:
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
>>> gettz()
|
||||
tzfile('/etc/localtime')
|
||||
|
||||
This function is also the preferred way to map IANA tz database keys
|
||||
to :class:`tzfile` objects:
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
>>> gettz('Pacific/Kiritimati')
|
||||
tzfile('/usr/share/zoneinfo/Pacific/Kiritimati')
|
||||
|
||||
On Windows, the standard is extended to include the Windows-specific
|
||||
zone names provided by the operating system:
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
>>> gettz('Egypt Standard Time')
|
||||
tzwin('Egypt Standard Time')
|
||||
|
||||
Passing a GNU ``TZ`` style string time zone specification returns a
|
||||
:class:`tzstr` object:
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
>>> gettz('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3')
|
||||
tzstr('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3')
|
||||
|
||||
:param name:
|
||||
A time zone name (IANA, or, on Windows, Windows keys), location of
|
||||
a ``tzfile(5)`` zoneinfo file or ``TZ`` variable style time zone
|
||||
specifier. An empty string, no argument or ``None`` is interpreted
|
||||
as local time.
|
||||
|
||||
:return:
|
||||
Returns an instance of one of ``dateutil``'s :py:class:`tzinfo`
|
||||
subclasses.
|
||||
|
||||
.. versionchanged:: 2.7.0
|
||||
|
||||
After version 2.7.0, any two calls to ``gettz`` using the same
|
||||
input strings will return the same object:
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
>>> tz.gettz('America/Chicago') is tz.gettz('America/Chicago')
|
||||
True
|
||||
|
||||
In addition to improving performance, this ensures that
|
||||
`"same zone" semantics`_ are used for datetimes in the same zone.
|
||||
|
||||
|
||||
.. _`TZ variable`:
|
||||
https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
|
||||
|
||||
.. _`"same zone" semantics`:
|
||||
https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html
|
||||
"""
|
||||
def __init__(self, name, zoneinfo_priority=False):
|
||||
|
||||
self.__instances = {}
|
||||
self.__instances = weakref.WeakValueDictionary()
|
||||
self._cache_lock = _thread.allocate_lock()
|
||||
|
||||
def __call__(self, name=None, zoneinfo_priority=False):
|
||||
|
@ -1415,17 +1546,22 @@ def __get_gettz(name, zoneinfo_priority=False):
|
|||
|
||||
if rv is None:
|
||||
rv = self.nocache(name=name, zoneinfo_priority=zoneinfo_priority)
|
||||
if not (name is None or isinstance(rv, tzlocal_classes)):
|
||||
if not (name is None
|
||||
or isinstance(rv, tzlocal_classes)
|
||||
or rv is None):
|
||||
# tzlocal is slightly more complicated than the other
|
||||
# time zone providers because it depends on environment
|
||||
# at construction time, so don't cache that.
|
||||
#
|
||||
# We also cannot store weak references to None, so we
|
||||
# will also not store that.
|
||||
self.__instances[name] = rv
|
||||
|
||||
return rv
|
||||
|
||||
def cache_clear(self):
|
||||
with self._cache_lock:
|
||||
self.__instances = {}
|
||||
self.__instances = weakref.WeakValueDictionary()
|
||||
|
||||
@staticmethod
|
||||
def nocache(name=None, zoneinfo_priority=False):
|
||||
|
@ -1492,7 +1628,10 @@ def __get_gettz(name, zoneinfo_priority=False):
|
|||
|
||||
if not tz:
|
||||
for c in name:
|
||||
# name must have at least one offset to be a tzstr
|
||||
# name is not a tzstr unless it has at least
|
||||
# one offset. For short values of "name", an
|
||||
# explicit for loop seems to be the fastest way
|
||||
# To determine if a string contains a digit
|
||||
if c in "0123456789":
|
||||
try:
|
||||
tz = tzstr(name)
|
||||
|
@ -1508,9 +1647,11 @@ def __get_gettz(name, zoneinfo_priority=False):
|
|||
|
||||
return GettzFunc(name, zoneinfo_priority)
|
||||
|
||||
|
||||
gettz = __get_gettz(name=None, zoneinfo_priority=False)
|
||||
del __get_gettz
|
||||
|
||||
|
||||
def datetime_exists(dt, tz=None):
|
||||
"""
|
||||
Given a datetime and a time zone, determine whether or not a given datetime
|
||||
|
@ -1525,9 +1666,10 @@ def datetime_exists(dt, tz=None):
|
|||
``None`` or not provided, the datetime's own time zone will be used.
|
||||
|
||||
:return:
|
||||
Returns a boolean value whether or not the "wall time" exists in ``tz``.
|
||||
Returns a boolean value whether or not the "wall time" exists in
|
||||
``tz``.
|
||||
|
||||
..versionadded:: 2.7.0
|
||||
.. versionadded:: 2.7.0
|
||||
"""
|
||||
if tz is None:
|
||||
if dt.tzinfo is None:
|
||||
|
@ -1575,7 +1717,7 @@ def datetime_ambiguous(dt, tz=None):
|
|||
if is_ambiguous_fn is not None:
|
||||
try:
|
||||
return tz.is_ambiguous(dt)
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# If it doesn't come out and tell us it's ambiguous, we'll just check if
|
||||
|
@ -1598,7 +1740,8 @@ def resolve_imaginary(dt):
|
|||
wall time would be in a zone had the offset transition not occurred, so
|
||||
it will always fall forward by the transition's change in offset.
|
||||
|
||||
..doctest::
|
||||
.. doctest::
|
||||
|
||||
>>> from dateutil import tz
|
||||
>>> from datetime import datetime
|
||||
>>> NYC = tz.gettz('America/New_York')
|
||||
|
@ -1623,7 +1766,7 @@ def resolve_imaginary(dt):
|
|||
imaginary, the datetime returned is guaranteed to be the same object
|
||||
passed to the function.
|
||||
|
||||
..versionadded:: 2.7.0
|
||||
.. versionadded:: 2.7.0
|
||||
"""
|
||||
if dt.tzinfo is not None and not datetime_exists(dt):
|
||||
|
||||
|
@ -1637,13 +1780,31 @@ def resolve_imaginary(dt):
|
|||
|
||||
def _datetime_to_timestamp(dt):
|
||||
"""
|
||||
Convert a :class:`datetime.datetime` object to an epoch timestamp in seconds
|
||||
since January 1, 1970, ignoring the time zone.
|
||||
Convert a :class:`datetime.datetime` object to an epoch timestamp in
|
||||
seconds since January 1, 1970, ignoring the time zone.
|
||||
"""
|
||||
return (dt.replace(tzinfo=None) - EPOCH).total_seconds()
|
||||
|
||||
|
||||
class _ContextWrapper(object):
|
||||
if sys.version_info >= (3, 6):
|
||||
def _get_supported_offset(second_offset):
|
||||
return second_offset
|
||||
else:
|
||||
def _get_supported_offset(second_offset):
|
||||
# For python pre-3.6, round to full-minutes if that's not the case.
|
||||
# Python's datetime doesn't accept sub-minute timezones. Check
|
||||
# http://python.org/sf/1447945 or https://bugs.python.org/issue5288
|
||||
# for some information.
|
||||
old_offset = second_offset
|
||||
calculated_offset = 60 * ((second_offset + 30) // 60)
|
||||
return calculated_offset
|
||||
|
||||
|
||||
try:
|
||||
# Python 3.7 feature
|
||||
from contextmanager import nullcontext as _nullcontext
|
||||
except ImportError:
|
||||
class _nullcontext(object):
|
||||
"""
|
||||
Class for wrapping contexts so that they are passed through in a
|
||||
with statement.
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This module provides an interface to the native time zone data on Windows,
|
||||
including :py:class:`datetime.tzinfo` implementations.
|
||||
|
||||
Attempting to import this module on a non-Windows platform will raise an
|
||||
:py:obj:`ImportError`.
|
||||
"""
|
||||
# This code was originally contributed by Jeffrey Harris.
|
||||
import datetime
|
||||
import struct
|
||||
|
@ -39,7 +47,7 @@ TZKEYNAME = _settzkeyname()
|
|||
|
||||
class tzres(object):
|
||||
"""
|
||||
Class for accessing `tzres.dll`, which contains timezone name related
|
||||
Class for accessing ``tzres.dll``, which contains timezone name related
|
||||
resources.
|
||||
|
||||
.. versionadded:: 2.5.0
|
||||
|
@ -72,9 +80,10 @@ class tzres(object):
|
|||
:param offset:
|
||||
A positive integer value referring to a string from the tzres dll.
|
||||
|
||||
..note:
|
||||
.. note::
|
||||
|
||||
Offsets found in the registry are generally of the form
|
||||
`@tzres.dll,-114`. The offset in this case if 114, not -114.
|
||||
``@tzres.dll,-114``. The offset in this case is 114, not -114.
|
||||
|
||||
"""
|
||||
resource = self.p_wchar()
|
||||
|
@ -146,6 +155,9 @@ class tzwinbase(tzrangebase):
|
|||
return result
|
||||
|
||||
def display(self):
|
||||
"""
|
||||
Return the display name of the time zone.
|
||||
"""
|
||||
return self._display
|
||||
|
||||
def transitions(self, year):
|
||||
|
@ -188,6 +200,17 @@ class tzwinbase(tzrangebase):
|
|||
|
||||
|
||||
class tzwin(tzwinbase):
|
||||
"""
|
||||
Time zone object created from the zone info in the Windows registry
|
||||
|
||||
These are similar to :py:class:`dateutil.tz.tzrange` objects in that
|
||||
the time zone data is provided in the format of a single offset rule
|
||||
for either 0 or 2 time zone transitions per year.
|
||||
|
||||
:param: name
|
||||
The name of a Windows time zone key, e.g. "Eastern Standard Time".
|
||||
The full list of keys can be retrieved with :func:`tzwin.list`.
|
||||
"""
|
||||
|
||||
def __init__(self, name):
|
||||
self._name = name
|
||||
|
@ -234,6 +257,22 @@ class tzwin(tzwinbase):
|
|||
|
||||
|
||||
class tzwinlocal(tzwinbase):
|
||||
"""
|
||||
Class representing the local time zone information in the Windows registry
|
||||
|
||||
While :class:`dateutil.tz.tzlocal` makes system calls (via the :mod:`time`
|
||||
module) to retrieve time zone information, ``tzwinlocal`` retrieves the
|
||||
rules directly from the Windows registry and creates an object like
|
||||
:class:`dateutil.tz.tzwin`.
|
||||
|
||||
Because Windows does not have an equivalent of :func:`time.tzset`, on
|
||||
Windows, :class:`dateutil.tz.tzlocal` instances will always reflect the
|
||||
time zone settings *at the time that the process was started*, meaning
|
||||
changes to the machine's time zone settings during the run of a program
|
||||
on Windows will **not** be reflected by :class:`dateutil.tz.tzlocal`.
|
||||
Because ``tzwinlocal`` reads the registry directly, it is unaffected by
|
||||
this issue.
|
||||
"""
|
||||
def __init__(self):
|
||||
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
||||
with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey:
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This module offers general convenience and utility functions for dealing with
|
||||
datetimes.
|
||||
|
||||
.. versionadded:: 2.7.0
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from datetime import datetime, time
|
||||
|
|
Loading…
Reference in a new issue