mirror of
https://github.com/SickGear/SickGear.git
synced 2025-01-07 10:33:38 +00:00
Merge pull request #413 from JackDandy/feature/UpdateDateutil
Update dateutil library 2.2 to 2.4.2 (a6b8925).
This commit is contained in:
commit
6f77328bc1
10 changed files with 1531 additions and 651 deletions
|
@ -42,6 +42,7 @@
|
|||
* Update Six compatibility library 1.5.2 to 1.9.0 (8a545f4)
|
||||
* Update SimpleJSON library 2.0.9 to 3.7.3 (0bcdf20)
|
||||
* Update xmltodict library 0.9.0 to 0.9.2 (579a005)
|
||||
* Update dateutil library 2.2 to 2.4.2 (a6b8925)
|
||||
|
||||
[develop changelog]
|
||||
* Update Requests library 2.7.0 (ab1f493) to 2.7.0 (8b5e457)
|
||||
|
|
|
@ -1,10 +1,2 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2003-2010 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard Python
|
||||
datetime module.
|
||||
"""
|
||||
__author__ = "Tomi Pieviläinen <tomi.pievilainen@iki.fi>"
|
||||
__license__ = "Simplified BSD"
|
||||
__version__ = "2.2"
|
||||
__version__ = "2.4.2"
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard Python
|
||||
datetime module.
|
||||
This module offers a generic easter computing method for any given year, using
|
||||
Western, Orthodox or Julian algorithms.
|
||||
"""
|
||||
__license__ = "Simplified BSD"
|
||||
|
||||
import datetime
|
||||
|
||||
|
@ -14,6 +12,7 @@ EASTER_JULIAN = 1
|
|||
EASTER_ORTHODOX = 2
|
||||
EASTER_WESTERN = 3
|
||||
|
||||
|
||||
def easter(year, method=EASTER_WESTERN):
|
||||
"""
|
||||
This method was ported from the work done by GM Arts,
|
||||
|
@ -68,24 +67,23 @@ def easter(year, method=EASTER_WESTERN):
|
|||
e = 0
|
||||
if method < 3:
|
||||
# Old method
|
||||
i = (19*g+15)%30
|
||||
j = (y+y//4+i)%7
|
||||
i = (19*g + 15) % 30
|
||||
j = (y + y//4 + i) % 7
|
||||
if method == 2:
|
||||
# Extra dates to convert Julian to Gregorian date
|
||||
e = 10
|
||||
if y > 1600:
|
||||
e = e+y//100-16-(y//100-16)//4
|
||||
e = e + y//100 - 16 - (y//100 - 16)//4
|
||||
else:
|
||||
# New method
|
||||
c = y//100
|
||||
h = (c-c//4-(8*c+13)//25+19*g+15)%30
|
||||
i = h-(h//28)*(1-(h//28)*(29//(h+1))*((21-g)//11))
|
||||
j = (y+y//4+i+2-c+c//4)%7
|
||||
h = (c - c//4 - (8*c + 13)//25 + 19*g + 15) % 30
|
||||
i = h - (h//28)*(1 - (h//28)*(29//(h + 1))*((21 - g)//11))
|
||||
j = (y + y//4 + i + 2 - c + c//4) % 7
|
||||
|
||||
# p can be from -6 to 56 corresponding to dates 22 March to 23 May
|
||||
# (later dates apply to method 2, although 23 May never actually occurs)
|
||||
p = i-j+e
|
||||
d = 1+(p+27+(p+6)//40)%31
|
||||
m = 3+(p+26)//30
|
||||
p = i - j + e
|
||||
d = 1 + (p + 27 + (p + 6)//40) % 31
|
||||
m = 3 + (p + 26)//30
|
||||
return datetime.date(int(y), int(m), int(d))
|
||||
|
||||
|
|
|
@ -1,50 +1,70 @@
|
|||
# -*- coding:iso-8859-1 -*-
|
||||
"""
|
||||
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
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 offers extensions to the standard Python
|
||||
datetime module.
|
||||
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:
|
||||
- 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
|
||||
specified.
|
||||
- If a time zone is omitted, it is assumed to be UTC.
|
||||
|
||||
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`.
|
||||
|
||||
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`.
|
||||
- If the element is larger than the largest specified element, select the
|
||||
*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
|
||||
inverted, and instead the *next* time matching the specified conditions is
|
||||
returned.
|
||||
|
||||
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>`_
|
||||
- `Time Formats (Planetary Rings Node) <http://pds-rings.seti.org/tools/time_formats.html>`_
|
||||
- `CPAN ParseDate module
|
||||
<http://search.cpan.org/~muir/Time-modules-2013.0912/lib/Time/ParseDate.pm>`_
|
||||
- `Java SimpleDateFormat Class
|
||||
<https://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>`_
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
__license__ = "Simplified BSD"
|
||||
|
||||
|
||||
import datetime
|
||||
import string
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
import collections
|
||||
|
||||
try:
|
||||
from io import StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
from io import StringIO
|
||||
from calendar import monthrange, isleap
|
||||
|
||||
from six import text_type, binary_type, integer_types
|
||||
|
||||
from . import relativedelta
|
||||
from . import tz
|
||||
|
||||
|
||||
__all__ = ["parse", "parserinfo"]
|
||||
|
||||
|
||||
# Some pointers:
|
||||
#
|
||||
# http://www.cl.cam.ac.uk/~mgk25/iso-time.html
|
||||
# http://www.iso.ch/iso/en/prods-services/popstds/datesandtime.html
|
||||
# http://www.w3.org/TR/NOTE-datetime
|
||||
# http://ringmaster.arc.nasa.gov/tools/time_formats.html
|
||||
# http://search.cpan.org/author/MUIR/Time-modules-2003.0211/lib/Time/ParseDate.pm
|
||||
# http://stein.cshl.org/jade/distrib/docs/java.text.SimpleDateFormat.html
|
||||
|
||||
|
||||
class _timelex(object):
|
||||
|
||||
def __init__(self, instream):
|
||||
if isinstance(instream, binary_type):
|
||||
instream = instream.decode()
|
||||
|
||||
if isinstance(instream, text_type):
|
||||
instream = StringIO(instream)
|
||||
|
||||
self.instream = instream
|
||||
self.wordchars = ('abcdfeghijklmnopqrstuvwxyz'
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'
|
||||
|
@ -57,25 +77,47 @@ class _timelex(object):
|
|||
self.eof = False
|
||||
|
||||
def get_token(self):
|
||||
"""
|
||||
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 main complication arises from the fact that dots ('.') can be used
|
||||
both as separators (e.g. "Sep.20.2009") or decimal points (e.g.
|
||||
"4:30:21.447"). As such, it is necessary to read the full context of
|
||||
any dot-separated strings before breaking it into tokens; as such, this
|
||||
function maintains a "token stack", for when the ambiguous context
|
||||
demands that multiple tokens be parsed at once.
|
||||
"""
|
||||
if self.tokenstack:
|
||||
return self.tokenstack.pop(0)
|
||||
|
||||
seenletters = False
|
||||
token = None
|
||||
state = None
|
||||
wordchars = self.wordchars
|
||||
numchars = self.numchars
|
||||
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
|
||||
# charstack.
|
||||
if self.charstack:
|
||||
nextchar = self.charstack.pop(0)
|
||||
else:
|
||||
nextchar = self.instream.read(1)
|
||||
while nextchar == '\x00':
|
||||
nextchar = self.instream.read(1)
|
||||
|
||||
if not nextchar:
|
||||
self.eof = True
|
||||
break
|
||||
elif not state:
|
||||
# First character of the token - determines if we're starting
|
||||
# to parse a word, a number or something else.
|
||||
token = nextchar
|
||||
if nextchar in wordchars:
|
||||
state = 'a'
|
||||
|
@ -87,6 +129,8 @@ class _timelex(object):
|
|||
else:
|
||||
break # emit token
|
||||
elif state == 'a':
|
||||
# If we've already started reading a word, we keep reading
|
||||
# letters until we find something that's not part of a word.
|
||||
seenletters = True
|
||||
if nextchar in wordchars:
|
||||
token += nextchar
|
||||
|
@ -97,6 +141,8 @@ class _timelex(object):
|
|||
self.charstack.append(nextchar)
|
||||
break # emit token
|
||||
elif state == '0':
|
||||
# If we've already started reading a number, we keep reading
|
||||
# numbers until we find something that doesn't fit.
|
||||
if nextchar in numchars:
|
||||
token += nextchar
|
||||
elif nextchar == '.':
|
||||
|
@ -106,6 +152,8 @@ class _timelex(object):
|
|||
self.charstack.append(nextchar)
|
||||
break # emit token
|
||||
elif state == 'a.':
|
||||
# If we've seen some letters and a dot separator, continue
|
||||
# parsing, and the tokens will be broken up later.
|
||||
seenletters = True
|
||||
if nextchar == '.' or nextchar in wordchars:
|
||||
token += nextchar
|
||||
|
@ -116,6 +164,8 @@ class _timelex(object):
|
|||
self.charstack.append(nextchar)
|
||||
break # emit token
|
||||
elif state == '0.':
|
||||
# If we've seen at least one dot separator, keep going, we'll
|
||||
# break up the tokens later.
|
||||
if nextchar == '.' or nextchar in numchars:
|
||||
token += nextchar
|
||||
elif nextchar in wordchars and token[-1] == '.':
|
||||
|
@ -124,14 +174,16 @@ class _timelex(object):
|
|||
else:
|
||||
self.charstack.append(nextchar)
|
||||
break # emit token
|
||||
if (state in ('a.', '0.') and
|
||||
(seenletters or token.count('.') > 1 or token[-1] == '.')):
|
||||
|
||||
if (state in ('a.', '0.') and (seenletters or token.count('.') > 1 or
|
||||
token[-1] == '.')):
|
||||
l = token.split('.')
|
||||
token = l[0]
|
||||
for tok in l[1:]:
|
||||
self.tokenstack.append('.')
|
||||
if tok:
|
||||
self.tokenstack.append(tok)
|
||||
|
||||
return token
|
||||
|
||||
def __iter__(self):
|
||||
|
@ -141,6 +193,7 @@ class _timelex(object):
|
|||
token = self.get_token()
|
||||
if token is None:
|
||||
raise StopIteration
|
||||
|
||||
return token
|
||||
|
||||
def next(self):
|
||||
|
@ -170,6 +223,22 @@ 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.
|
||||
|
||||
: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`.
|
||||
|
||||
: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`.
|
||||
"""
|
||||
|
||||
# m from a.m/p.m, t from ISO T separator
|
||||
JUMP = [" ", ".", ",", ";", "-", "/", "'",
|
||||
|
@ -204,7 +273,7 @@ class parserinfo(object):
|
|||
PERTAIN = ["of"]
|
||||
TZOFFSET = {}
|
||||
|
||||
def __init__(self, dayfirst=False, yearfirst=False):
|
||||
def __init__(self, dayfirst=False, yearfirst=False, smart_defaults=False):
|
||||
self._jump = self._convert(self.JUMP)
|
||||
self._weekdays = self._convert(self.WEEKDAYS)
|
||||
self._months = self._convert(self.MONTHS)
|
||||
|
@ -215,14 +284,14 @@ class parserinfo(object):
|
|||
|
||||
self.dayfirst = dayfirst
|
||||
self.yearfirst = yearfirst
|
||||
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 = {}
|
||||
for i in range(len(lst)):
|
||||
v = lst[i]
|
||||
for i, v in enumerate(lst):
|
||||
if isinstance(v, tuple):
|
||||
for v in v:
|
||||
dct[v.lower()] = i
|
||||
|
@ -270,6 +339,7 @@ class parserinfo(object):
|
|||
def tzoffset(self, name):
|
||||
if name in self._utczone:
|
||||
return 0
|
||||
|
||||
return self.TZOFFSET.get(name)
|
||||
|
||||
def convertyear(self, year):
|
||||
|
@ -286,6 +356,7 @@ class parserinfo(object):
|
|||
# move to info
|
||||
if res.year is not None:
|
||||
res.year = self.convertyear(res.year)
|
||||
|
||||
if res.tzoffset == 0 and not res.tzname or res.tzname == 'Z':
|
||||
res.tzname = "UTC"
|
||||
res.tzoffset = 0
|
||||
|
@ -295,37 +366,192 @@ class parserinfo(object):
|
|||
|
||||
|
||||
class parser(object):
|
||||
|
||||
def __init__(self, info=None):
|
||||
self.info = info or parserinfo()
|
||||
|
||||
def parse(self, timestr, default=None,
|
||||
ignoretz=False, tzinfos=None,
|
||||
**kwargs):
|
||||
if not default:
|
||||
def parse(self, timestr, default=None, ignoretz=False, tzinfos=None,
|
||||
smart_defaults=None, date_in_future=False,
|
||||
fallback_on_invalid_day=None, **kwargs):
|
||||
"""
|
||||
Parse the date/time string into a 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.
|
||||
|
||||
: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.
|
||||
|
||||
: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
|
||||
"Feb 1, 2013" and "May 3" parses to May 3rd, 2012). Setting this
|
||||
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.
|
||||
|
||||
:param ignoretz:
|
||||
Whether or not to ignore the time zone.
|
||||
|
||||
: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.
|
||||
|
||||
:param **kwargs:
|
||||
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.
|
||||
|
||||
: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.
|
||||
|
||||
:raises OverFlowError:
|
||||
Raised if the parsed date exceeds the largest valid C integer on
|
||||
your system.
|
||||
"""
|
||||
|
||||
if smart_defaults is None:
|
||||
smart_defaults = self.info.smart_defaults
|
||||
|
||||
if default is None:
|
||||
effective_dt = datetime.datetime.now()
|
||||
default = datetime.datetime.now().replace(hour=0, minute=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)
|
||||
|
||||
if res is None:
|
||||
raise ValueError("unknown string format")
|
||||
raise ValueError("Unknown string format")
|
||||
|
||||
repl = {}
|
||||
for attr in ["year", "month", "day", "hour",
|
||||
"minute", "second", "microsecond"]:
|
||||
for attr in ("year", "month", "day", "hour",
|
||||
"minute", "second", "microsecond"):
|
||||
value = getattr(res, attr)
|
||||
if value is not None:
|
||||
repl[attr] = value
|
||||
|
||||
# Choose the correct fallback position if requested by the
|
||||
# `smart_defaults` parameter.
|
||||
if smart_defaults:
|
||||
# Determine if it refers to this year, last year or next year
|
||||
if res.year is None:
|
||||
if res.month is not None:
|
||||
# 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)
|
||||
|
||||
if date_in_future:
|
||||
next_year += ly_offset
|
||||
|
||||
if not isleap(next_year):
|
||||
next_year += ly_offset
|
||||
|
||||
if not isleap(default.year):
|
||||
default = default.replace(year=next_year)
|
||||
elif date_in_future:
|
||||
next_year = default.year + 1
|
||||
else:
|
||||
next_year = default.year - 1
|
||||
|
||||
if ((res.month == default.month and res.day is not None and
|
||||
((res.day < default.day and date_in_future) or
|
||||
(res.day > default.day and not date_in_future))) or
|
||||
((res.month < default.month and date_in_future) or
|
||||
(res.month > default.month and not date_in_future))):
|
||||
|
||||
default = default.replace(year=next_year)
|
||||
|
||||
# Select a proper month
|
||||
if res.month is None:
|
||||
if res.year is not None:
|
||||
default = default.replace(month=1)
|
||||
|
||||
# I'm not sure if this is even possible.
|
||||
if res.day is not None:
|
||||
if res.day < default.day and date_in_future:
|
||||
default += datetime.timedelta(months=1)
|
||||
elif res.day > default.day and not date_in_future:
|
||||
default -= datetime.timedelta(months=1)
|
||||
|
||||
if res.day is None:
|
||||
# Determine if it's today, tomorrow or yesterday.
|
||||
if res.year is None and res.month is None:
|
||||
t_repl = {}
|
||||
for key, val in repl.iteritems():
|
||||
if key in ('hour', 'minute', 'second', 'microsecond'):
|
||||
t_repl[key] = val
|
||||
|
||||
stime = effective_dt.replace(**t_repl)
|
||||
|
||||
if stime < effective_dt and date_in_future:
|
||||
default += datetime.timedelta(days=1)
|
||||
elif stime > effective_dt and not date_in_future:
|
||||
default -= datetime.timedelta(days=1)
|
||||
else:
|
||||
# Otherwise it's the beginning of the month
|
||||
default = default.replace(day=1)
|
||||
|
||||
if fallback_on_invalid_day or (fallback_on_invalid_day is None and
|
||||
'day' not in repl):
|
||||
# If the default day exceeds the last day of the month, fall back to
|
||||
# the end of the month.
|
||||
cyear = default.year if res.year is None else res.year
|
||||
cmonth = default.month if res.month is None else res.month
|
||||
cday = default.day if res.day is None else res.day
|
||||
|
||||
if cday > monthrange(cyear, cmonth)[1]:
|
||||
repl['day'] = monthrange(cyear, cmonth)[1]
|
||||
|
||||
ret = default.replace(**repl)
|
||||
|
||||
if res.weekday is not None and not res.day:
|
||||
ret = ret+relativedelta.relativedelta(weekday=res.weekday)
|
||||
|
||||
if not ignoretz:
|
||||
if isinstance(tzinfos, collections.Callable) or tzinfos and res.tzname in tzinfos:
|
||||
if (isinstance(tzinfos, collections.Callable) or
|
||||
tzinfos and res.tzname in tzinfos):
|
||||
|
||||
if isinstance(tzinfos, collections.Callable):
|
||||
tzdata = tzinfos(res.tzname, res.tzoffset)
|
||||
else:
|
||||
tzdata = tzinfos.get(res.tzname)
|
||||
|
||||
if isinstance(tzdata, datetime.tzinfo):
|
||||
tzinfo = tzdata
|
||||
elif isinstance(tzdata, text_type):
|
||||
|
@ -333,8 +559,8 @@ class parser(object):
|
|||
elif isinstance(tzdata, integer_types):
|
||||
tzinfo = tz.tzoffset(res.tzname, tzdata)
|
||||
else:
|
||||
raise ValueError("offset must be tzinfo subclass, " \
|
||||
"tz string, or int offset")
|
||||
raise ValueError("Offset must be tzinfo subclass, "
|
||||
"tz string, or int offset.")
|
||||
ret = ret.replace(tzinfo=tzinfo)
|
||||
elif res.tzname and res.tzname in time.tzname:
|
||||
ret = ret.replace(tzinfo=tz.tzlocal())
|
||||
|
@ -343,28 +569,64 @@ class parser(object):
|
|||
elif res.tzoffset:
|
||||
ret = ret.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset))
|
||||
|
||||
if skipped_tokens:
|
||||
if kwargs.get('fuzzy_with_tokens', False):
|
||||
return ret, skipped_tokens
|
||||
|
||||
else:
|
||||
return ret
|
||||
|
||||
class _result(_resultbase):
|
||||
__slots__ = ["year", "month", "day", "weekday",
|
||||
"hour", "minute", "second", "microsecond",
|
||||
"tzname", "tzoffset"]
|
||||
"tzname", "tzoffset", "ampm"]
|
||||
|
||||
def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, fuzzy_with_tokens=False):
|
||||
def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False,
|
||||
fuzzy_with_tokens=False):
|
||||
"""
|
||||
Private method which performs the heavy lifting of parsing, called from
|
||||
`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`).
|
||||
|
||||
: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`).
|
||||
|
||||
: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 fuzzy_with_tokens:
|
||||
fuzzy = True
|
||||
|
||||
info = self.info
|
||||
|
||||
if dayfirst is None:
|
||||
dayfirst = info.dayfirst
|
||||
|
||||
if yearfirst is None:
|
||||
yearfirst = info.yearfirst
|
||||
res = self._result()
|
||||
l = _timelex.split(timestr)
|
||||
|
||||
res = self._result()
|
||||
l = _timelex.split(timestr) # Splits the timestr into tokens
|
||||
|
||||
# keep up with the last token skipped so we can recombine
|
||||
# consecutively skipped tokens (-2 for when i begins at 0).
|
||||
|
@ -372,7 +634,6 @@ class parser(object):
|
|||
skipped_tokens = list()
|
||||
|
||||
try:
|
||||
|
||||
# year/month/day list
|
||||
ymd = []
|
||||
|
||||
|
@ -394,17 +655,21 @@ class parser(object):
|
|||
# Token is a number
|
||||
len_li = len(l[i])
|
||||
i += 1
|
||||
|
||||
if (len(ymd) == 3 and len_li in (2, 4)
|
||||
and (i >= len_l or (l[i] != ':' and
|
||||
and res.hour is None and (i >= len_l or (l[i] != ':' and
|
||||
info.hms(l[i]) is None))):
|
||||
# 19990101T23[59]
|
||||
s = l[i-1]
|
||||
res.hour = int(s[:2])
|
||||
|
||||
if len_li == 4:
|
||||
res.minute = int(s[2:])
|
||||
|
||||
elif len_li == 6 or (len_li > 6 and l[i-1].find('.') == 6):
|
||||
# YYMMDD or HHMMSS[.ss]
|
||||
s = l[i-1]
|
||||
|
||||
if not ymd and l[i-1].find('.') == -1:
|
||||
ymd.append(info.convertyear(int(s[:2])))
|
||||
ymd.append(int(s[2:4]))
|
||||
|
@ -414,12 +679,14 @@ class parser(object):
|
|||
res.hour = int(s[:2])
|
||||
res.minute = int(s[2:4])
|
||||
res.second, res.microsecond = _parsems(s[4:])
|
||||
|
||||
elif len_li == 8:
|
||||
# YYYYMMDD
|
||||
s = l[i-1]
|
||||
ymd.append(int(s[:4]))
|
||||
ymd.append(int(s[4:6]))
|
||||
ymd.append(int(s[6:]))
|
||||
|
||||
elif len_li in (12, 14):
|
||||
# YYYYMMDDhhmm[ss]
|
||||
s = l[i-1]
|
||||
|
@ -428,30 +695,42 @@ class parser(object):
|
|||
ymd.append(int(s[6:8]))
|
||||
res.hour = int(s[8:10])
|
||||
res.minute = int(s[10:12])
|
||||
|
||||
if len_li == 14:
|
||||
res.second = int(s[12:])
|
||||
|
||||
elif ((i < len_l and info.hms(l[i]) is not None) or
|
||||
(i+1 < len_l and l[i] == ' ' and
|
||||
info.hms(l[i+1]) is not None)):
|
||||
|
||||
# HH[ ]h or MM[ ]m or SS[.ss][ ]s
|
||||
if l[i] == ' ':
|
||||
i += 1
|
||||
|
||||
idx = info.hms(l[i])
|
||||
|
||||
while True:
|
||||
if idx == 0:
|
||||
res.hour = int(value)
|
||||
if value%1:
|
||||
res.minute = int(60*(value%1))
|
||||
|
||||
if value % 1:
|
||||
res.minute = int(60*(value % 1))
|
||||
|
||||
elif idx == 1:
|
||||
res.minute = int(value)
|
||||
if value%1:
|
||||
res.second = int(60*(value%1))
|
||||
|
||||
if value % 1:
|
||||
res.second = int(60*(value % 1))
|
||||
|
||||
elif idx == 2:
|
||||
res.second, res.microsecond = \
|
||||
_parsems(value_repr)
|
||||
|
||||
i += 1
|
||||
|
||||
if i >= len_l or idx == 2:
|
||||
break
|
||||
|
||||
# 12h00
|
||||
try:
|
||||
value_repr = l[i]
|
||||
|
@ -461,37 +740,49 @@ class parser(object):
|
|||
else:
|
||||
i += 1
|
||||
idx += 1
|
||||
|
||||
if i < len_l:
|
||||
newidx = info.hms(l[i])
|
||||
|
||||
if newidx is not None:
|
||||
idx = newidx
|
||||
elif i == len_l and l[i-2] == ' ' and info.hms(l[i-3]) is not None:
|
||||
|
||||
elif (i == len_l and l[i-2] == ' ' and
|
||||
info.hms(l[i-3]) is not None):
|
||||
# X h MM or X m SS
|
||||
idx = info.hms(l[i-3]) + 1
|
||||
|
||||
if idx == 1:
|
||||
res.minute = int(value)
|
||||
if value%1:
|
||||
res.second = int(60*(value%1))
|
||||
|
||||
if value % 1:
|
||||
res.second = int(60*(value % 1))
|
||||
elif idx == 2:
|
||||
res.second, res.microsecond = \
|
||||
_parsems(value_repr)
|
||||
i += 1
|
||||
|
||||
elif i+1 < len_l and l[i] == ':':
|
||||
# HH:MM[:SS[.ss]]
|
||||
res.hour = int(value)
|
||||
i += 1
|
||||
value = float(l[i])
|
||||
res.minute = int(value)
|
||||
if value%1:
|
||||
res.second = int(60*(value%1))
|
||||
|
||||
if value % 1:
|
||||
res.second = int(60*(value % 1))
|
||||
|
||||
i += 1
|
||||
|
||||
if i < len_l and l[i] == ':':
|
||||
res.second, res.microsecond = _parsems(l[i+1])
|
||||
i += 2
|
||||
|
||||
elif i < len_l and l[i] in ('-', '/', '.'):
|
||||
sep = l[i]
|
||||
ymd.append(int(value))
|
||||
i += 1
|
||||
|
||||
if i < len_l and not info.jump(l[i]):
|
||||
try:
|
||||
# 01-01[-01]
|
||||
|
@ -499,45 +790,55 @@ class parser(object):
|
|||
except ValueError:
|
||||
# 01-Jan[-01]
|
||||
value = info.month(l[i])
|
||||
|
||||
if value is not None:
|
||||
ymd.append(value)
|
||||
assert mstridx == -1
|
||||
mstridx = len(ymd)-1
|
||||
else:
|
||||
return None
|
||||
|
||||
i += 1
|
||||
|
||||
if i < len_l and l[i] == sep:
|
||||
# We have three members
|
||||
i += 1
|
||||
value = info.month(l[i])
|
||||
|
||||
if value is not None:
|
||||
ymd.append(value)
|
||||
mstridx = len(ymd)-1
|
||||
assert mstridx == -1
|
||||
else:
|
||||
ymd.append(int(l[i]))
|
||||
|
||||
i += 1
|
||||
elif i >= len_l or info.jump(l[i]):
|
||||
if i+1 < len_l and info.ampm(l[i+1]) is not None:
|
||||
# 12 am
|
||||
res.hour = int(value)
|
||||
|
||||
if res.hour < 12 and info.ampm(l[i+1]) == 1:
|
||||
res.hour += 12
|
||||
elif res.hour == 12 and info.ampm(l[i+1]) == 0:
|
||||
res.hour = 0
|
||||
|
||||
i += 1
|
||||
else:
|
||||
# Year, month or day
|
||||
ymd.append(int(value))
|
||||
i += 1
|
||||
elif info.ampm(l[i]) is not None:
|
||||
|
||||
# 12am
|
||||
res.hour = int(value)
|
||||
|
||||
if res.hour < 12 and info.ampm(l[i]) == 1:
|
||||
res.hour += 12
|
||||
elif res.hour == 12 and info.ampm(l[i]) == 0:
|
||||
res.hour = 0
|
||||
i += 1
|
||||
|
||||
elif not fuzzy:
|
||||
return None
|
||||
else:
|
||||
|
@ -557,6 +858,7 @@ class parser(object):
|
|||
ymd.append(value)
|
||||
assert mstridx == -1
|
||||
mstridx = len(ymd)-1
|
||||
|
||||
i += 1
|
||||
if i < len_l:
|
||||
if l[i] in ('-', '/'):
|
||||
|
@ -565,11 +867,13 @@ class parser(object):
|
|||
i += 1
|
||||
ymd.append(int(l[i]))
|
||||
i += 1
|
||||
|
||||
if i < len_l and l[i] == sep:
|
||||
# Jan-01-99
|
||||
i += 1
|
||||
ymd.append(int(l[i]))
|
||||
i += 1
|
||||
|
||||
elif (i+3 < len_l and l[i] == l[i+2] == ' '
|
||||
and info.pertain(l[i+1])):
|
||||
# Jan of 01
|
||||
|
@ -588,17 +892,47 @@ class parser(object):
|
|||
# Check am/pm
|
||||
value = info.ampm(l[i])
|
||||
if value is not None:
|
||||
# For fuzzy parsing, 'a' or 'am' (both valid English words)
|
||||
# may erroneously trigger the AM/PM flag. Deal with that
|
||||
# here.
|
||||
val_is_ampm = True
|
||||
|
||||
# If there's already an AM/PM flag, this one isn't one.
|
||||
if fuzzy and res.ampm is not None:
|
||||
val_is_ampm = False
|
||||
|
||||
# If AM/PM is found and hour is not, raise a ValueError
|
||||
if res.hour is None:
|
||||
if fuzzy:
|
||||
val_is_ampm = False
|
||||
else:
|
||||
raise ValueError('No hour specified with ' +
|
||||
'AM or PM flag.')
|
||||
elif not 0 <= res.hour <= 12:
|
||||
# If AM/PM is found, it's a 12 hour clock, so raise
|
||||
# an error for invalid range
|
||||
if fuzzy:
|
||||
val_is_ampm = False
|
||||
else:
|
||||
raise ValueError('Invalid hour specified for ' +
|
||||
'12-hour clock.')
|
||||
|
||||
if val_is_ampm:
|
||||
if value == 1 and res.hour < 12:
|
||||
res.hour += 12
|
||||
elif value == 0 and res.hour == 12:
|
||||
res.hour = 0
|
||||
|
||||
res.ampm = value
|
||||
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Check for a timezone name
|
||||
if (res.hour is not None and len(l[i]) <= 5 and
|
||||
res.tzname is None and res.tzoffset is None and
|
||||
not [x for x in l[i] if x not in string.ascii_uppercase]):
|
||||
not [x for x in l[i] if x not in
|
||||
string.ascii_uppercase]):
|
||||
res.tzname = l[i]
|
||||
res.tzoffset = info.tzoffset(res.tzname)
|
||||
i += 1
|
||||
|
@ -623,6 +957,7 @@ class parser(object):
|
|||
signal = (-1, 1)[l[i] == '+']
|
||||
i += 1
|
||||
len_li = len(l[i])
|
||||
|
||||
if len_li == 4:
|
||||
# -0300
|
||||
res.tzoffset = int(l[i][:2])*3600+int(l[i][2:])*60
|
||||
|
@ -636,6 +971,7 @@ class parser(object):
|
|||
else:
|
||||
return None
|
||||
i += 1
|
||||
|
||||
res.tzoffset *= signal
|
||||
|
||||
# Look for a timezone name between parenthesis
|
||||
|
@ -672,11 +1008,13 @@ class parser(object):
|
|||
if mstridx != -1:
|
||||
res.month = ymd[mstridx]
|
||||
del ymd[mstridx]
|
||||
|
||||
if len_ymd > 1 or mstridx == -1:
|
||||
if ymd[0] > 31:
|
||||
res.year = ymd[0]
|
||||
else:
|
||||
res.day = ymd[0]
|
||||
|
||||
elif len_ymd == 2:
|
||||
# Two members with numbers
|
||||
if ymd[0] > 31:
|
||||
|
@ -691,7 +1029,8 @@ class parser(object):
|
|||
else:
|
||||
# 01-13
|
||||
res.month, res.day = ymd
|
||||
if len_ymd == 3:
|
||||
|
||||
elif len_ymd == 3:
|
||||
# Three members
|
||||
if mstridx == 0:
|
||||
res.month, res.day, res.year = ymd
|
||||
|
@ -704,6 +1043,7 @@ class parser(object):
|
|||
# Give precendence to day-first, since
|
||||
# two-digit years is usually hand-written.
|
||||
res.day, res.month, res.year = ymd
|
||||
|
||||
elif mstridx == 2:
|
||||
# WTF!?
|
||||
if ymd[1] > 31:
|
||||
|
@ -712,6 +1052,7 @@ class parser(object):
|
|||
else:
|
||||
# 99-01-Jan
|
||||
res.year, res.day, res.month = ymd
|
||||
|
||||
else:
|
||||
if ymd[0] > 31 or \
|
||||
(yearfirst and ymd[1] <= 12 and ymd[2] <= 31):
|
||||
|
@ -732,16 +1073,66 @@ class parser(object):
|
|||
|
||||
if fuzzy_with_tokens:
|
||||
return res, tuple(skipped_tokens)
|
||||
|
||||
return res, None
|
||||
else:
|
||||
return res
|
||||
|
||||
DEFAULTPARSER = parser()
|
||||
|
||||
|
||||
def parse(timestr, parserinfo=None, **kwargs):
|
||||
# Python 2.x support: datetimes return their string presentation as
|
||||
# bytes in 2.x and unicode in 3.x, so it's reasonable to expect that
|
||||
# the parser will get both kinds. Internally we use unicode only.
|
||||
if isinstance(timestr, binary_type):
|
||||
timestr = timestr.decode()
|
||||
"""
|
||||
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.
|
||||
|
||||
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
|
||||
default object.
|
||||
|
||||
:param ignoretz:
|
||||
Whether or not to ignore the time zone (boolean).
|
||||
|
||||
: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.
|
||||
|
||||
: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`).
|
||||
|
||||
: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
|
||||
: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 parserinfo:
|
||||
return parser(parserinfo).parse(timestr, **kwargs)
|
||||
else:
|
||||
|
@ -789,8 +1180,8 @@ class _tzparser(object):
|
|||
offattr = "dstoffset"
|
||||
res.dstabbr = "".join(l[i:j])
|
||||
i = j
|
||||
if (i < len_l and
|
||||
(l[i] in ('+', '-') or l[i][0] in "0123456789")):
|
||||
if (i < len_l and (l[i] in ('+', '-') or l[i][0] in
|
||||
"0123456789")):
|
||||
if l[i] in ('+', '-'):
|
||||
# Yes, that's right. See the TZ variable
|
||||
# documentation.
|
||||
|
@ -801,8 +1192,8 @@ class _tzparser(object):
|
|||
len_li = len(l[i])
|
||||
if len_li == 4:
|
||||
# -0300
|
||||
setattr(res, offattr,
|
||||
(int(l[i][:2])*3600+int(l[i][2:])*60)*signal)
|
||||
setattr(res, offattr, (int(l[i][:2])*3600 +
|
||||
int(l[i][2:])*60)*signal)
|
||||
elif i+1 < len_l and l[i+1] == ':':
|
||||
# -03:00
|
||||
setattr(res, offattr,
|
||||
|
@ -822,7 +1213,8 @@ class _tzparser(object):
|
|||
|
||||
if i < len_l:
|
||||
for j in range(i, len_l):
|
||||
if l[j] == ';': l[j] = ','
|
||||
if l[j] == ';':
|
||||
l[j] = ','
|
||||
|
||||
assert l[i] == ','
|
||||
|
||||
|
@ -845,7 +1237,7 @@ class _tzparser(object):
|
|||
i += 2
|
||||
if value:
|
||||
x.week = value
|
||||
x.weekday = (int(l[i])-1)%7
|
||||
x.weekday = (int(l[i])-1) % 7
|
||||
else:
|
||||
x.day = int(l[i])
|
||||
i += 2
|
||||
|
@ -880,7 +1272,7 @@ class _tzparser(object):
|
|||
i += 1
|
||||
assert l[i] in ('-', '.')
|
||||
i += 1
|
||||
x.weekday = (int(l[i])-1)%7
|
||||
x.weekday = (int(l[i])-1) % 7
|
||||
else:
|
||||
# year day (zero based)
|
||||
x.yday = int(l[i])+1
|
||||
|
@ -921,6 +1313,8 @@ class _tzparser(object):
|
|||
|
||||
|
||||
DEFAULTTZPARSER = _tzparser()
|
||||
|
||||
|
||||
def _parsetz(tzstr):
|
||||
return DEFAULTTZPARSER.parse(tzstr)
|
||||
|
||||
|
|
|
@ -1,11 +1,4 @@
|
|||
"""
|
||||
Copyright (c) 2003-2010 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard Python
|
||||
datetime module.
|
||||
"""
|
||||
__license__ = "Simplified BSD"
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
import calendar
|
||||
|
||||
|
@ -13,6 +6,7 @@ from six import integer_types
|
|||
|
||||
__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
|
||||
|
||||
|
||||
class weekday(object):
|
||||
__slots__ = ["weekday", "n"]
|
||||
|
||||
|
@ -43,25 +37,35 @@ class weekday(object):
|
|||
|
||||
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)])
|
||||
|
||||
|
||||
class relativedelta(object):
|
||||
"""
|
||||
The relativedelta type is based on the specification of the excelent
|
||||
work done by M.-A. Lemburg in his mx.DateTime extension. However,
|
||||
notice that this type does *NOT* implement the same algorithm as
|
||||
The relativedelta type is based on the specification of the excellent
|
||||
work done by M.-A. Lemburg in his
|
||||
`mx.DateTime <http://www.egenix.com/files/python/mxDateTime.html>`_ 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.
|
||||
|
||||
There's two different ways to build a relativedelta instance. The
|
||||
first one is passing it two date/datetime classes:
|
||||
There are two different ways to build a relativedelta instance. The
|
||||
first one is passing it two date/datetime classes::
|
||||
|
||||
relativedelta(datetime1, datetime2)
|
||||
|
||||
And the other way is to use the following keyword arguments:
|
||||
The second one is passing it any number of the following keyword arguments::
|
||||
|
||||
relativedelta(arg1=x,arg2=y,arg3=z...)
|
||||
|
||||
year, month, day, hour, minute, second, microsecond:
|
||||
Absolute information.
|
||||
Absolute information (argument is singular); adding or subtracting a
|
||||
relativedelta with absolute information does not perform an aritmetic
|
||||
operation, but rather REPLACES the corresponding value in the
|
||||
original datetime with the value(s) in relativedelta.
|
||||
|
||||
years, months, weeks, days, hours, minutes, seconds, microseconds:
|
||||
Relative information, may be negative.
|
||||
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
|
||||
with the information in the relativedelta.
|
||||
|
||||
weekday:
|
||||
One of the weekday instances (MO, TU, etc). These instances may
|
||||
|
@ -80,26 +84,26 @@ And the other way is to use the following keyword arguments:
|
|||
|
||||
Here is the behavior of operations with relativedelta:
|
||||
|
||||
1) Calculate the absolute year, using the 'year' argument, or the
|
||||
1. Calculate the absolute year, using the 'year' argument, or the
|
||||
original datetime year, if the argument is not present.
|
||||
|
||||
2) Add the relative 'years' argument to the absolute year.
|
||||
2. Add the relative 'years' argument to the absolute year.
|
||||
|
||||
3) Do steps 1 and 2 for month/months.
|
||||
3. Do steps 1 and 2 for month/months.
|
||||
|
||||
4) Calculate the absolute day, using the 'day' argument, or the
|
||||
4. Calculate the absolute day, using the 'day' argument, or the
|
||||
original datetime day, if the argument is not present. Then,
|
||||
subtract from the day until it fits in the year and month
|
||||
found after their operations.
|
||||
|
||||
5) Add the relative 'days' argument to the absolute day. Notice
|
||||
5. Add the relative 'days' argument to the absolute day. Notice
|
||||
that the 'weeks' argument is multiplied by 7 and added to
|
||||
'days'.
|
||||
|
||||
6) Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds,
|
||||
6. Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds,
|
||||
microsecond/microseconds.
|
||||
|
||||
7) If the 'weekday' argument is present, calculate the weekday,
|
||||
7. If the 'weekday' argument is present, calculate the weekday,
|
||||
with the given (wday, nth) tuple. wday is the index of the
|
||||
weekday (0-6, 0=Mon), and nth is the number of weeks to add
|
||||
forward or backward, depending on its signal. Notice that if
|
||||
|
@ -114,9 +118,14 @@ Here is the behavior of operations with relativedelta:
|
|||
yearday=None, nlyearday=None,
|
||||
hour=None, minute=None, second=None, microsecond=None):
|
||||
if dt1 and dt2:
|
||||
if (not isinstance(dt1, datetime.date)) or (not isinstance(dt2, datetime.date)):
|
||||
# datetime is a subclass of date. So both must be date
|
||||
if not (isinstance(dt1, datetime.date) and
|
||||
isinstance(dt2, datetime.date)):
|
||||
raise TypeError("relativedelta only diffs datetime/date")
|
||||
if not type(dt1) == type(dt2): #isinstance(dt1, type(dt2)):
|
||||
# We allow two dates, or two datetimes, so we coerce them to be
|
||||
# of the same type
|
||||
if (isinstance(dt1, datetime.datetime) !=
|
||||
isinstance(dt2, datetime.datetime)):
|
||||
if not isinstance(dt1, datetime.datetime):
|
||||
dt1 = datetime.datetime.fromordinal(dt1.toordinal())
|
||||
elif not isinstance(dt2, datetime.datetime):
|
||||
|
@ -158,7 +167,7 @@ Here is the behavior of operations with relativedelta:
|
|||
else:
|
||||
self.years = years
|
||||
self.months = months
|
||||
self.days = days+weeks*7
|
||||
self.days = days + weeks * 7
|
||||
self.leapdays = leapdays
|
||||
self.hours = hours
|
||||
self.minutes = minutes
|
||||
|
@ -185,7 +194,8 @@ Here is the behavior of operations with relativedelta:
|
|||
if yearday > 59:
|
||||
self.leapdays = -1
|
||||
if yday:
|
||||
ydayidx = [31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 366]
|
||||
ydayidx = [31, 59, 90, 120, 151, 181, 212,
|
||||
243, 273, 304, 334, 366]
|
||||
for idx, ydays in enumerate(ydayidx):
|
||||
if yday <= ydays:
|
||||
self.month = idx+1
|
||||
|
@ -225,13 +235,20 @@ Here is the behavior of operations with relativedelta:
|
|||
div, mod = divmod(self.months*s, 12)
|
||||
self.months = mod*s
|
||||
self.years += div*s
|
||||
if (self.hours or self.minutes or self.seconds or self.microseconds or
|
||||
self.hour is not None or self.minute is not None or
|
||||
if (self.hours or self.minutes or self.seconds or self.microseconds
|
||||
or self.hour is not None or self.minute is not None or
|
||||
self.second is not None or self.microsecond is not None):
|
||||
self._has_time = 1
|
||||
else:
|
||||
self._has_time = 0
|
||||
|
||||
@property
|
||||
def weeks(self):
|
||||
return self.days // 7
|
||||
@weeks.setter
|
||||
def weeks(self, value):
|
||||
self.days = self.days - (self.weeks * 7) + value*7
|
||||
|
||||
def _set_months(self, months):
|
||||
self.months = months
|
||||
if abs(self.months) > 11:
|
||||
|
@ -244,13 +261,14 @@ Here is the behavior of operations with relativedelta:
|
|||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, relativedelta):
|
||||
return relativedelta(years=other.years+self.years,
|
||||
return self.__class__(years=other.years+self.years,
|
||||
months=other.months+self.months,
|
||||
days=other.days+self.days,
|
||||
hours=other.hours+self.hours,
|
||||
minutes=other.minutes+self.minutes,
|
||||
seconds=other.seconds+self.seconds,
|
||||
microseconds=other.microseconds+self.microseconds,
|
||||
microseconds=(other.microseconds +
|
||||
self.microseconds),
|
||||
leapdays=other.leapdays or self.leapdays,
|
||||
year=other.year or self.year,
|
||||
month=other.month or self.month,
|
||||
|
@ -259,7 +277,8 @@ Here is the behavior of operations with relativedelta:
|
|||
hour=other.hour or self.hour,
|
||||
minute=other.minute or self.minute,
|
||||
second=other.second or self.second,
|
||||
microsecond=other.microsecond or self.microsecond)
|
||||
microsecond=(other.microsecond or
|
||||
self.microsecond))
|
||||
if not isinstance(other, datetime.date):
|
||||
raise TypeError("unsupported type for add operation")
|
||||
elif self._has_time and not isinstance(other, datetime.datetime):
|
||||
|
@ -295,9 +314,9 @@ Here is the behavior of operations with relativedelta:
|
|||
weekday, nth = self.weekday.weekday, self.weekday.n or 1
|
||||
jumpdays = (abs(nth)-1)*7
|
||||
if nth > 0:
|
||||
jumpdays += (7-ret.weekday()+weekday)%7
|
||||
jumpdays += (7-ret.weekday()+weekday) % 7
|
||||
else:
|
||||
jumpdays += (ret.weekday()-weekday)%7
|
||||
jumpdays += (ret.weekday()-weekday) % 7
|
||||
jumpdays *= -1
|
||||
ret += datetime.timedelta(days=jumpdays)
|
||||
return ret
|
||||
|
@ -311,7 +330,7 @@ Here is the behavior of operations with relativedelta:
|
|||
def __sub__(self, other):
|
||||
if not isinstance(other, relativedelta):
|
||||
raise TypeError("unsupported type for sub operation")
|
||||
return relativedelta(years=self.years-other.years,
|
||||
return self.__class__(years=self.years-other.years,
|
||||
months=self.months-other.months,
|
||||
days=self.days-other.days,
|
||||
hours=self.hours-other.hours,
|
||||
|
@ -329,7 +348,7 @@ Here is the behavior of operations with relativedelta:
|
|||
microsecond=self.microsecond or other.microsecond)
|
||||
|
||||
def __neg__(self):
|
||||
return relativedelta(years=-self.years,
|
||||
return self.__class__(years=-self.years,
|
||||
months=-self.months,
|
||||
days=-self.days,
|
||||
hours=-self.hours,
|
||||
|
@ -363,10 +382,12 @@ Here is the behavior of operations with relativedelta:
|
|||
self.minute is None and
|
||||
self.second is None and
|
||||
self.microsecond is None)
|
||||
# Compatibility with Python 2.x
|
||||
__nonzero__ = __bool__
|
||||
|
||||
def __mul__(self, other):
|
||||
f = float(other)
|
||||
return relativedelta(years=int(self.years*f),
|
||||
return self.__class__(years=int(self.years*f),
|
||||
months=int(self.months*f),
|
||||
days=int(self.days*f),
|
||||
hours=int(self.hours*f),
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,19 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard Python
|
||||
datetime module.
|
||||
This module offers timezone implementations subclassing the abstract
|
||||
:py:`datetime.tzinfo` type. There are classes to handle tzfile format files
|
||||
(usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`, etc), TZ
|
||||
environment string (in all known formats), given ranges (with help from
|
||||
relative deltas), local machine timezone, fixed offset timezone, and UTC
|
||||
timezone.
|
||||
"""
|
||||
__license__ = "Simplified BSD"
|
||||
|
||||
from six import string_types, PY3
|
||||
|
||||
import datetime
|
||||
import struct
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
|
||||
from six import string_types, PY3
|
||||
|
||||
try:
|
||||
from dateutil.tzwin import tzwin, tzwinlocal
|
||||
except ImportError:
|
||||
tzwin = tzwinlocal = None
|
||||
|
||||
relativedelta = None
|
||||
parser = None
|
||||
rrule = None
|
||||
|
@ -21,27 +27,26 @@ rrule = None
|
|||
__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
|
||||
"tzstr", "tzical", "tzwin", "tzwinlocal", "gettz"]
|
||||
|
||||
try:
|
||||
from dateutil.tzwin import tzwin, tzwinlocal
|
||||
except (ImportError, OSError):
|
||||
tzwin, tzwinlocal = None, None
|
||||
|
||||
def tzname_in_python2(myfunc):
|
||||
def tzname_in_python2(namefunc):
|
||||
"""Change unicode output into bytestrings in Python 2
|
||||
|
||||
tzname() API changed in Python 3. It used to return bytes, but was changed
|
||||
to unicode strings
|
||||
"""
|
||||
def inner_func(*args, **kwargs):
|
||||
if PY3:
|
||||
return myfunc(*args, **kwargs)
|
||||
else:
|
||||
return myfunc(*args, **kwargs).encode()
|
||||
return inner_func
|
||||
def adjust_encoding(*args, **kwargs):
|
||||
name = namefunc(*args, **kwargs)
|
||||
if name is not None and not PY3:
|
||||
name = name.encode()
|
||||
|
||||
return name
|
||||
|
||||
return adjust_encoding
|
||||
|
||||
ZERO = datetime.timedelta(0)
|
||||
EPOCHORDINAL = datetime.datetime.utcfromtimestamp(0).toordinal()
|
||||
|
||||
|
||||
class tzutc(datetime.tzinfo):
|
||||
|
||||
def utcoffset(self, dt):
|
||||
|
@ -66,6 +71,7 @@ class tzutc(datetime.tzinfo):
|
|||
|
||||
__reduce__ = object.__reduce__
|
||||
|
||||
|
||||
class tzoffset(datetime.tzinfo):
|
||||
|
||||
def __init__(self, name, offset):
|
||||
|
@ -96,6 +102,7 @@ class tzoffset(datetime.tzinfo):
|
|||
|
||||
__reduce__ = object.__reduce__
|
||||
|
||||
|
||||
class tzlocal(datetime.tzinfo):
|
||||
|
||||
_std_offset = datetime.timedelta(seconds=-time.timezone)
|
||||
|
@ -130,18 +137,18 @@ class tzlocal(datetime.tzinfo):
|
|||
#
|
||||
# The code above yields the following result:
|
||||
#
|
||||
#>>> import tz, datetime
|
||||
#>>> t = tz.tzlocal()
|
||||
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||
#'BRDT'
|
||||
#>>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()
|
||||
#'BRST'
|
||||
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||
#'BRST'
|
||||
#>>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()
|
||||
#'BRDT'
|
||||
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||
#'BRDT'
|
||||
# >>> import tz, datetime
|
||||
# >>> t = tz.tzlocal()
|
||||
# >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||
# 'BRDT'
|
||||
# >>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()
|
||||
# 'BRST'
|
||||
# >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||
# 'BRST'
|
||||
# >>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()
|
||||
# 'BRDT'
|
||||
# >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||
# 'BRDT'
|
||||
#
|
||||
# Here is a more stable implementation:
|
||||
#
|
||||
|
@ -166,6 +173,7 @@ class tzlocal(datetime.tzinfo):
|
|||
|
||||
__reduce__ = object.__reduce__
|
||||
|
||||
|
||||
class _ttinfo(object):
|
||||
__slots__ = ["offset", "delta", "isdst", "abbr", "isstd", "isgmt"]
|
||||
|
||||
|
@ -205,15 +213,20 @@ class _ttinfo(object):
|
|||
if name in state:
|
||||
setattr(self, name, state[name])
|
||||
|
||||
|
||||
class tzfile(datetime.tzinfo):
|
||||
|
||||
# http://www.twinsun.com/tz/tz-link.htm
|
||||
# ftp://ftp.iana.org/tz/tz*.tar.gz
|
||||
|
||||
def __init__(self, fileobj):
|
||||
def __init__(self, fileobj, filename=None):
|
||||
file_opened_here = False
|
||||
if isinstance(fileobj, string_types):
|
||||
self._filename = fileobj
|
||||
fileobj = open(fileobj, 'rb')
|
||||
file_opened_here = True
|
||||
elif filename is not None:
|
||||
self._filename = filename
|
||||
elif hasattr(fileobj, "name"):
|
||||
self._filename = fileobj.name
|
||||
else:
|
||||
|
@ -228,7 +241,7 @@ class tzfile(datetime.tzinfo):
|
|||
# six four-byte values of type long, written in a
|
||||
# ``standard'' byte order (the high-order byte
|
||||
# of the value is written first).
|
||||
|
||||
try:
|
||||
if fileobj.read(4).decode() != "TZif":
|
||||
raise ValueError("magic not found")
|
||||
|
||||
|
@ -313,9 +326,9 @@ class tzfile(datetime.tzinfo):
|
|||
# by time.
|
||||
|
||||
# Not used, for now
|
||||
if leapcnt:
|
||||
leap = struct.unpack(">%dl" % (leapcnt*2),
|
||||
fileobj.read(leapcnt*8))
|
||||
# if leapcnt:
|
||||
# leap = struct.unpack(">%dl" % (leapcnt*2),
|
||||
# fileobj.read(leapcnt*8))
|
||||
|
||||
# Then there are tzh_ttisstdcnt standard/wall
|
||||
# indicators, each stored as a one-byte value;
|
||||
|
@ -342,6 +355,9 @@ class tzfile(datetime.tzinfo):
|
|||
fileobj.read(ttisgmtcnt))
|
||||
|
||||
# ** Everything has been read **
|
||||
finally:
|
||||
if file_opened_here:
|
||||
fileobj.close()
|
||||
|
||||
# Build ttinfo list
|
||||
self._ttinfo_list = []
|
||||
|
@ -481,7 +497,6 @@ class tzfile(datetime.tzinfo):
|
|||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s)" % (self.__class__.__name__, repr(self._filename))
|
||||
|
||||
|
@ -490,8 +505,8 @@ class tzfile(datetime.tzinfo):
|
|||
raise ValueError("Unpickable %s class" % self.__class__.__name__)
|
||||
return (self.__class__, (self._filename,))
|
||||
|
||||
class tzrange(datetime.tzinfo):
|
||||
|
||||
class tzrange(datetime.tzinfo):
|
||||
def __init__(self, stdabbr, stdoffset=None,
|
||||
dstabbr=None, dstoffset=None,
|
||||
start=None, end=None):
|
||||
|
@ -570,6 +585,7 @@ class tzrange(datetime.tzinfo):
|
|||
|
||||
__reduce__ = object.__reduce__
|
||||
|
||||
|
||||
class tzstr(tzrange):
|
||||
|
||||
def __init__(self, s):
|
||||
|
@ -645,6 +661,7 @@ class tzstr(tzrange):
|
|||
def __repr__(self):
|
||||
return "%s(%s)" % (self.__class__.__name__, repr(self._s))
|
||||
|
||||
|
||||
class _tzicalvtzcomp(object):
|
||||
def __init__(self, tzoffsetfrom, tzoffsetto, isdst,
|
||||
tzname=None, rrule=None):
|
||||
|
@ -655,6 +672,7 @@ class _tzicalvtzcomp(object):
|
|||
self.tzname = tzname
|
||||
self.rrule = rrule
|
||||
|
||||
|
||||
class _tzicalvtz(datetime.tzinfo):
|
||||
def __init__(self, tzid, comps=[]):
|
||||
self._tzid = tzid
|
||||
|
@ -718,6 +736,7 @@ class _tzicalvtz(datetime.tzinfo):
|
|||
|
||||
__reduce__ = object.__reduce__
|
||||
|
||||
|
||||
class tzical(object):
|
||||
def __init__(self, fileobj):
|
||||
global rrule
|
||||
|
@ -726,7 +745,8 @@ class tzical(object):
|
|||
|
||||
if isinstance(fileobj, string_types):
|
||||
self._s = fileobj
|
||||
fileobj = open(fileobj, 'r') # ical should be encoded in UTF-8 with CRLF
|
||||
# ical should be encoded in UTF-8 with CRLF
|
||||
fileobj = open(fileobj, 'r')
|
||||
elif hasattr(fileobj, "name"):
|
||||
self._s = fileobj.name
|
||||
else:
|
||||
|
@ -754,7 +774,7 @@ class tzical(object):
|
|||
if not s:
|
||||
raise ValueError("empty offset")
|
||||
if s[0] in ('+', '-'):
|
||||
signal = (-1, +1)[s[0]=='+']
|
||||
signal = (-1, +1)[s[0] == '+']
|
||||
s = s[1:]
|
||||
else:
|
||||
signal = +1
|
||||
|
@ -815,7 +835,8 @@ class tzical(object):
|
|||
if not tzid:
|
||||
raise ValueError("mandatory TZID not found")
|
||||
if not comps:
|
||||
raise ValueError("at least one component is needed")
|
||||
raise ValueError(
|
||||
"at least one component is needed")
|
||||
# Process vtimezone
|
||||
self._vtz[tzid] = _tzicalvtz(tzid, comps)
|
||||
invtz = False
|
||||
|
@ -823,9 +844,11 @@ class tzical(object):
|
|||
if not founddtstart:
|
||||
raise ValueError("mandatory DTSTART not found")
|
||||
if tzoffsetfrom is None:
|
||||
raise ValueError("mandatory TZOFFSETFROM not found")
|
||||
raise ValueError(
|
||||
"mandatory TZOFFSETFROM not found")
|
||||
if tzoffsetto is None:
|
||||
raise ValueError("mandatory TZOFFSETFROM not found")
|
||||
raise ValueError(
|
||||
"mandatory TZOFFSETFROM not found")
|
||||
# Process component
|
||||
rr = None
|
||||
if rrulelines:
|
||||
|
@ -848,15 +871,18 @@ class tzical(object):
|
|||
rrulelines.append(line)
|
||||
elif name == "TZOFFSETFROM":
|
||||
if parms:
|
||||
raise ValueError("unsupported %s parm: %s "%(name, parms[0]))
|
||||
raise ValueError(
|
||||
"unsupported %s parm: %s " % (name, parms[0]))
|
||||
tzoffsetfrom = self._parse_offset(value)
|
||||
elif name == "TZOFFSETTO":
|
||||
if parms:
|
||||
raise ValueError("unsupported TZOFFSETTO parm: "+parms[0])
|
||||
raise ValueError(
|
||||
"unsupported TZOFFSETTO parm: "+parms[0])
|
||||
tzoffsetto = self._parse_offset(value)
|
||||
elif name == "TZNAME":
|
||||
if parms:
|
||||
raise ValueError("unsupported TZNAME parm: "+parms[0])
|
||||
raise ValueError(
|
||||
"unsupported TZNAME parm: "+parms[0])
|
||||
tzname = value
|
||||
elif name == "COMMENT":
|
||||
pass
|
||||
|
@ -865,7 +891,8 @@ class tzical(object):
|
|||
else:
|
||||
if name == "TZID":
|
||||
if parms:
|
||||
raise ValueError("unsupported TZID parm: "+parms[0])
|
||||
raise ValueError(
|
||||
"unsupported TZID parm: "+parms[0])
|
||||
tzid = value
|
||||
elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"):
|
||||
pass
|
||||
|
@ -886,6 +913,7 @@ else:
|
|||
TZFILES = []
|
||||
TZPATHS = []
|
||||
|
||||
|
||||
def gettz(name=None):
|
||||
tz = None
|
||||
if not name:
|
||||
|
@ -933,11 +961,11 @@ def gettz(name=None):
|
|||
pass
|
||||
else:
|
||||
tz = None
|
||||
if tzwin:
|
||||
if tzwin is not None:
|
||||
try:
|
||||
tz = tzwin(name)
|
||||
except OSError:
|
||||
pass
|
||||
except WindowsError:
|
||||
tz = None
|
||||
if not tz:
|
||||
from dateutil.zoneinfo import gettz
|
||||
tz = gettz(name)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# This code was originally contributed by Jeffrey Harris.
|
||||
import datetime
|
||||
import struct
|
||||
import winreg
|
||||
|
||||
from six.moves import winreg
|
||||
|
||||
__all__ = ["tzwin", "tzwinlocal"]
|
||||
|
||||
|
@ -12,8 +12,8 @@ TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
|
|||
TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones"
|
||||
TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
|
||||
|
||||
|
||||
def _settzkeyname():
|
||||
global TZKEYNAME
|
||||
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
|
||||
try:
|
||||
winreg.OpenKey(handle, TZKEYNAMENT).Close()
|
||||
|
@ -21,8 +21,10 @@ def _settzkeyname():
|
|||
except WindowsError:
|
||||
TZKEYNAME = TZKEYNAME9X
|
||||
handle.Close()
|
||||
return TZKEYNAME
|
||||
|
||||
TZKEYNAME = _settzkeyname()
|
||||
|
||||
_settzkeyname()
|
||||
|
||||
class tzwinbase(datetime.tzinfo):
|
||||
"""tzinfo class based on win32's timezones available in the registry."""
|
||||
|
@ -61,6 +63,9 @@ class tzwinbase(datetime.tzinfo):
|
|||
return self._display
|
||||
|
||||
def _isdst(self, dt):
|
||||
if not self._dstmonth:
|
||||
# dstmonth == 0 signals the zone has no daylight saving time
|
||||
return False
|
||||
dston = picknthweekday(dt.year, self._dstmonth, self._dstdayofweek,
|
||||
self._dsthour, self._dstminute,
|
||||
self._dstweeknumber)
|
||||
|
@ -78,11 +83,11 @@ class tzwin(tzwinbase):
|
|||
def __init__(self, name):
|
||||
self._name = name
|
||||
|
||||
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
|
||||
tzkey = winreg.OpenKey(handle, "%s\%s" % (TZKEYNAME, name))
|
||||
# multiple contexts only possible in 2.7 and 3.1, we still support 2.6
|
||||
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
||||
with winreg.OpenKey(handle,
|
||||
"%s\%s" % (TZKEYNAME, name)) as tzkey:
|
||||
keydict = valuestodict(tzkey)
|
||||
tzkey.Close()
|
||||
handle.Close()
|
||||
|
||||
self._stdname = keydict["Std"].encode("iso-8859-1")
|
||||
self._dstname = keydict["Dlt"].encode("iso-8859-1")
|
||||
|
@ -94,6 +99,8 @@ class tzwin(tzwinbase):
|
|||
self._stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1
|
||||
self._dstoffset = self._stdoffset-tup[2] # + DaylightBias * -1
|
||||
|
||||
# for the meaning see the win32 TIME_ZONE_INFORMATION structure docs
|
||||
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx
|
||||
(self._stdmonth,
|
||||
self._stddayofweek, # Sunday = 0
|
||||
self._stdweeknumber, # Last = 5
|
||||
|
@ -117,29 +124,25 @@ class tzwinlocal(tzwinbase):
|
|||
|
||||
def __init__(self):
|
||||
|
||||
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
|
||||
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
||||
|
||||
tzlocalkey = winreg.OpenKey(handle, TZLOCALKEYNAME)
|
||||
with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey:
|
||||
keydict = valuestodict(tzlocalkey)
|
||||
tzlocalkey.Close()
|
||||
|
||||
self._stdname = keydict["StandardName"].encode("iso-8859-1")
|
||||
self._dstname = keydict["DaylightName"].encode("iso-8859-1")
|
||||
|
||||
try:
|
||||
tzkey = winreg.OpenKey(handle, "%s\%s"%(TZKEYNAME, self._stdname))
|
||||
with winreg.OpenKey(
|
||||
handle, "%s\%s" % (TZKEYNAME, self._stdname)) as tzkey:
|
||||
_keydict = valuestodict(tzkey)
|
||||
self._display = _keydict["Display"]
|
||||
tzkey.Close()
|
||||
except OSError:
|
||||
self._display = None
|
||||
|
||||
handle.Close()
|
||||
|
||||
self._stdoffset = -keydict["Bias"]-keydict["StandardBias"]
|
||||
self._dstoffset = self._stdoffset-keydict["DaylightBias"]
|
||||
|
||||
|
||||
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
|
||||
tup = struct.unpack("=8h", keydict["StandardStart"])
|
||||
|
||||
|
@ -160,15 +163,17 @@ class tzwinlocal(tzwinbase):
|
|||
def __reduce__(self):
|
||||
return (self.__class__, ())
|
||||
|
||||
|
||||
def picknthweekday(year, month, dayofweek, hour, minute, whichweek):
|
||||
"""dayofweek == 0 means Sunday, whichweek 5 means last instance"""
|
||||
first = datetime.datetime(year, month, 1, hour, minute)
|
||||
weekdayone = first.replace(day=((dayofweek-first.isoweekday())%7+1))
|
||||
weekdayone = first.replace(day=((dayofweek-first.isoweekday()) % 7+1))
|
||||
for n in range(whichweek):
|
||||
dt = weekdayone+(whichweek-n)*ONEWEEK
|
||||
if dt.month == month:
|
||||
return dt
|
||||
|
||||
|
||||
def valuestodict(key):
|
||||
"""Convert a registry key's values to a dictionary."""
|
||||
dict = {}
|
||||
|
|
1
lib/dateutil/zoneinfo/.gitignore
vendored
1
lib/dateutil/zoneinfo/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
*.tar.gz
|
|
@ -1,93 +1,121 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2003-2005 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard Python
|
||||
datetime module.
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
from subprocess import call
|
||||
import warnings
|
||||
import tempfile
|
||||
import shutil
|
||||
import json
|
||||
|
||||
from subprocess import check_call
|
||||
from tarfile import TarFile
|
||||
from pkgutil import get_data
|
||||
from io import BytesIO
|
||||
from contextlib import closing
|
||||
|
||||
from dateutil.tz import tzfile
|
||||
|
||||
__author__ = "Tomi Pieviläinen <tomi.pievilainen@iki.fi>"
|
||||
__license__ = "Simplified BSD"
|
||||
__all__ = ["gettz", "gettz_db_metadata", "rebuild"]
|
||||
|
||||
__all__ = ["setcachesize", "gettz", "rebuild"]
|
||||
_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
|
||||
if not hasattr(TarFile, '__exit__'):
|
||||
def _tar_open(*args, **kwargs):
|
||||
return closing(TarFile.open(*args, **kwargs))
|
||||
|
||||
CACHE = []
|
||||
CACHESIZE = 10
|
||||
|
||||
class tzfile(tzfile):
|
||||
def __reduce__(self):
|
||||
return (gettz, (self._filename,))
|
||||
|
||||
def getzoneinfofile():
|
||||
filenames = sorted(os.listdir(os.path.join(os.path.dirname(__file__))))
|
||||
filenames.reverse()
|
||||
for entry in filenames:
|
||||
if entry.startswith("zoneinfo") and ".tar." in entry:
|
||||
return os.path.join(os.path.dirname(__file__), entry)
|
||||
|
||||
def getzoneinfofile_stream():
|
||||
try:
|
||||
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
|
||||
|
||||
ZONEINFOFILE = getzoneinfofile()
|
||||
|
||||
del getzoneinfofile
|
||||
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:
|
||||
# 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),
|
||||
# filename = zf.name)
|
||||
# for zf in tf.getmembers() if zf.isfile()}
|
||||
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)
|
||||
# deal with links: They'll point to their parent object. Less
|
||||
# waste of memory
|
||||
# links = {zl.name: self.zones[zl.linkname]
|
||||
# for zl in tf.getmembers() if zl.islnk() or zl.issym()}
|
||||
links = dict((zl.name, self.zones[zl.linkname])
|
||||
for zl in tf.getmembers() if
|
||||
zl.islnk() or zl.issym())
|
||||
self.zones.update(links)
|
||||
try:
|
||||
metadata_json = tf.extractfile(tf.getmember(_METADATA_FN))
|
||||
metadata_str = metadata_json.read().decode('UTF-8')
|
||||
self.metadata = json.loads(metadata_str)
|
||||
except KeyError:
|
||||
# no metadata in tar file
|
||||
self.metadata = None
|
||||
else:
|
||||
self.zones = dict()
|
||||
self.metadata = None
|
||||
|
||||
|
||||
# The current API has gettz as a module function, although in fact it taps into
|
||||
# a stateful class. So as a workaround for now, without changing the API, we
|
||||
# will create a new "global" class instance the first time a user requests a
|
||||
# timezone. Ugly, but adheres to the api.
|
||||
#
|
||||
# TODO: deprecate this.
|
||||
_CLASS_ZONE_INSTANCE = list()
|
||||
|
||||
def setcachesize(size):
|
||||
global CACHESIZE, CACHE
|
||||
CACHESIZE = size
|
||||
del CACHE[size:]
|
||||
|
||||
def gettz(name):
|
||||
tzinfo = None
|
||||
if ZONEINFOFILE:
|
||||
for cachedname, tzinfo in CACHE:
|
||||
if cachedname == name:
|
||||
break
|
||||
else:
|
||||
tf = TarFile.open(ZONEINFOFILE)
|
||||
try:
|
||||
zonefile = tf.extractfile(name)
|
||||
except KeyError:
|
||||
tzinfo = None
|
||||
else:
|
||||
tzinfo = tzfile(zonefile)
|
||||
tf.close()
|
||||
CACHE.insert(0, (name, tzinfo))
|
||||
del CACHE[CACHESIZE:]
|
||||
return tzinfo
|
||||
if len(_CLASS_ZONE_INSTANCE) == 0:
|
||||
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
|
||||
return _CLASS_ZONE_INSTANCE[0].zones.get(name)
|
||||
|
||||
def rebuild(filename, tag=None, format="gz"):
|
||||
|
||||
def gettz_db_metadata():
|
||||
""" Get the zonefile metadata
|
||||
|
||||
See `zonefile_metadata`_
|
||||
|
||||
:returns: A dictionary with the database metadata
|
||||
"""
|
||||
if len(_CLASS_ZONE_INSTANCE) == 0:
|
||||
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
|
||||
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.
|
||||
|
||||
"""
|
||||
import tempfile, shutil
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
zonedir = os.path.join(tmpdir, "zoneinfo")
|
||||
moduledir = os.path.dirname(__file__)
|
||||
if tag: tag = "-"+tag
|
||||
targetname = "zoneinfo%s.tar.%s" % (tag, format)
|
||||
try:
|
||||
tf = TarFile.open(filename)
|
||||
# The "backwards" zone file contains links to other files, so must be
|
||||
# processed as last
|
||||
for name in sorted(tf.getnames(),
|
||||
key=lambda k: k != "backward" and k or "z"):
|
||||
if not (name.endswith(".sh") or
|
||||
name.endswith(".tab") or
|
||||
name == "leapseconds"):
|
||||
with _tar_open(filename) as tf:
|
||||
for name in zonegroups:
|
||||
tf.extract(name, tmpdir)
|
||||
filepath = os.path.join(tmpdir, name)
|
||||
filepaths = [os.path.join(tmpdir, n) for n in zonegroups]
|
||||
try:
|
||||
# zic will return errors for nontz files in the package
|
||||
# such as the Makefile or README, so check_call cannot
|
||||
# be used (or at least extra checks would be needed)
|
||||
call(["zic", "-d", zonedir, filepath])
|
||||
check_call(["zic", "-d", zonedir] + filepaths)
|
||||
except OSError as e:
|
||||
if e.errno == 2:
|
||||
logging.error(
|
||||
|
@ -95,15 +123,13 @@ def rebuild(filename, tag=None, format="gz"):
|
|||
"libc-bin or some other package that provides it, "
|
||||
"or it's not in your PATH?")
|
||||
raise
|
||||
tf.close()
|
||||
target = os.path.join(moduledir, targetname)
|
||||
for entry in os.listdir(moduledir):
|
||||
if entry.startswith("zoneinfo") and ".tar." in entry:
|
||||
os.unlink(os.path.join(moduledir, entry))
|
||||
tf = TarFile.open(target, "w:%s" % format)
|
||||
# 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)
|
||||
tf.close()
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
|
|
Loading…
Reference in a new issue