mirror of
https://github.com/clinton-hall/nzbToMedia.git
synced 2025-08-14 18:47:09 -07:00
Update vendored guessit to 3.1.1
Updates python-dateutil to 2.8.2 Updates rebulk to 2.0.1
This commit is contained in:
parent
ebc9718117
commit
2226a74ef8
66 changed files with 2995 additions and 1306 deletions
|
@ -1,4 +1,5 @@
|
|||
# coding: utf-8
|
||||
# file generated by setuptools_scm
|
||||
# don't change, don't track in version control
|
||||
version = '2.7.5'
|
||||
version = '2.8.2'
|
||||
version_tuple = (2, 8, 2)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This module offers a generic easter computing method for any given year, using
|
||||
This module offers a generic Easter computing method for any given year, using
|
||||
Western, Orthodox or Julian algorithms.
|
||||
"""
|
||||
|
||||
|
@ -21,15 +21,15 @@ def easter(year, method=EASTER_WESTERN):
|
|||
quoted in "Explanatory Supplement to the Astronomical
|
||||
Almanac", P. Kenneth Seidelmann, editor.
|
||||
|
||||
This algorithm implements three different easter
|
||||
This algorithm implements three different Easter
|
||||
calculation methods:
|
||||
|
||||
1 - Original calculation in Julian calendar, valid in
|
||||
dates after 326 AD
|
||||
2 - Original method, with date converted to Gregorian
|
||||
calendar, valid in years 1583 to 4099
|
||||
3 - Revised method, in Gregorian calendar, valid in
|
||||
years 1583 to 4099 as well
|
||||
1. Original calculation in Julian calendar, valid in
|
||||
dates after 326 AD
|
||||
2. Original method, with date converted to Gregorian
|
||||
calendar, valid in years 1583 to 4099
|
||||
3. Revised method, in Gregorian calendar, valid in
|
||||
years 1583 to 4099 as well
|
||||
|
||||
These methods are represented by the constants:
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from ._parser import parse, parser, parserinfo
|
||||
from ._parser import parse, parser, parserinfo, ParserError
|
||||
from ._parser import DEFAULTPARSER, DEFAULTTZPARSER
|
||||
from ._parser import UnknownTimezoneWarning
|
||||
|
||||
|
@ -9,6 +9,7 @@ from .isoparser import isoparser, isoparse
|
|||
|
||||
__all__ = ['parse', 'parser', 'parserinfo',
|
||||
'isoparse', 'isoparser',
|
||||
'ParserError',
|
||||
'UnknownTimezoneWarning']
|
||||
|
||||
|
||||
|
|
|
@ -20,11 +20,11 @@ value falls back to the end of the month.
|
|||
Additional resources about date/time string formats can be found below:
|
||||
|
||||
- `A summary of the international standard date and time notation
|
||||
<http://www.cl.cam.ac.uk/~mgk25/iso-time.html>`_
|
||||
- `W3C Date and Time Formats <http://www.w3.org/TR/NOTE-datetime>`_
|
||||
<https://www.cl.cam.ac.uk/~mgk25/iso-time.html>`_
|
||||
- `W3C Date and Time Formats <https://www.w3.org/TR/NOTE-datetime>`_
|
||||
- `Time Formats (Planetary Rings Node) <https://pds-rings.seti.org:443/tools/time_formats.html>`_
|
||||
- `CPAN ParseDate module
|
||||
<http://search.cpan.org/~muir/Time-modules-2013.0912/lib/Time/ParseDate.pm>`_
|
||||
<https://metacpan.org/pod/release/MUIR/Time-modules-2013.0912/lib/Time/ParseDate.pm>`_
|
||||
- `Java SimpleDateFormat Class
|
||||
<https://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>`_
|
||||
"""
|
||||
|
@ -40,7 +40,7 @@ from calendar import monthrange
|
|||
from io import StringIO
|
||||
|
||||
import six
|
||||
from six import binary_type, integer_types, text_type
|
||||
from six import integer_types, text_type
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
|
@ -49,7 +49,7 @@ from warnings import warn
|
|||
from .. import relativedelta
|
||||
from .. import tz
|
||||
|
||||
__all__ = ["parse", "parserinfo"]
|
||||
__all__ = ["parse", "parserinfo", "ParserError"]
|
||||
|
||||
|
||||
# TODO: pandas.core.tools.datetimes imports this explicitly. Might be worth
|
||||
|
@ -60,14 +60,8 @@ class _timelex(object):
|
|||
_split_decimal = re.compile("([.,])")
|
||||
|
||||
def __init__(self, instream):
|
||||
if six.PY2:
|
||||
# In Python 2, we can't duck type properly because unicode has
|
||||
# a 'decode' function, and we'd be double-decoding
|
||||
if isinstance(instream, (binary_type, bytearray)):
|
||||
instream = instream.decode()
|
||||
else:
|
||||
if getattr(instream, 'decode', None) is not None:
|
||||
instream = instream.decode()
|
||||
if isinstance(instream, (bytes, bytearray)):
|
||||
instream = instream.decode()
|
||||
|
||||
if isinstance(instream, text_type):
|
||||
instream = StringIO(instream)
|
||||
|
@ -291,7 +285,7 @@ class parserinfo(object):
|
|||
("s", "second", "seconds")]
|
||||
AMPM = [("am", "a"),
|
||||
("pm", "p")]
|
||||
UTCZONE = ["UTC", "GMT", "Z"]
|
||||
UTCZONE = ["UTC", "GMT", "Z", "z"]
|
||||
PERTAIN = ["of"]
|
||||
TZOFFSET = {}
|
||||
# TODO: ERA = ["AD", "BC", "CE", "BCE", "Stardate",
|
||||
|
@ -388,7 +382,8 @@ class parserinfo(object):
|
|||
if res.year is not None:
|
||||
res.year = self.convertyear(res.year, res.century_specified)
|
||||
|
||||
if res.tzoffset == 0 and not res.tzname or res.tzname == 'Z':
|
||||
if ((res.tzoffset == 0 and not res.tzname) or
|
||||
(res.tzname == 'Z' or res.tzname == 'z')):
|
||||
res.tzname = "UTC"
|
||||
res.tzoffset = 0
|
||||
elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname):
|
||||
|
@ -422,7 +417,7 @@ class _ymd(list):
|
|||
elif not self.has_month:
|
||||
return 1 <= value <= 31
|
||||
elif not self.has_year:
|
||||
# Be permissive, assume leapyear
|
||||
# Be permissive, assume leap year
|
||||
month = self[self.mstridx]
|
||||
return 1 <= value <= monthrange(2000, month)[1]
|
||||
else:
|
||||
|
@ -538,7 +533,7 @@ class _ymd(list):
|
|||
year, month, day = self
|
||||
else:
|
||||
# 01-Jan-01
|
||||
# Give precendence to day-first, since
|
||||
# Give precedence to day-first, since
|
||||
# two-digit years is usually hand-written.
|
||||
day, month, year = self
|
||||
|
||||
|
@ -625,7 +620,7 @@ class parser(object):
|
|||
first element being a :class:`datetime.datetime` object, the second
|
||||
a tuple containing the fuzzy tokens.
|
||||
|
||||
:raises ValueError:
|
||||
:raises ParserError:
|
||||
Raised for invalid or unknown string format, if the provided
|
||||
:class:`tzinfo` is not in a valid format, or if an invalid date
|
||||
would be created.
|
||||
|
@ -645,12 +640,15 @@ class parser(object):
|
|||
res, skipped_tokens = self._parse(timestr, **kwargs)
|
||||
|
||||
if res is None:
|
||||
raise ValueError("Unknown string format:", timestr)
|
||||
raise ParserError("Unknown string format: %s", timestr)
|
||||
|
||||
if len(res) == 0:
|
||||
raise ValueError("String does not contain a date:", timestr)
|
||||
raise ParserError("String does not contain a date: %s", timestr)
|
||||
|
||||
ret = self._build_naive(res, default)
|
||||
try:
|
||||
ret = self._build_naive(res, default)
|
||||
except ValueError as e:
|
||||
six.raise_from(ParserError(str(e) + ": %s", timestr), e)
|
||||
|
||||
if not ignoretz:
|
||||
ret = self._build_tzaware(ret, res, tzinfos)
|
||||
|
@ -1021,7 +1019,7 @@ class parser(object):
|
|||
hms_idx = idx + 2
|
||||
|
||||
elif idx > 0 and info.hms(tokens[idx-1]) is not None:
|
||||
# There is a "h", "m", or "s" preceeding this token. Since neither
|
||||
# There is a "h", "m", or "s" preceding this token. Since neither
|
||||
# of the previous cases was hit, there is no label following this
|
||||
# token, so we use the previous label.
|
||||
# e.g. the "04" in "12h04"
|
||||
|
@ -1060,7 +1058,8 @@ class parser(object):
|
|||
tzname is None and
|
||||
tzoffset is None and
|
||||
len(token) <= 5 and
|
||||
all(x in string.ascii_uppercase for x in token))
|
||||
(all(x in string.ascii_uppercase for x in token)
|
||||
or token in self.info.UTCZONE))
|
||||
|
||||
def _ampm_valid(self, hour, ampm, fuzzy):
|
||||
"""
|
||||
|
@ -1100,7 +1099,7 @@ class parser(object):
|
|||
def _parse_min_sec(self, value):
|
||||
# TODO: Every usage of this function sets res.second to the return
|
||||
# value. Are there any cases where second will be returned as None and
|
||||
# we *dont* want to set res.second = None?
|
||||
# we *don't* want to set res.second = None?
|
||||
minute = int(value)
|
||||
second = None
|
||||
|
||||
|
@ -1109,14 +1108,6 @@ class parser(object):
|
|||
second = int(60 * sec_remainder)
|
||||
return (minute, second)
|
||||
|
||||
def _parsems(self, value):
|
||||
"""Parse a I[.F] seconds value into (seconds, microseconds)."""
|
||||
if "." not in value:
|
||||
return int(value), 0
|
||||
else:
|
||||
i, f = value.split(".")
|
||||
return int(i), int(f.ljust(6, "0")[:6])
|
||||
|
||||
def _parse_hms(self, idx, tokens, info, hms_idx):
|
||||
# TODO: Is this going to admit a lot of false-positives for when we
|
||||
# just happen to have digits and "h", "m" or "s" characters in non-date
|
||||
|
@ -1135,21 +1126,35 @@ class parser(object):
|
|||
|
||||
return (new_idx, hms)
|
||||
|
||||
def _recombine_skipped(self, tokens, skipped_idxs):
|
||||
"""
|
||||
>>> tokens = ["foo", " ", "bar", " ", "19June2000", "baz"]
|
||||
>>> skipped_idxs = [0, 1, 2, 5]
|
||||
>>> _recombine_skipped(tokens, skipped_idxs)
|
||||
["foo bar", "baz"]
|
||||
"""
|
||||
skipped_tokens = []
|
||||
for i, idx in enumerate(sorted(skipped_idxs)):
|
||||
if i > 0 and idx - 1 == skipped_idxs[i - 1]:
|
||||
skipped_tokens[-1] = skipped_tokens[-1] + tokens[idx]
|
||||
else:
|
||||
skipped_tokens.append(tokens[idx])
|
||||
# ------------------------------------------------------------------
|
||||
# Handling for individual tokens. These are kept as methods instead
|
||||
# of functions for the sake of customizability via subclassing.
|
||||
|
||||
return skipped_tokens
|
||||
def _parsems(self, value):
|
||||
"""Parse a I[.F] seconds value into (seconds, microseconds)."""
|
||||
if "." not in value:
|
||||
return int(value), 0
|
||||
else:
|
||||
i, f = value.split(".")
|
||||
return int(i), int(f.ljust(6, "0")[:6])
|
||||
|
||||
def _to_decimal(self, val):
|
||||
try:
|
||||
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
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Post-Parsing construction of datetime output. These are kept as
|
||||
# methods instead of functions for the sake of customizability via
|
||||
# subclassing.
|
||||
|
||||
def _build_tzinfo(self, tzinfos, tzname, tzoffset):
|
||||
if callable(tzinfos):
|
||||
|
@ -1164,6 +1169,9 @@ class parser(object):
|
|||
tzinfo = tz.tzstr(tzdata)
|
||||
elif isinstance(tzdata, integer_types):
|
||||
tzinfo = tz.tzoffset(tzname, tzdata)
|
||||
else:
|
||||
raise TypeError("Offset must be tzinfo subclass, tz string, "
|
||||
"or int offset.")
|
||||
return tzinfo
|
||||
|
||||
def _build_tzaware(self, naive, res, tzinfos):
|
||||
|
@ -1181,10 +1189,10 @@ class parser(object):
|
|||
# This is mostly relevant for winter GMT zones parsed in the UK
|
||||
if (aware.tzname() != res.tzname and
|
||||
res.tzname in self.info.UTCZONE):
|
||||
aware = aware.replace(tzinfo=tz.tzutc())
|
||||
aware = aware.replace(tzinfo=tz.UTC)
|
||||
|
||||
elif res.tzoffset == 0:
|
||||
aware = naive.replace(tzinfo=tz.tzutc())
|
||||
aware = naive.replace(tzinfo=tz.UTC)
|
||||
|
||||
elif res.tzoffset:
|
||||
aware = naive.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset))
|
||||
|
@ -1239,17 +1247,21 @@ class parser(object):
|
|||
|
||||
return dt
|
||||
|
||||
def _to_decimal(self, val):
|
||||
try:
|
||||
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
|
||||
def _recombine_skipped(self, tokens, skipped_idxs):
|
||||
"""
|
||||
>>> tokens = ["foo", " ", "bar", " ", "19June2000", "baz"]
|
||||
>>> skipped_idxs = [0, 1, 2, 5]
|
||||
>>> _recombine_skipped(tokens, skipped_idxs)
|
||||
["foo bar", "baz"]
|
||||
"""
|
||||
skipped_tokens = []
|
||||
for i, idx in enumerate(sorted(skipped_idxs)):
|
||||
if i > 0 and idx - 1 == skipped_idxs[i - 1]:
|
||||
skipped_tokens[-1] = skipped_tokens[-1] + tokens[idx]
|
||||
else:
|
||||
skipped_tokens.append(tokens[idx])
|
||||
|
||||
return skipped_tokens
|
||||
|
||||
|
||||
DEFAULTPARSER = parser()
|
||||
|
@ -1341,10 +1353,10 @@ def parse(timestr, parserinfo=None, **kwargs):
|
|||
first element being a :class:`datetime.datetime` object, the second
|
||||
a tuple containing the fuzzy tokens.
|
||||
|
||||
:raises ValueError:
|
||||
Raised for invalid or unknown string format, if the provided
|
||||
:class:`tzinfo` is not in a valid format, or if an invalid date
|
||||
would be created.
|
||||
:raises ParserError:
|
||||
Raised for invalid or unknown string formats, if the provided
|
||||
:class:`tzinfo` is not in a valid format, or if an invalid date would
|
||||
be created.
|
||||
|
||||
:raises OverflowError:
|
||||
Raised if the parsed date exceeds the largest valid C integer on
|
||||
|
@ -1573,6 +1585,29 @@ DEFAULTTZPARSER = _tzparser()
|
|||
def _parsetz(tzstr):
|
||||
return DEFAULTTZPARSER.parse(tzstr)
|
||||
|
||||
|
||||
class ParserError(ValueError):
|
||||
"""Exception subclass used for any failure to parse a datetime string.
|
||||
|
||||
This is a subclass of :py:exc:`ValueError`, and should be raised any time
|
||||
earlier versions of ``dateutil`` would have raised ``ValueError``.
|
||||
|
||||
.. versionadded:: 2.8.1
|
||||
"""
|
||||
def __str__(self):
|
||||
try:
|
||||
return self.args[0] % self.args[1:]
|
||||
except (TypeError, IndexError):
|
||||
return super(ParserError, self).__str__()
|
||||
|
||||
def __repr__(self):
|
||||
args = ", ".join("'%s'" % arg for arg in self.args)
|
||||
return "%s(%s)" % (self.__class__.__name__, args)
|
||||
|
||||
|
||||
class UnknownTimezoneWarning(RuntimeWarning):
|
||||
"""Raised when the parser finds a timezone it cannot parse into a tzinfo"""
|
||||
"""Raised when the parser finds a timezone it cannot parse into a tzinfo.
|
||||
|
||||
.. versionadded:: 2.7.0
|
||||
"""
|
||||
# vim:ts=4:sw=4:et
|
||||
|
|
|
@ -88,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::
|
||||
|
||||
|
@ -137,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
|
||||
|
@ -153,7 +159,7 @@ class isoparser(object):
|
|||
components, pos = self._parse_isodate(datestr)
|
||||
if pos < len(datestr):
|
||||
raise ValueError('String contains unknown ISO ' +
|
||||
'components: {}'.format(datestr))
|
||||
'components: {!r}'.format(datestr.decode('ascii')))
|
||||
return date(*components)
|
||||
|
||||
@_takes_ascii
|
||||
|
@ -167,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):
|
||||
|
@ -190,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:
|
||||
|
@ -325,39 +333,42 @@ class isoparser(object):
|
|||
pos = 0
|
||||
comp = -1
|
||||
|
||||
if len(timestr) < 2:
|
||||
if len_str < 2:
|
||||
raise ValueError('ISO time too short')
|
||||
|
||||
has_sep = len_str >= 3 and timestr[2:3] == self._TIME_SEP
|
||||
has_sep = False
|
||||
|
||||
while pos < len_str and comp < 5:
|
||||
comp += 1
|
||||
|
||||
if timestr[pos:pos + 1] in b'-+Z':
|
||||
if timestr[pos:pos + 1] in b'-+Zz':
|
||||
# Detect time zone boundary
|
||||
components[-1] = self._parse_tzstr(timestr[pos:])
|
||||
pos = len_str
|
||||
break
|
||||
|
||||
if comp == 1 and timestr[pos:pos+1] == self._TIME_SEP:
|
||||
has_sep = True
|
||||
pos += 1
|
||||
elif comp == 2 and has_sep:
|
||||
if timestr[pos:pos+1] != self._TIME_SEP:
|
||||
raise ValueError('Inconsistent use of colon separator')
|
||||
pos += 1
|
||||
|
||||
if comp < 3:
|
||||
# Hour, minute, second
|
||||
components[comp] = int(timestr[pos:pos + 2])
|
||||
pos += 2
|
||||
if (has_sep and pos < len_str and
|
||||
timestr[pos:pos + 1] == self._TIME_SEP):
|
||||
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')
|
||||
|
@ -366,13 +377,12 @@ 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
|
||||
|
||||
def _parse_tzstr(self, tzstr, zero_as_utc=True):
|
||||
if tzstr == b'Z':
|
||||
return tz.tzutc()
|
||||
if tzstr == b'Z' or tzstr == b'z':
|
||||
return tz.UTC
|
||||
|
||||
if len(tzstr) not in {3, 5, 6}:
|
||||
raise ValueError('Time zone offset must be 1, 3, 5 or 6 characters')
|
||||
|
@ -391,7 +401,7 @@ class isoparser(object):
|
|||
minutes = int(tzstr[(4 if tzstr[3:4] == self._TIME_SEP else 3):])
|
||||
|
||||
if zero_as_utc and hours == 0 and minutes == 0:
|
||||
return tz.tzutc()
|
||||
return tz.UTC
|
||||
else:
|
||||
if minutes > 59:
|
||||
raise ValueError('Invalid minutes in time zone offset')
|
||||
|
|
|
@ -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.
|
||||
|
@ -41,17 +45,19 @@ class relativedelta(object):
|
|||
years, months, weeks, days, hours, minutes, seconds, microseconds:
|
||||
Relative information, may be negative (argument is plural); adding
|
||||
or subtracting a relativedelta with relative information performs
|
||||
the corresponding aritmetic operation on the original datetime value
|
||||
the corresponding arithmetic operation on the original datetime value
|
||||
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. Notice that
|
||||
if the calculated date is already Monday, for example,
|
||||
using MO(1) or MO(-1) won't change the day.
|
||||
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
|
||||
|
@ -82,9 +88,12 @@ class relativedelta(object):
|
|||
|
||||
For example
|
||||
|
||||
>>> 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))
|
||||
datetime(2018, 4, 2, 14, 37, 0)
|
||||
>>> dt + delta
|
||||
datetime.datetime(2018, 4, 2, 14, 37)
|
||||
|
||||
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
|
||||
|
@ -276,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.
|
||||
|
|
|
@ -5,27 +5,27 @@ the recurrence rules documented in the
|
|||
`iCalendar RFC <https://tools.ietf.org/html/rfc5545>`_,
|
||||
including support for caching of results.
|
||||
"""
|
||||
import itertools
|
||||
import datetime
|
||||
import calendar
|
||||
import datetime
|
||||
import heapq
|
||||
import itertools
|
||||
import re
|
||||
import sys
|
||||
from functools import wraps
|
||||
# For warning about deprecation of until and count
|
||||
from warnings import warn
|
||||
|
||||
from six import advance_iterator, integer_types
|
||||
|
||||
from six.moves import _thread, range
|
||||
|
||||
from ._common import weekday as weekdaybase
|
||||
|
||||
try:
|
||||
from math import gcd
|
||||
except ImportError:
|
||||
from fractions import gcd
|
||||
|
||||
from six import advance_iterator, integer_types
|
||||
from six.moves import _thread, range
|
||||
import heapq
|
||||
|
||||
from ._common import weekday as weekdaybase
|
||||
from .tz import tzutc, tzlocal
|
||||
|
||||
# For warning about deprecation of until and count
|
||||
from warnings import warn
|
||||
|
||||
__all__ = ["rrule", "rruleset", "rrulestr",
|
||||
"YEARLY", "MONTHLY", "WEEKLY", "DAILY",
|
||||
"HOURLY", "MINUTELY", "SECONDLY",
|
||||
|
@ -82,6 +82,7 @@ def _invalidates_cache(f):
|
|||
Decorator for rruleset methods which may invalidate the
|
||||
cached length.
|
||||
"""
|
||||
@wraps(f)
|
||||
def inner_func(self, *args, **kwargs):
|
||||
rv = f(self, *args, **kwargs)
|
||||
self._invalidate_cache()
|
||||
|
@ -178,7 +179,7 @@ class rrulebase(object):
|
|||
return False
|
||||
return False
|
||||
|
||||
# __len__() introduces a large performance penality.
|
||||
# __len__() introduces a large performance penalty.
|
||||
def count(self):
|
||||
""" Returns the number of recurrences in this set. It will have go
|
||||
trough the whole recurrence, if this hasn't been done before. """
|
||||
|
@ -353,20 +354,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
|
||||
|
@ -429,7 +436,7 @@ class rrule(rrulebase):
|
|||
if not dtstart:
|
||||
if until and until.tzinfo:
|
||||
dtstart = datetime.datetime.now(tz=until.tzinfo).replace(microsecond=0)
|
||||
else:
|
||||
else:
|
||||
dtstart = datetime.datetime.now().replace(microsecond=0)
|
||||
elif not isinstance(dtstart, datetime.datetime):
|
||||
dtstart = datetime.datetime.fromordinal(dtstart.toordinal())
|
||||
|
@ -1406,7 +1413,52 @@ class rruleset(rrulebase):
|
|||
self._len = total
|
||||
|
||||
|
||||
|
||||
|
||||
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,
|
||||
|
@ -1508,6 +1560,58 @@ class _rrulestr(object):
|
|||
raise ValueError("invalid '%s': %s" % (name, value))
|
||||
return rrule(dtstart=dtstart, cache=cache, **rrkwargs)
|
||||
|
||||
def _parse_date_value(self, date_value, parms, rule_tzids,
|
||||
ignoretz, tzids, tzinfos):
|
||||
global parser
|
||||
if not parser:
|
||||
from dateutil import parser
|
||||
|
||||
datevals = []
|
||||
value_found = False
|
||||
TZID = None
|
||||
|
||||
for parm in parms:
|
||||
if parm.startswith("TZID="):
|
||||
try:
|
||||
tzkey = rule_tzids[parm.split('TZID=')[-1]]
|
||||
except KeyError:
|
||||
continue
|
||||
if tzids is None:
|
||||
from . import tz
|
||||
tzlookup = tz.gettz
|
||||
elif callable(tzids):
|
||||
tzlookup = tzids
|
||||
else:
|
||||
tzlookup = getattr(tzids, 'get', None)
|
||||
if tzlookup is None:
|
||||
msg = ('tzids must be a callable, mapping, or None, '
|
||||
'not %s' % tzids)
|
||||
raise ValueError(msg)
|
||||
|
||||
TZID = tzlookup(tzkey)
|
||||
continue
|
||||
|
||||
# RFC 5445 3.8.2.4: The VALUE parameter is optional, but may be found
|
||||
# only once.
|
||||
if parm not in {"VALUE=DATE-TIME", "VALUE=DATE"}:
|
||||
raise ValueError("unsupported parm: " + parm)
|
||||
else:
|
||||
if value_found:
|
||||
msg = ("Duplicate value parameter found in: " + parm)
|
||||
raise ValueError(msg)
|
||||
value_found = True
|
||||
|
||||
for datestr in date_value.split(','):
|
||||
date = parser.parse(datestr, ignoretz=ignoretz, tzinfos=tzinfos)
|
||||
if TZID is not None:
|
||||
if date.tzinfo is None:
|
||||
date = date.replace(tzinfo=TZID)
|
||||
else:
|
||||
raise ValueError('DTSTART/EXDATE specifies multiple timezone')
|
||||
datevals.append(date)
|
||||
|
||||
return datevals
|
||||
|
||||
def _parse_rfc(self, s,
|
||||
dtstart=None,
|
||||
cache=False,
|
||||
|
@ -1580,54 +1684,18 @@ class _rrulestr(object):
|
|||
raise ValueError("unsupported EXRULE parm: "+parm)
|
||||
exrulevals.append(value)
|
||||
elif name == "EXDATE":
|
||||
for parm in parms:
|
||||
if parm != "VALUE=DATE-TIME":
|
||||
raise ValueError("unsupported EXDATE parm: "+parm)
|
||||
exdatevals.append(value)
|
||||
exdatevals.extend(
|
||||
self._parse_date_value(value, parms,
|
||||
TZID_NAMES, ignoretz,
|
||||
tzids, tzinfos)
|
||||
)
|
||||
elif name == "DTSTART":
|
||||
# RFC 5445 3.8.2.4: The VALUE parameter is optional, but
|
||||
# may be found only once.
|
||||
value_found = False
|
||||
TZID = None
|
||||
valid_values = {"VALUE=DATE-TIME", "VALUE=DATE"}
|
||||
for parm in parms:
|
||||
if parm.startswith("TZID="):
|
||||
try:
|
||||
tzkey = TZID_NAMES[parm.split('TZID=')[-1]]
|
||||
except KeyError:
|
||||
continue
|
||||
if tzids is None:
|
||||
from . import tz
|
||||
tzlookup = tz.gettz
|
||||
elif callable(tzids):
|
||||
tzlookup = tzids
|
||||
else:
|
||||
tzlookup = getattr(tzids, 'get', None)
|
||||
if tzlookup is None:
|
||||
msg = ('tzids must be a callable, ' +
|
||||
'mapping, or None, ' +
|
||||
'not %s' % tzids)
|
||||
raise ValueError(msg)
|
||||
|
||||
TZID = tzlookup(tzkey)
|
||||
continue
|
||||
if parm not in valid_values:
|
||||
raise ValueError("unsupported DTSTART parm: "+parm)
|
||||
else:
|
||||
if value_found:
|
||||
msg = ("Duplicate value parameter found in " +
|
||||
"DTSTART: " + parm)
|
||||
raise ValueError(msg)
|
||||
value_found = True
|
||||
if not parser:
|
||||
from dateutil import parser
|
||||
dtstart = parser.parse(value, ignoretz=ignoretz,
|
||||
tzinfos=tzinfos)
|
||||
if TZID is not None:
|
||||
if dtstart.tzinfo is None:
|
||||
dtstart = dtstart.replace(tzinfo=TZID)
|
||||
else:
|
||||
raise ValueError('DTSTART specifies multiple timezones')
|
||||
dtvals = self._parse_date_value(value, parms, TZID_NAMES,
|
||||
ignoretz, tzids, tzinfos)
|
||||
if len(dtvals) != 1:
|
||||
raise ValueError("Multiple DTSTART values specified:" +
|
||||
value)
|
||||
dtstart = dtvals[0]
|
||||
else:
|
||||
raise ValueError("unsupported property: "+name)
|
||||
if (forceset or len(rrulevals) > 1 or rdatevals
|
||||
|
@ -1649,10 +1717,7 @@ class _rrulestr(object):
|
|||
ignoretz=ignoretz,
|
||||
tzinfos=tzinfos))
|
||||
for value in exdatevals:
|
||||
for datestr in value.split(','):
|
||||
rset.exdate(parser.parse(datestr,
|
||||
ignoretz=ignoretz,
|
||||
tzinfos=tzinfos))
|
||||
rset.exdate(value)
|
||||
if compatible and dtstart:
|
||||
rset.rdate(dtstart)
|
||||
return rset
|
||||
|
|
|
@ -2,11 +2,6 @@
|
|||
from .tz import *
|
||||
from .tz import __doc__
|
||||
|
||||
#: Convenience constant providing a :class:`tzutc()` instance
|
||||
#:
|
||||
#: .. versionadded:: 2.7.0
|
||||
UTC = tzutc()
|
||||
|
||||
__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
|
||||
"tzstr", "tzical", "tzwin", "tzwinlocal", "gettz",
|
||||
"enfold", "datetime_ambiguous", "datetime_exists",
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
def adjust_encoding(*args, **kwargs):
|
||||
name = namefunc(*args, **kwargs)
|
||||
if name is not None and not PY3:
|
||||
name = name.encode()
|
||||
if PY2:
|
||||
@wraps(namefunc)
|
||||
def adjust_encoding(*args, **kwargs):
|
||||
name = namefunc(*args, **kwargs)
|
||||
if name is not None:
|
||||
name = name.encode()
|
||||
|
||||
return name
|
||||
return name
|
||||
|
||||
return adjust_encoding
|
||||
return adjust_encoding
|
||||
else:
|
||||
return namefunc
|
||||
|
||||
|
||||
# The following is adapted from Alexander Belopolsky's tz library
|
||||
|
@ -208,7 +212,7 @@ class _tzinfo(tzinfo):
|
|||
Since this is the one time that we *know* we have an unambiguous
|
||||
datetime object, we take this opportunity to determine whether the
|
||||
datetime is ambiguous and in a "fold" state (e.g. if it's the first
|
||||
occurence, chronologically, of the ambiguous datetime).
|
||||
occurrence, chronologically, of the ambiguous datetime).
|
||||
|
||||
:param dt:
|
||||
A timezone-aware :class:`datetime.datetime` object.
|
||||
|
@ -246,7 +250,7 @@ class _tzinfo(tzinfo):
|
|||
Since this is the one time that we *know* we have an unambiguous
|
||||
datetime object, we take this opportunity to determine whether the
|
||||
datetime is ambiguous and in a "fold" state (e.g. if it's the first
|
||||
occurance, chronologically, of the ambiguous datetime).
|
||||
occurrence, chronologically, of the ambiguous datetime).
|
||||
|
||||
:param dt:
|
||||
A timezone-aware :class:`datetime.datetime` object.
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
from datetime import timedelta
|
||||
import weakref
|
||||
from collections import OrderedDict
|
||||
|
||||
from six.moves import _thread
|
||||
|
||||
|
||||
class _TzSingleton(type):
|
||||
|
@ -11,6 +15,7 @@ class _TzSingleton(type):
|
|||
cls.__instance = super(_TzSingleton, cls).__call__()
|
||||
return cls.__instance
|
||||
|
||||
|
||||
class _TzFactory(type):
|
||||
def instance(cls, *args, **kwargs):
|
||||
"""Alternate constructor that returns a fresh instance"""
|
||||
|
@ -19,7 +24,11 @@ class _TzFactory(type):
|
|||
|
||||
class _TzOffsetFactory(_TzFactory):
|
||||
def __init__(cls, *args, **kwargs):
|
||||
cls.__instances = {}
|
||||
cls.__instances = weakref.WeakValueDictionary()
|
||||
cls.__strong_cache = OrderedDict()
|
||||
cls.__strong_cache_size = 8
|
||||
|
||||
cls._cache_lock = _thread.allocate_lock()
|
||||
|
||||
def __call__(cls, name, offset):
|
||||
if isinstance(offset, timedelta):
|
||||
|
@ -31,12 +40,25 @@ class _TzOffsetFactory(_TzFactory):
|
|||
if instance is None:
|
||||
instance = cls.__instances.setdefault(key,
|
||||
cls.instance(name, offset))
|
||||
|
||||
# This lock may not be necessary in Python 3. See GH issue #901
|
||||
with cls._cache_lock:
|
||||
cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance)
|
||||
|
||||
# Remove an item if the strong cache is overpopulated
|
||||
if len(cls.__strong_cache) > cls.__strong_cache_size:
|
||||
cls.__strong_cache.popitem(last=False)
|
||||
|
||||
return instance
|
||||
|
||||
|
||||
class _TzStrFactory(_TzFactory):
|
||||
def __init__(cls, *args, **kwargs):
|
||||
cls.__instances = {}
|
||||
cls.__instances = weakref.WeakValueDictionary()
|
||||
cls.__strong_cache = OrderedDict()
|
||||
cls.__strong_cache_size = 8
|
||||
|
||||
cls.__cache_lock = _thread.allocate_lock()
|
||||
|
||||
def __call__(cls, s, posix_offset=False):
|
||||
key = (s, posix_offset)
|
||||
|
@ -45,5 +67,14 @@ class _TzStrFactory(_TzFactory):
|
|||
if instance is None:
|
||||
instance = cls.__instances.setdefault(key,
|
||||
cls.instance(s, posix_offset))
|
||||
|
||||
# This lock may not be necessary in Python 3. See GH issue #901
|
||||
with cls.__cache_lock:
|
||||
cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance)
|
||||
|
||||
# Remove an item if the strong cache is overpopulated
|
||||
if len(cls.__strong_cache) > cls.__strong_cache_size:
|
||||
cls.__strong_cache.popitem(last=False)
|
||||
|
||||
return instance
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ import time
|
|||
import sys
|
||||
import os
|
||||
import bisect
|
||||
import weakref
|
||||
from collections import OrderedDict
|
||||
|
||||
import six
|
||||
from six import string_types
|
||||
|
@ -28,6 +30,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()
|
||||
|
@ -118,6 +123,12 @@ class tzutc(datetime.tzinfo):
|
|||
__reduce__ = object.__reduce__
|
||||
|
||||
|
||||
#: Convenience constant providing a :class:`tzutc()` instance
|
||||
#:
|
||||
#: .. versionadded:: 2.7.0
|
||||
UTC = tzutc()
|
||||
|
||||
|
||||
@six.add_metaclass(_TzOffsetFactory)
|
||||
class tzoffset(datetime.tzinfo):
|
||||
"""
|
||||
|
@ -137,7 +148,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
|
||||
|
@ -373,7 +385,7 @@ class _tzfile(object):
|
|||
|
||||
class tzfile(_tzinfo):
|
||||
"""
|
||||
This is a ``tzinfo`` subclass thant allows one to use the ``tzfile(5)``
|
||||
This is a ``tzinfo`` subclass that allows one to use the ``tzfile(5)``
|
||||
format timezone files to extract current and historical zone information.
|
||||
|
||||
:param fileobj:
|
||||
|
@ -460,7 +472,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)
|
||||
|
@ -600,10 +612,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)
|
||||
|
@ -655,37 +664,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
|
||||
offset = tti.offset
|
||||
dstoffset = 0
|
||||
|
||||
offset = laststdoffset or 0
|
||||
if lastdst is not None:
|
||||
if tti.isdst:
|
||||
if not lastdst:
|
||||
dstoffset = offset - lastoffset
|
||||
|
||||
out.trans_list.append(out.trans_list_utc[i] + offset)
|
||||
if not dstoffset and lastdstoffset:
|
||||
dstoffset = lastdstoffset
|
||||
|
||||
# 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 tti.isdst:
|
||||
if not (tti.dstoffset or laststdoffset is None):
|
||||
tti.dstoffset = tti.offset - laststdoffset
|
||||
else:
|
||||
laststdoffset = tti.offset
|
||||
tti.dstoffset = datetime.timedelta(seconds=dstoffset)
|
||||
lastdstoffset = dstoffset
|
||||
|
||||
if not isinstance(tti.dstoffset, datetime.timedelta):
|
||||
tti.dstoffset = datetime.timedelta(seconds=tti.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
|
||||
|
||||
out.trans_idx[i] = tti
|
||||
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)
|
||||
|
@ -1255,7 +1271,7 @@ class tzical(object):
|
|||
fileobj = open(fileobj, 'r')
|
||||
else:
|
||||
self._s = getattr(fileobj, 'name', repr(fileobj))
|
||||
fileobj = _ContextWrapper(fileobj)
|
||||
fileobj = _nullcontext(fileobj)
|
||||
|
||||
self._vtz = {}
|
||||
|
||||
|
@ -1528,7 +1544,9 @@ def __get_gettz():
|
|||
"""
|
||||
def __init__(self):
|
||||
|
||||
self.__instances = {}
|
||||
self.__instances = weakref.WeakValueDictionary()
|
||||
self.__strong_cache_size = 8
|
||||
self.__strong_cache = OrderedDict()
|
||||
self._cache_lock = _thread.allocate_lock()
|
||||
|
||||
def __call__(self, name=None):
|
||||
|
@ -1537,17 +1555,37 @@ def __get_gettz():
|
|||
|
||||
if rv is None:
|
||||
rv = self.nocache(name=name)
|
||||
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
|
||||
else:
|
||||
# No need for strong caching, return immediately
|
||||
return rv
|
||||
|
||||
self.__strong_cache[name] = self.__strong_cache.pop(name, rv)
|
||||
|
||||
if len(self.__strong_cache) > self.__strong_cache_size:
|
||||
self.__strong_cache.popitem(last=False)
|
||||
|
||||
return rv
|
||||
|
||||
def set_cache_size(self, size):
|
||||
with self._cache_lock:
|
||||
self.__strong_cache_size = size
|
||||
while len(self.__strong_cache) > size:
|
||||
self.__strong_cache.popitem(last=False)
|
||||
|
||||
def cache_clear(self):
|
||||
with self._cache_lock:
|
||||
self.__instances = {}
|
||||
self.__instances = weakref.WeakValueDictionary()
|
||||
self.__strong_cache.clear()
|
||||
|
||||
@staticmethod
|
||||
def nocache(name=None):
|
||||
|
@ -1558,7 +1596,7 @@ def __get_gettz():
|
|||
name = os.environ["TZ"]
|
||||
except KeyError:
|
||||
pass
|
||||
if name is None or name == ":":
|
||||
if name is None or name in ("", ":"):
|
||||
for filepath in TZFILES:
|
||||
if not os.path.isabs(filepath):
|
||||
filename = filepath
|
||||
|
@ -1577,8 +1615,15 @@ def __get_gettz():
|
|||
else:
|
||||
tz = tzlocal()
|
||||
else:
|
||||
if name.startswith(":"):
|
||||
name = name[1:]
|
||||
try:
|
||||
if name.startswith(":"):
|
||||
name = name[1:]
|
||||
except TypeError as e:
|
||||
if isinstance(name, bytes):
|
||||
new_msg = "gettz argument should be str, not bytes"
|
||||
six.raise_from(TypeError(new_msg), e)
|
||||
else:
|
||||
raise
|
||||
if os.path.isabs(name):
|
||||
if os.path.isfile(name):
|
||||
tz = tzfile(name)
|
||||
|
@ -1601,7 +1646,8 @@ def __get_gettz():
|
|||
if tzwin is not None:
|
||||
try:
|
||||
tz = tzwin(name)
|
||||
except WindowsError:
|
||||
except (WindowsError, UnicodeEncodeError):
|
||||
# UnicodeEncodeError is for Python 2.7 compat
|
||||
tz = None
|
||||
|
||||
if not tz:
|
||||
|
@ -1622,7 +1668,7 @@ def __get_gettz():
|
|||
break
|
||||
else:
|
||||
if name in ("GMT", "UTC"):
|
||||
tz = tzutc()
|
||||
tz = UTC
|
||||
elif name in time.tzname:
|
||||
tz = tzlocal()
|
||||
return tz
|
||||
|
@ -1662,7 +1708,7 @@ def datetime_exists(dt, tz=None):
|
|||
|
||||
# This is essentially a test of whether or not the datetime can survive
|
||||
# a round trip to UTC.
|
||||
dt_rt = dt.replace(tzinfo=tz).astimezone(tzutc()).astimezone(tz)
|
||||
dt_rt = dt.replace(tzinfo=tz).astimezone(UTC).astimezone(tz)
|
||||
dt_rt = dt_rt.replace(tzinfo=None)
|
||||
|
||||
return dt == dt_rt
|
||||
|
@ -1768,18 +1814,36 @@ def _datetime_to_timestamp(dt):
|
|||
return (dt.replace(tzinfo=None) - EPOCH).total_seconds()
|
||||
|
||||
|
||||
class _ContextWrapper(object):
|
||||
"""
|
||||
Class for wrapping contexts so that they are passed through in a
|
||||
with statement.
|
||||
"""
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
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
|
||||
|
||||
def __enter__(self):
|
||||
return self.context
|
||||
|
||||
def __exit__(*args, **kwargs):
|
||||
pass
|
||||
try:
|
||||
# Python 3.7 feature
|
||||
from contextlib import nullcontext as _nullcontext
|
||||
except ImportError:
|
||||
class _nullcontext(object):
|
||||
"""
|
||||
Class for wrapping contexts so that they are passed through in a
|
||||
with statement.
|
||||
"""
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
|
||||
def __enter__(self):
|
||||
return self.context
|
||||
|
||||
def __exit__(*args, **kwargs):
|
||||
pass
|
||||
|
||||
# vim:ts=4:sw=4:et
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -28,7 +28,7 @@ def today(tzinfo=None):
|
|||
|
||||
def default_tzinfo(dt, tzinfo):
|
||||
"""
|
||||
Sets the the ``tzinfo`` parameter on naive datetimes only
|
||||
Sets the ``tzinfo`` parameter on naive datetimes only
|
||||
|
||||
This is useful for example when you are provided a datetime that may have
|
||||
either an implicit or explicit time zone, such as when parsing a time zone
|
||||
|
@ -63,7 +63,7 @@ def default_tzinfo(dt, tzinfo):
|
|||
|
||||
def within_delta(dt1, dt2, delta):
|
||||
"""
|
||||
Useful for comparing two datetimes that may a negilible difference
|
||||
Useful for comparing two datetimes that may have a negligible difference
|
||||
to be considered equal.
|
||||
"""
|
||||
delta = abs(delta)
|
||||
|
|
Binary file not shown.
|
@ -3,7 +3,7 @@ import os
|
|||
import tempfile
|
||||
import shutil
|
||||
import json
|
||||
from subprocess import check_call
|
||||
from subprocess import check_call, check_output
|
||||
from tarfile import TarFile
|
||||
|
||||
from dateutil.zoneinfo import METADATA_FN, ZONEFILENAME
|
||||
|
@ -23,11 +23,9 @@ def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None):
|
|||
for name in zonegroups:
|
||||
tf.extract(name, tmpdir)
|
||||
filepaths = [os.path.join(tmpdir, n) for n in zonegroups]
|
||||
try:
|
||||
check_call(["zic", "-d", zonedir] + filepaths)
|
||||
except OSError as e:
|
||||
_print_on_nosuchfile(e)
|
||||
raise
|
||||
|
||||
_run_zic(zonedir, filepaths)
|
||||
|
||||
# write metadata file
|
||||
with open(os.path.join(zonedir, METADATA_FN), 'w') as f:
|
||||
json.dump(metadata, f, indent=4, sort_keys=True)
|
||||
|
@ -40,6 +38,30 @@ def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None):
|
|||
shutil.rmtree(tmpdir)
|
||||
|
||||
|
||||
def _run_zic(zonedir, filepaths):
|
||||
"""Calls the ``zic`` compiler in a compatible way to get a "fat" binary.
|
||||
|
||||
Recent versions of ``zic`` default to ``-b slim``, while older versions
|
||||
don't even have the ``-b`` option (but default to "fat" binaries). The
|
||||
current version of dateutil does not support Version 2+ TZif files, which
|
||||
causes problems when used in conjunction with "slim" binaries, so this
|
||||
function is used to ensure that we always get a "fat" binary.
|
||||
"""
|
||||
|
||||
try:
|
||||
help_text = check_output(["zic", "--help"])
|
||||
except OSError as e:
|
||||
_print_on_nosuchfile(e)
|
||||
raise
|
||||
|
||||
if b"-b " in help_text:
|
||||
bloat_args = ["-b", "fat"]
|
||||
else:
|
||||
bloat_args = []
|
||||
|
||||
check_call(["zic"] + bloat_args + ["-d", zonedir] + filepaths)
|
||||
|
||||
|
||||
def _print_on_nosuchfile(e):
|
||||
"""Print helpful troubleshooting message
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue