Update dateutil library to 2.4.2 (083f666).

This commit is contained in:
JackDandy 2015-09-12 11:51:54 +01:00
parent 6267156831
commit e6317dada5
7 changed files with 260 additions and 172 deletions

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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):

View file

@ -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(

View file

@ -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)

View 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)