mirror of
https://github.com/SickGear/SickGear.git
synced 2025-01-05 17:43:37 +00:00
Update dateutil library to 2.4.2 (083f666).
This commit is contained in:
parent
6267156831
commit
e6317dada5
7 changed files with 260 additions and 172 deletions
|
@ -15,6 +15,7 @@
|
|||
* Remove legacy anime split home option from anime settings tab (new option located in general/interface tab)
|
||||
* Remove "Manage Torrents"
|
||||
* Update Beautiful Soup 4.3.2 to 4.4.0 (r390)
|
||||
* Update dateutil library to 2.4.2 (083f666)
|
||||
* Update Hachoir library 1.3.3 to 1.3.4 (r1383)
|
||||
* Change configure quiet option in Hachoir to suppress warnings (add ref:hacks.txt)
|
||||
* Add parse media content to determine quality before making final assumptions during re-scan, update, pp
|
||||
|
|
|
@ -4,28 +4,29 @@ This module offers a generic date/time string parser which is able to parse
|
|||
most known formats to represent a date and/or time.
|
||||
|
||||
This module attempts to be forgiving with regards to unlikely input formats,
|
||||
returning a datetime object even for dates which are ambiguous. If an element of
|
||||
a date/time stamp is omitted, the following rules are applied:
|
||||
returning a datetime object even for dates which are ambiguous. If an element
|
||||
of a date/time stamp is omitted, the following rules are applied:
|
||||
- If AM or PM is left unspecified, a 24-hour clock is assumed, however, an hour
|
||||
on a 12-hour clock (`0 <= hour <= 12`) *must* be specified if AM or PM is
|
||||
on a 12-hour clock (``0 <= hour <= 12``) *must* be specified if AM or PM is
|
||||
specified.
|
||||
- If a time zone is omitted, it is assumed to be UTC.
|
||||
- If a time zone is omitted, a timezone-naive datetime is returned.
|
||||
|
||||
If any other elements are missing, they are taken from the `datetime.datetime`
|
||||
object passed to the parameter `default`. If this results in a day number
|
||||
exceeding the valid number of days per month, one can fall back to the last
|
||||
day of the month by setting `fallback_on_invalid_day` parameter to `True`.
|
||||
If any other elements are missing, they are taken from the
|
||||
:class:`datetime.datetime` object passed to the parameter ``default``. If this
|
||||
results in a day number exceeding the valid number of days per month, one can
|
||||
fall back to the last day of the month by setting ``fallback_on_invalid_day``
|
||||
parameter to ``True``.
|
||||
|
||||
Also provided is the `smart_defaults` option, which attempts to fill in the
|
||||
Also provided is the ``smart_defaults`` option, which attempts to fill in the
|
||||
missing elements from context. If specified, the logic is:
|
||||
- If the omitted element is smaller than the largest specified element, select
|
||||
the *earliest* time matching the specified conditions; so `"June 2010"` is
|
||||
interpreted as `June 1, 2010 0:00:00`) and the (somewhat strange)
|
||||
`"Feb 1997 3:15 PM"` is interpreted as `February 1, 1997 15:15:00`.
|
||||
the *earliest* time matching the specified conditions; so ``"June 2010"`` is
|
||||
interpreted as ``June 1, 2010 0:00:00``) and the (somewhat strange)
|
||||
``"Feb 1997 3:15 PM"`` is interpreted as ``February 1, 1997 15:15:00``.
|
||||
- If the element is larger than the largest specified element, select the
|
||||
*most recent* time matching the specified conditions (e.g parsing `"May"`
|
||||
*most recent* time matching the specified conditions (e.g parsing ``"May"``
|
||||
in June 2015 returns the date May 1st, 2015, whereas parsing it in April 2015
|
||||
returns May 1st 2014). If using the `date_in_future` flag, this logic is
|
||||
returns May 1st 2014). If using the ``date_in_future`` flag, this logic is
|
||||
inverted, and instead the *next* time matching the specified conditions is
|
||||
returned.
|
||||
|
||||
|
@ -46,6 +47,7 @@ import datetime
|
|||
import string
|
||||
import time
|
||||
import collections
|
||||
import re
|
||||
from io import StringIO
|
||||
from calendar import monthrange, isleap
|
||||
|
||||
|
@ -58,6 +60,9 @@ __all__ = ["parse", "parserinfo"]
|
|||
|
||||
|
||||
class _timelex(object):
|
||||
# Fractional seconds are sometimes split by a comma
|
||||
_split_decimal = re.compile("([\.,])")
|
||||
|
||||
def __init__(self, instream):
|
||||
if isinstance(instream, binary_type):
|
||||
instream = instream.decode()
|
||||
|
@ -80,8 +85,8 @@ class _timelex(object):
|
|||
"""
|
||||
This function breaks the time string into lexical units (tokens), which
|
||||
can be parsed by the parser. Lexical units are demarcated by changes in
|
||||
the character set, so any continuous string of letters is considered one
|
||||
unit, any continuous string of numbers is considered one unit.
|
||||
the character set, so any continuous string of letters is considered
|
||||
one unit, any continuous string of numbers is considered one unit.
|
||||
|
||||
The main complication arises from the fact that dots ('.') can be used
|
||||
both as separators (e.g. "Sep.20.2009") or decimal points (e.g.
|
||||
|
@ -101,9 +106,9 @@ class _timelex(object):
|
|||
whitespace = self.whitespace
|
||||
|
||||
while not self.eof:
|
||||
# We only realize that we've reached the end of a token when we find
|
||||
# a character that's not part of the current token - since that
|
||||
# character may be part of the next token, it's stored in the
|
||||
# We only realize that we've reached the end of a token when we
|
||||
# find a character that's not part of the current token - since
|
||||
# that character may be part of the next token, it's stored in the
|
||||
# charstack.
|
||||
if self.charstack:
|
||||
nextchar = self.charstack.pop(0)
|
||||
|
@ -145,7 +150,7 @@ class _timelex(object):
|
|||
# numbers until we find something that doesn't fit.
|
||||
if nextchar in numchars:
|
||||
token += nextchar
|
||||
elif nextchar == '.':
|
||||
elif nextchar == '.' or (nextchar == ',' and len(token) >= 2):
|
||||
token += nextchar
|
||||
state = '0.'
|
||||
else:
|
||||
|
@ -176,14 +181,16 @@ class _timelex(object):
|
|||
break # emit token
|
||||
|
||||
if (state in ('a.', '0.') and (seenletters or token.count('.') > 1 or
|
||||
token[-1] == '.')):
|
||||
l = token.split('.')
|
||||
token[-1] in '.,')):
|
||||
l = self._split_decimal.split(token)
|
||||
token = l[0]
|
||||
for tok in l[1:]:
|
||||
self.tokenstack.append('.')
|
||||
if tok:
|
||||
self.tokenstack.append(tok)
|
||||
|
||||
if state == '0.' and token.count('.') == 0:
|
||||
token = token.replace(',', '.')
|
||||
|
||||
return token
|
||||
|
||||
def __iter__(self):
|
||||
|
@ -224,20 +231,20 @@ class _resultbase(object):
|
|||
|
||||
class parserinfo(object):
|
||||
"""
|
||||
Class which handles what inputs are accepted. Subclass this to customize the
|
||||
language and acceptable values for each parameter.
|
||||
Class which handles what inputs are accepted. Subclass this to customize
|
||||
the language and acceptable values for each parameter.
|
||||
|
||||
:param dayfirst:
|
||||
Whether to interpret the first value in an ambiguous 3-integer date
|
||||
(e.g. 01/05/09) as the day (`True`) or month (`False`). If
|
||||
`yearfirst` is set to `True`, this distinguishes between YDM and
|
||||
YMD. Default is `False`.
|
||||
(e.g. 01/05/09) as the day (``True``) or month (``False``). If
|
||||
``yearfirst`` is set to ``True``, this distinguishes between YDM
|
||||
and YMD. Default is ``False``.
|
||||
|
||||
:param yearfirst:
|
||||
Whether to interpret the first value in an ambiguous 3-integer date
|
||||
(e.g. 01/05/09) as the year. If `True`, the first number is taken to
|
||||
be the year, otherwise the last number is taken to be the year.
|
||||
Default is `False`.
|
||||
(e.g. 01/05/09) as the year. If ``True``, the first number is taken
|
||||
to be the year, otherwise the last number is taken to be the year.
|
||||
Default is ``False``.
|
||||
"""
|
||||
|
||||
# m from a.m/p.m, t from ISO T separator
|
||||
|
@ -287,7 +294,7 @@ class parserinfo(object):
|
|||
self.smart_defaults = smart_defaults
|
||||
|
||||
self._year = time.localtime().tm_year
|
||||
self._century = self._year // 100*100
|
||||
self._century = self._year // 100 * 100
|
||||
|
||||
def _convert(self, lst):
|
||||
dct = {}
|
||||
|
@ -313,7 +320,7 @@ class parserinfo(object):
|
|||
def month(self, name):
|
||||
if len(name) >= 3:
|
||||
try:
|
||||
return self._months[name.lower()]+1
|
||||
return self._months[name.lower()] + 1
|
||||
except KeyError:
|
||||
pass
|
||||
return None
|
||||
|
@ -345,7 +352,7 @@ class parserinfo(object):
|
|||
def convertyear(self, year):
|
||||
if year < 100:
|
||||
year += self._century
|
||||
if abs(year-self._year) >= 50:
|
||||
if abs(year - self._year) >= 50:
|
||||
if year < self._year:
|
||||
year += 100
|
||||
else:
|
||||
|
@ -373,65 +380,87 @@ class parser(object):
|
|||
smart_defaults=None, date_in_future=False,
|
||||
fallback_on_invalid_day=None, **kwargs):
|
||||
"""
|
||||
Parse the date/time string into a datetime object.
|
||||
Parse the date/time string into a :class:`datetime.datetime` object.
|
||||
|
||||
:param timestr:
|
||||
Any date/time string using the supported formats.
|
||||
|
||||
:param default:
|
||||
The default datetime object, if this is a datetime object and not
|
||||
`None`, elements specified in `timestr` replace elements in the
|
||||
default object, unless `smart_defaults` is set to `True`, in which
|
||||
case to the extent necessary, timestamps are calculated relative to
|
||||
this date.
|
||||
``None``, elements specified in ``timestr`` replace elements in the
|
||||
default object, unless ``smart_defaults`` is set to ``True``, in
|
||||
which case to the extent necessary, timestamps are calculated
|
||||
relative to this date.
|
||||
|
||||
:param smart_defaults:
|
||||
If using smart defaults, the `default` parameter is treated as the
|
||||
effective parsing date/time, and the context of the datetime string
|
||||
is determined relative to `default`. If `None`, this parameter is
|
||||
inherited from the :class:`parserinfo` object.
|
||||
If using smart defaults, the ``default`` parameter is treated as
|
||||
the effective parsing date/time, and the context of the datetime
|
||||
string is determined relative to ``default``. If ``None``, this
|
||||
parameter is inherited from the :class:`parserinfo` object.
|
||||
|
||||
:param date_in_future:
|
||||
If `smart_defaults` is `True`, the parser assumes by default that
|
||||
the timestamp refers to a date in the past, and will return the
|
||||
beginning of the most recent timespan which matches the time string
|
||||
(e.g. if `default` is March 3rd, 2013, "Feb" parses to
|
||||
If ``smart_defaults`` is ``True``, the parser assumes by default
|
||||
that the timestamp refers to a date in the past, and will return
|
||||
the beginning of the most recent timespan which matches the time
|
||||
string (e.g. if ``default`` is March 3rd, 2013, "Feb" parses to
|
||||
"Feb 1, 2013" and "May 3" parses to May 3rd, 2012). Setting this
|
||||
parameter to `True` inverts this assumption, and returns the
|
||||
parameter to ``True`` inverts this assumption, and returns the
|
||||
beginning of the *next* matching timespan.
|
||||
|
||||
:param fallback_on_invalid_day:
|
||||
If specified `True`, an otherwise invalid date such as "Feb 30" or
|
||||
"June 32" falls back to the last day of the month. If specified as
|
||||
"False", the parser is strict about parsing otherwise valid dates
|
||||
that would turn up as invalid because of the fallback rules (e.g.
|
||||
"Feb 2010" run with a default of January 30, 2010 and `smartparser`
|
||||
set to `False` would would throw an error, rather than falling
|
||||
back to the end of February). If `None` or unspecified, the date
|
||||
falls back to the most recent valid date only if the invalid date
|
||||
is created as a result of an unspecified day in the time string.
|
||||
If specified ``True``, an otherwise invalid date such as "Feb 30"
|
||||
or "June 32" falls back to the last day of the month. If specified
|
||||
as "False", the parser is strict about parsing otherwise valid
|
||||
dates that would turn up as invalid because of the fallback rules
|
||||
(e.g. "Feb 2010" run with a default of January 30, 2010 and
|
||||
``smartparser`` set to ``False`` would would throw an error, rather
|
||||
than falling back to the end of February). If ``None`` or
|
||||
unspecified, the date falls back to the most recent valid date only
|
||||
if the invalid date is created as a result of an unspecified day in
|
||||
the time string.
|
||||
|
||||
:param ignoretz:
|
||||
Whether or not to ignore the time zone.
|
||||
If set ``True``, time zones in parsed strings are ignored and a
|
||||
naive :class:`datetime.datetime` object is returned.
|
||||
|
||||
:param tzinfos:
|
||||
A time zone, to be applied to the date, if `ignoretz` is `True`.
|
||||
This can be either a subclass of `tzinfo`, a time zone string or an
|
||||
integer offset.
|
||||
Additional time zone names / aliases which may be present in the
|
||||
string. This argument maps time zone names (and optionally offsets
|
||||
from those time zones) to time zones. This parameter can be a
|
||||
dictionary with timezone aliases mapping time zone names to time
|
||||
zones or a function taking two parameters (``tzname`` and
|
||||
``tzoffset``) and returning a time zone.
|
||||
|
||||
The timezones to which the names are mapped can be an integer
|
||||
offset from UTC in minutes or a :class:`tzinfo` object.
|
||||
|
||||
.. doctest::
|
||||
:options: +NORMALIZE_WHITESPACE
|
||||
|
||||
>>> from dateutil.parser import parse
|
||||
>>> from dateutil.tz import gettz
|
||||
>>> tzinfos = {"BRST": -10800, "CST": gettz("America/Chicago")}
|
||||
>>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos)
|
||||
datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -10800))
|
||||
>>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos)
|
||||
datetime.datetime(2012, 1, 19, 17, 21,
|
||||
tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago'))
|
||||
|
||||
This parameter is ignored if ``ignoretz`` is set.
|
||||
|
||||
:param **kwargs:
|
||||
Keyword arguments as passed to `_parse()`.
|
||||
Keyword arguments as passed to ``_parse()``.
|
||||
|
||||
:return:
|
||||
Returns a `datetime.datetime` object or, if the `fuzzy_with_tokens`
|
||||
option is `True`, returns a tuple, the first element being a
|
||||
`datetime.datetime` object, the second a tuple containing the
|
||||
fuzzy tokens.
|
||||
Returns a :class:`datetime.datetime` object or, if the
|
||||
``fuzzy_with_tokens`` option is ``True``, returns a tuple, the
|
||||
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
|
||||
`tzinfo` is not in a valid format, or if an invalid date would
|
||||
be created.
|
||||
: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
|
||||
|
@ -444,14 +473,11 @@ class parser(object):
|
|||
if default is None:
|
||||
effective_dt = datetime.datetime.now()
|
||||
default = datetime.datetime.now().replace(hour=0, minute=0,
|
||||
second=0, microsecond=0)
|
||||
second=0, microsecond=0)
|
||||
else:
|
||||
effective_dt = default
|
||||
|
||||
if kwargs.get('fuzzy_with_tokens', False):
|
||||
res, skipped_tokens = self._parse(timestr, **kwargs)
|
||||
else:
|
||||
res = self._parse(timestr, **kwargs)
|
||||
res, skipped_tokens = self._parse(timestr, **kwargs)
|
||||
|
||||
if res is None:
|
||||
raise ValueError("Unknown string format")
|
||||
|
@ -464,7 +490,7 @@ class parser(object):
|
|||
repl[attr] = value
|
||||
|
||||
# Choose the correct fallback position if requested by the
|
||||
# `smart_defaults` parameter.
|
||||
# ``smart_defaults`` parameter.
|
||||
if smart_defaults:
|
||||
# Determine if it refers to this year, last year or next year
|
||||
if res.year is None:
|
||||
|
@ -472,7 +498,7 @@ class parser(object):
|
|||
# Explicitly deal with leap year problems
|
||||
if res.month == 2 and (res.day is not None and
|
||||
res.day == 29):
|
||||
|
||||
|
||||
ly_offset = 4 if date_in_future else -4
|
||||
next_year = 4 * (default.year // 4)
|
||||
|
||||
|
@ -583,36 +609,42 @@ class parser(object):
|
|||
fuzzy_with_tokens=False):
|
||||
"""
|
||||
Private method which performs the heavy lifting of parsing, called from
|
||||
`parse()`, which passes on its `kwargs` to this function.
|
||||
``parse()``, which passes on its ``kwargs`` to this function.
|
||||
|
||||
:param timestr:
|
||||
The string to parse.
|
||||
|
||||
:param dayfirst:
|
||||
Whether to interpret the first value in an ambiguous 3-integer date
|
||||
(e.g. 01/05/09) as the day (`True`) or month (`False`). If
|
||||
`yearfirst` is set to `True`, this distinguishes between YDM and
|
||||
YMD. If set to `None`, this value is retrieved from the current
|
||||
`parserinfo` object (which itself defaults to `False`).
|
||||
(e.g. 01/05/09) as the day (``True``) or month (``False``). If
|
||||
``yearfirst`` is set to ``True``, this distinguishes between YDM
|
||||
and YMD. If set to ``None``, this value is retrieved from the
|
||||
current :class:`parserinfo` object (which itself defaults to
|
||||
``False``).
|
||||
|
||||
:param yearfirst:
|
||||
Whether to interpret the first value in an ambiguous 3-integer date
|
||||
(e.g. 01/05/09) as the year. If `True`, the first number is taken to
|
||||
be the year, otherwise the last number is taken to be the year. If
|
||||
this is set to `None`, the value is retrieved from the current
|
||||
`parserinfo` object (which itself defaults to `False`).
|
||||
(e.g. 01/05/09) as the year. If ``True``, the first number is taken
|
||||
to be the year, otherwise the last number is taken to be the year.
|
||||
If this is set to ``None``, the value is retrieved from the current
|
||||
:class:`parserinfo` object (which itself defaults to ``False``).
|
||||
|
||||
:param fuzzy:
|
||||
Whether to allow fuzzy parsing, allowing for string like "Today is
|
||||
January 1, 2047 at 8:21:00AM".
|
||||
|
||||
:param fuzzy_with_tokens:
|
||||
If `True`, `fuzzy` is automatically set to True, and the parser will
|
||||
return a tuple where the first element is the parsed
|
||||
`datetime.datetime` datetimestamp and the second element is a tuple
|
||||
containing the portions of the string which were ignored, e.g.
|
||||
"Today is January 1, 2047 at 8:21:00AM" should return
|
||||
`(datetime.datetime(2011, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))`
|
||||
If ``True``, ``fuzzy`` is automatically set to True, and the parser
|
||||
will return a tuple where the first element is the parsed
|
||||
:class:`datetime.datetime` datetimestamp and the second element is
|
||||
a tuple containing the portions of the string which were ignored:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> from dateutil.parser import parse
|
||||
>>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True)
|
||||
(datetime.datetime(2011, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))
|
||||
|
||||
"""
|
||||
if fuzzy_with_tokens:
|
||||
fuzzy = True
|
||||
|
@ -796,7 +828,7 @@ class parser(object):
|
|||
assert mstridx == -1
|
||||
mstridx = len(ymd)-1
|
||||
else:
|
||||
return None
|
||||
return None, None
|
||||
|
||||
i += 1
|
||||
|
||||
|
@ -840,7 +872,7 @@ class parser(object):
|
|||
i += 1
|
||||
|
||||
elif not fuzzy:
|
||||
return None
|
||||
return None, None
|
||||
else:
|
||||
i += 1
|
||||
continue
|
||||
|
@ -969,7 +1001,7 @@ class parser(object):
|
|||
# -[0]3
|
||||
res.tzoffset = int(l[i][:2])*3600
|
||||
else:
|
||||
return None
|
||||
return None, None
|
||||
i += 1
|
||||
|
||||
res.tzoffset *= signal
|
||||
|
@ -987,7 +1019,7 @@ class parser(object):
|
|||
|
||||
# Check jumps
|
||||
if not (info.jump(l[i]) or fuzzy):
|
||||
return None
|
||||
return None, None
|
||||
|
||||
if last_skipped_token_i == i - 1:
|
||||
# recombine the tokens
|
||||
|
@ -1002,7 +1034,7 @@ class parser(object):
|
|||
len_ymd = len(ymd)
|
||||
if len_ymd > 3:
|
||||
# More than three members!?
|
||||
return None
|
||||
return None, None
|
||||
elif len_ymd == 1 or (mstridx != -1 and len_ymd == 2):
|
||||
# One member, or two members with a month string
|
||||
if mstridx != -1:
|
||||
|
@ -1066,72 +1098,113 @@ class parser(object):
|
|||
res.month, res.day, res.year = ymd
|
||||
|
||||
except (IndexError, ValueError, AssertionError):
|
||||
return None
|
||||
return None, None
|
||||
|
||||
if not info.validate(res):
|
||||
return None
|
||||
return None, None
|
||||
|
||||
if fuzzy_with_tokens:
|
||||
return res, tuple(skipped_tokens)
|
||||
else:
|
||||
return res
|
||||
return res, None
|
||||
|
||||
DEFAULTPARSER = parser()
|
||||
|
||||
|
||||
def parse(timestr, parserinfo=None, **kwargs):
|
||||
"""
|
||||
Parse a string in one of the supported formats, using the `parserinfo`
|
||||
parameters.
|
||||
|
||||
Parse a string in one of the supported formats, using the
|
||||
``parserinfo`` parameters.
|
||||
|
||||
:param timestr:
|
||||
A string containing a date/time stamp.
|
||||
|
||||
:param parserinfo:
|
||||
A :class:`parserinfo` object containing parameters for the parser.
|
||||
If `None`, the default arguments to the `parserinfo` constructor are
|
||||
used.
|
||||
If ``None``, the default arguments to the :class:`parserinfo`
|
||||
constructor are used.
|
||||
|
||||
The `**kwargs` parameter takes the following keyword arguments:
|
||||
The ``**kwargs`` parameter takes the following keyword arguments:
|
||||
|
||||
:param default:
|
||||
The default datetime object, if this is a datetime object and not
|
||||
`None`, elements specified in `timestr` replace elements in the
|
||||
``None``, elements specified in ``timestr`` replace elements in the
|
||||
default object.
|
||||
|
||||
:param ignoretz:
|
||||
Whether or not to ignore the time zone (boolean).
|
||||
If set ``True``, time zones in parsed strings are ignored and a naive
|
||||
:class:`datetime` object is returned.
|
||||
|
||||
:param tzinfos:
|
||||
A time zone, to be applied to the date, if `ignoretz` is `True`.
|
||||
This can be either a subclass of `tzinfo`, a time zone string or an
|
||||
integer offset.
|
||||
Additional time zone names / aliases which may be present in the
|
||||
string. This argument maps time zone names (and optionally offsets
|
||||
from those time zones) to time zones. This parameter can be a
|
||||
dictionary with timezone aliases mapping time zone names to time
|
||||
zones or a function taking two parameters (``tzname`` and
|
||||
``tzoffset``) and returning a time zone.
|
||||
|
||||
The timezones to which the names are mapped can be an integer
|
||||
offset from UTC in minutes or a :class:`tzinfo` object.
|
||||
|
||||
.. doctest::
|
||||
:options: +NORMALIZE_WHITESPACE
|
||||
|
||||
>>> from dateutil.parser import parse
|
||||
>>> from dateutil.tz import gettz
|
||||
>>> tzinfos = {"BRST": -10800, "CST": gettz("America/Chicago")}
|
||||
>>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos)
|
||||
datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -10800))
|
||||
>>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos)
|
||||
datetime.datetime(2012, 1, 19, 17, 21,
|
||||
tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago'))
|
||||
|
||||
This parameter is ignored if ``ignoretz`` is set.
|
||||
|
||||
:param dayfirst:
|
||||
Whether to interpret the first value in an ambiguous 3-integer date
|
||||
(e.g. 01/05/09) as the day (`True`) or month (`False`). If
|
||||
`yearfirst` is set to `True`, this distinguishes between YDM and
|
||||
YMD. If set to `None`, this value is retrieved from the current
|
||||
:class:`parserinfo` object (which itself defaults to `False`).
|
||||
(e.g. 01/05/09) as the day (``True``) or month (``False``). If
|
||||
``yearfirst`` is set to ``True``, this distinguishes between YDM and
|
||||
YMD. If set to ``None``, this value is retrieved from the current
|
||||
:class:`parserinfo` object (which itself defaults to ``False``).
|
||||
|
||||
:param yearfirst:
|
||||
Whether to interpret the first value in an ambiguous 3-integer date
|
||||
(e.g. 01/05/09) as the year. If `True`, the first number is taken to
|
||||
(e.g. 01/05/09) as the year. If ``True``, the first number is taken to
|
||||
be the year, otherwise the last number is taken to be the year. If
|
||||
this is set to `None`, the value is retrieved from the current
|
||||
:class:`parserinfo` object (which itself defaults to `False`).
|
||||
this is set to ``None``, the value is retrieved from the current
|
||||
:class:`parserinfo` object (which itself defaults to ``False``).
|
||||
|
||||
:param fuzzy:
|
||||
Whether to allow fuzzy parsing, allowing for string like "Today is
|
||||
January 1, 2047 at 8:21:00AM".
|
||||
|
||||
:param fuzzy_with_tokens:
|
||||
If `True`, `fuzzy` is automatically set to True, and the parser will
|
||||
return a tuple where the first element is the parsed
|
||||
`datetime.datetime` datetimestamp and the second element is a tuple
|
||||
containing the portions of the string which were ignored, e.g.
|
||||
"Today is January 1, 2047 at 8:21:00AM" should return
|
||||
`(datetime.datetime(2011, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))`
|
||||
If ``True``, ``fuzzy`` is automatically set to True, and the parser
|
||||
will return a tuple where the first element is the parsed
|
||||
:class:`datetime.datetime` datetimestamp and the second element is
|
||||
a tuple containing the portions of the string which were ignored:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> from dateutil.parser import parse
|
||||
>>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True)
|
||||
(datetime.datetime(2011, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))
|
||||
|
||||
:return:
|
||||
Returns a :class:`datetime.datetime` object or, if the
|
||||
``fuzzy_with_tokens`` option is ``True``, returns a tuple, the
|
||||
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 OverFlowError:
|
||||
Raised if the parsed date exceeds the largest valid C integer on
|
||||
your system.
|
||||
"""
|
||||
if parserinfo:
|
||||
return parser(parserinfo).parse(timestr, **kwargs)
|
||||
|
|
|
@ -423,6 +423,7 @@ Here is the behavior of operations with relativedelta:
|
|||
self.hours == other.hours and
|
||||
self.minutes == other.minutes and
|
||||
self.seconds == other.seconds and
|
||||
self.microseconds == other.microseconds and
|
||||
self.leapdays == other.leapdays and
|
||||
self.year == other.year and
|
||||
self.month == other.month and
|
||||
|
|
|
@ -104,12 +104,12 @@ class tzoffset(datetime.tzinfo):
|
|||
|
||||
|
||||
class tzlocal(datetime.tzinfo):
|
||||
|
||||
_std_offset = datetime.timedelta(seconds=-time.timezone)
|
||||
if time.daylight:
|
||||
_dst_offset = datetime.timedelta(seconds=-time.altzone)
|
||||
else:
|
||||
_dst_offset = _std_offset
|
||||
def __init__(self):
|
||||
self._std_offset = datetime.timedelta(seconds=-time.timezone)
|
||||
if time.daylight:
|
||||
self._dst_offset = datetime.timedelta(seconds=-time.altzone)
|
||||
else:
|
||||
self._dst_offset = self._std_offset
|
||||
|
||||
def utcoffset(self, dt):
|
||||
if self._isdst(dt):
|
||||
|
|
|
@ -4,6 +4,8 @@ import struct
|
|||
|
||||
from six.moves import winreg
|
||||
|
||||
from .tz import tzname_in_python2
|
||||
|
||||
__all__ = ["tzwin", "tzwinlocal"]
|
||||
|
||||
ONEWEEK = datetime.timedelta(7)
|
||||
|
@ -42,6 +44,7 @@ class tzwinbase(datetime.tzinfo):
|
|||
else:
|
||||
return datetime.timedelta(0)
|
||||
|
||||
@tzname_in_python2
|
||||
def tzname(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self._dstname
|
||||
|
@ -89,8 +92,8 @@ class tzwin(tzwinbase):
|
|||
"%s\%s" % (TZKEYNAME, name)) as tzkey:
|
||||
keydict = valuestodict(tzkey)
|
||||
|
||||
self._stdname = keydict["Std"].encode("iso-8859-1")
|
||||
self._dstname = keydict["Dlt"].encode("iso-8859-1")
|
||||
self._stdname = keydict["Std"]
|
||||
self._dstname = keydict["Dlt"]
|
||||
|
||||
self._display = keydict["Display"]
|
||||
|
||||
|
@ -129,8 +132,8 @@ class tzwinlocal(tzwinbase):
|
|||
with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey:
|
||||
keydict = valuestodict(tzlocalkey)
|
||||
|
||||
self._stdname = keydict["StandardName"].encode("iso-8859-1")
|
||||
self._dstname = keydict["DaylightName"].encode("iso-8859-1")
|
||||
self._stdname = keydict["StandardName"]
|
||||
self._dstname = keydict["DaylightName"]
|
||||
|
||||
try:
|
||||
with winreg.OpenKey(
|
||||
|
|
|
@ -16,14 +16,14 @@ from dateutil.tz import tzfile
|
|||
|
||||
__all__ = ["gettz", "gettz_db_metadata", "rebuild"]
|
||||
|
||||
_ZONEFILENAME = "dateutil-zoneinfo.tar.gz"
|
||||
_METADATA_FN = 'METADATA'
|
||||
ZONEFILENAME = "dateutil-zoneinfo.tar.gz"
|
||||
METADATA_FN = 'METADATA'
|
||||
|
||||
# python2.6 compatability. Note that TarFile.__exit__ != TarFile.close, but
|
||||
# it's close enough for python2.6
|
||||
_tar_open = TarFile.open
|
||||
tar_open = TarFile.open
|
||||
if not hasattr(TarFile, '__exit__'):
|
||||
def _tar_open(*args, **kwargs):
|
||||
def tar_open(*args, **kwargs):
|
||||
return closing(TarFile.open(*args, **kwargs))
|
||||
|
||||
|
||||
|
@ -34,7 +34,7 @@ class tzfile(tzfile):
|
|||
|
||||
def getzoneinfofile_stream():
|
||||
try:
|
||||
return BytesIO(get_data(__name__, _ZONEFILENAME))
|
||||
return BytesIO(get_data(__name__, ZONEFILENAME))
|
||||
except IOError as e: # TODO switch to FileNotFoundError?
|
||||
warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror))
|
||||
return None
|
||||
|
@ -43,7 +43,7 @@ def getzoneinfofile_stream():
|
|||
class ZoneInfoFile(object):
|
||||
def __init__(self, zonefile_stream=None):
|
||||
if zonefile_stream is not None:
|
||||
with _tar_open(fileobj=zonefile_stream, mode='r') as tf:
|
||||
with tar_open(fileobj=zonefile_stream, mode='r') as tf:
|
||||
# dict comprehension does not work on python2.6
|
||||
# TODO: get back to the nicer syntax when we ditch python2.6
|
||||
# self.zones = {zf.name: tzfile(tf.extractfile(zf),
|
||||
|
@ -52,7 +52,7 @@ class ZoneInfoFile(object):
|
|||
self.zones = dict((zf.name, tzfile(tf.extractfile(zf),
|
||||
filename=zf.name))
|
||||
for zf in tf.getmembers()
|
||||
if zf.isfile() and zf.name != _METADATA_FN)
|
||||
if zf.isfile() and zf.name != METADATA_FN)
|
||||
# deal with links: They'll point to their parent object. Less
|
||||
# waste of memory
|
||||
# links = {zl.name: self.zones[zl.linkname]
|
||||
|
@ -62,7 +62,7 @@ class ZoneInfoFile(object):
|
|||
zl.islnk() or zl.issym())
|
||||
self.zones.update(links)
|
||||
try:
|
||||
metadata_json = tf.extractfile(tf.getmember(_METADATA_FN))
|
||||
metadata_json = tf.extractfile(tf.getmember(METADATA_FN))
|
||||
metadata_str = metadata_json.read().decode('UTF-8')
|
||||
self.metadata = json.loads(metadata_str)
|
||||
except KeyError:
|
||||
|
@ -100,36 +100,3 @@ def gettz_db_metadata():
|
|||
return _CLASS_ZONE_INSTANCE[0].metadata
|
||||
|
||||
|
||||
def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None):
|
||||
"""Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar*
|
||||
|
||||
filename is the timezone tarball from ftp.iana.org/tz.
|
||||
|
||||
"""
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
zonedir = os.path.join(tmpdir, "zoneinfo")
|
||||
moduledir = os.path.dirname(__file__)
|
||||
try:
|
||||
with _tar_open(filename) as tf:
|
||||
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:
|
||||
if e.errno == 2:
|
||||
logging.error(
|
||||
"Could not find zic. Perhaps you need to install "
|
||||
"libc-bin or some other package that provides it, "
|
||||
"or it's not in your PATH?")
|
||||
raise
|
||||
# write metadata file
|
||||
with open(os.path.join(zonedir, _METADATA_FN), 'w') as f:
|
||||
json.dump(metadata, f, indent=4, sort_keys=True)
|
||||
target = os.path.join(moduledir, _ZONEFILENAME)
|
||||
with _tar_open(target, "w:%s" % format) as tf:
|
||||
for entry in os.listdir(zonedir):
|
||||
entrypath = os.path.join(zonedir, entry)
|
||||
tf.add(entrypath, entry)
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
|
|
43
lib/dateutil/zoneinfo/rebuild.py
Normal file
43
lib/dateutil/zoneinfo/rebuild.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
import shutil
|
||||
import json
|
||||
from subprocess import check_call
|
||||
|
||||
from dateutil.zoneinfo import tar_open, METADATA_FN, ZONEFILENAME
|
||||
|
||||
|
||||
def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None):
|
||||
"""Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar*
|
||||
|
||||
filename is the timezone tarball from ftp.iana.org/tz.
|
||||
|
||||
"""
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
zonedir = os.path.join(tmpdir, "zoneinfo")
|
||||
moduledir = os.path.dirname(__file__)
|
||||
try:
|
||||
with tar_open(filename) as tf:
|
||||
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:
|
||||
if e.errno == 2:
|
||||
logging.error(
|
||||
"Could not find zic. Perhaps you need to install "
|
||||
"libc-bin or some other package that provides it, "
|
||||
"or it's not in your PATH?")
|
||||
raise
|
||||
# write metadata file
|
||||
with open(os.path.join(zonedir, METADATA_FN), 'w') as f:
|
||||
json.dump(metadata, f, indent=4, sort_keys=True)
|
||||
target = os.path.join(moduledir, ZONEFILENAME)
|
||||
with tar_open(target, "w:%s" % format) as tf:
|
||||
for entry in os.listdir(zonedir):
|
||||
entrypath = os.path.join(zonedir, entry)
|
||||
tf.add(entrypath, entry)
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
Loading…
Reference in a new issue