mirror of
https://github.com/SickGear/SickGear.git
synced 2025-01-23 01:43:43 +00:00
Update dateutil library 2.2 to 2.4.2 (a6b8925).
This commit is contained in:
parent
89b36d31f9
commit
dd0b810a1d
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 Six compatibility library 1.5.2 to 1.9.0 (8a545f4)
|
||||||
* Update SimpleJSON library 2.0.9 to 3.7.3 (0bcdf20)
|
* Update SimpleJSON library 2.0.9 to 3.7.3 (0bcdf20)
|
||||||
* Update xmltodict library 0.9.0 to 0.9.2 (579a005)
|
* Update xmltodict library 0.9.0 to 0.9.2 (579a005)
|
||||||
|
* Update dateutil library 2.2 to 2.4.2 (a6b8925)
|
||||||
|
|
||||||
[develop changelog]
|
[develop changelog]
|
||||||
* Update Requests library 2.7.0 (ab1f493) to 2.7.0 (8b5e457)
|
* Update Requests library 2.7.0 (ab1f493) to 2.7.0 (8b5e457)
|
||||||
|
|
|
@ -1,10 +1,2 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
__version__ = "2.4.2"
|
||||||
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"
|
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
|
This module offers a generic easter computing method for any given year, using
|
||||||
|
Western, Orthodox or Julian algorithms.
|
||||||
This module offers extensions to the standard Python
|
|
||||||
datetime module.
|
|
||||||
"""
|
"""
|
||||||
__license__ = "Simplified BSD"
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
@ -14,6 +12,7 @@ EASTER_JULIAN = 1
|
||||||
EASTER_ORTHODOX = 2
|
EASTER_ORTHODOX = 2
|
||||||
EASTER_WESTERN = 3
|
EASTER_WESTERN = 3
|
||||||
|
|
||||||
|
|
||||||
def easter(year, method=EASTER_WESTERN):
|
def easter(year, method=EASTER_WESTERN):
|
||||||
"""
|
"""
|
||||||
This method was ported from the work done by GM Arts,
|
This method was ported from the work done by GM Arts,
|
||||||
|
@ -68,24 +67,23 @@ def easter(year, method=EASTER_WESTERN):
|
||||||
e = 0
|
e = 0
|
||||||
if method < 3:
|
if method < 3:
|
||||||
# Old method
|
# Old method
|
||||||
i = (19*g+15)%30
|
i = (19*g + 15) % 30
|
||||||
j = (y+y//4+i)%7
|
j = (y + y//4 + i) % 7
|
||||||
if method == 2:
|
if method == 2:
|
||||||
# Extra dates to convert Julian to Gregorian date
|
# Extra dates to convert Julian to Gregorian date
|
||||||
e = 10
|
e = 10
|
||||||
if y > 1600:
|
if y > 1600:
|
||||||
e = e+y//100-16-(y//100-16)//4
|
e = e + y//100 - 16 - (y//100 - 16)//4
|
||||||
else:
|
else:
|
||||||
# New method
|
# New method
|
||||||
c = y//100
|
c = y//100
|
||||||
h = (c-c//4-(8*c+13)//25+19*g+15)%30
|
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))
|
i = h - (h//28)*(1 - (h//28)*(29//(h + 1))*((21 - g)//11))
|
||||||
j = (y+y//4+i+2-c+c//4)%7
|
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
|
# 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)
|
# (later dates apply to method 2, although 23 May never actually occurs)
|
||||||
p = i-j+e
|
p = i - j + e
|
||||||
d = 1+(p+27+(p+6)//40)%31
|
d = 1 + (p + 27 + (p + 6)//40) % 31
|
||||||
m = 3+(p+26)//30
|
m = 3 + (p + 26)//30
|
||||||
return datetime.date(int(y), int(m), int(d))
|
return datetime.date(int(y), int(m), int(d))
|
||||||
|
|
||||||
|
|
|
@ -1,50 +1,70 @@
|
||||||
# -*- coding:iso-8859-1 -*-
|
# -*- 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
|
This module attempts to be forgiving with regards to unlikely input formats,
|
||||||
datetime module.
|
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
|
from __future__ import unicode_literals
|
||||||
__license__ = "Simplified BSD"
|
|
||||||
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import string
|
import string
|
||||||
import time
|
import time
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import collections
|
import collections
|
||||||
|
from io import StringIO
|
||||||
try:
|
from calendar import monthrange, isleap
|
||||||
from io import StringIO
|
|
||||||
except ImportError:
|
|
||||||
from io import StringIO
|
|
||||||
|
|
||||||
from six import text_type, binary_type, integer_types
|
from six import text_type, binary_type, integer_types
|
||||||
|
|
||||||
from . import relativedelta
|
from . import relativedelta
|
||||||
from . import tz
|
from . import tz
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["parse", "parserinfo"]
|
__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):
|
class _timelex(object):
|
||||||
|
|
||||||
def __init__(self, instream):
|
def __init__(self, instream):
|
||||||
|
if isinstance(instream, binary_type):
|
||||||
|
instream = instream.decode()
|
||||||
|
|
||||||
if isinstance(instream, text_type):
|
if isinstance(instream, text_type):
|
||||||
instream = StringIO(instream)
|
instream = StringIO(instream)
|
||||||
|
|
||||||
self.instream = instream
|
self.instream = instream
|
||||||
self.wordchars = ('abcdfeghijklmnopqrstuvwxyz'
|
self.wordchars = ('abcdfeghijklmnopqrstuvwxyz'
|
||||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'
|
||||||
|
@ -57,25 +77,47 @@ class _timelex(object):
|
||||||
self.eof = False
|
self.eof = False
|
||||||
|
|
||||||
def get_token(self):
|
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:
|
if self.tokenstack:
|
||||||
return self.tokenstack.pop(0)
|
return self.tokenstack.pop(0)
|
||||||
|
|
||||||
seenletters = False
|
seenletters = False
|
||||||
token = None
|
token = None
|
||||||
state = None
|
state = None
|
||||||
wordchars = self.wordchars
|
wordchars = self.wordchars
|
||||||
numchars = self.numchars
|
numchars = self.numchars
|
||||||
whitespace = self.whitespace
|
whitespace = self.whitespace
|
||||||
|
|
||||||
while not self.eof:
|
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:
|
if self.charstack:
|
||||||
nextchar = self.charstack.pop(0)
|
nextchar = self.charstack.pop(0)
|
||||||
else:
|
else:
|
||||||
nextchar = self.instream.read(1)
|
nextchar = self.instream.read(1)
|
||||||
while nextchar == '\x00':
|
while nextchar == '\x00':
|
||||||
nextchar = self.instream.read(1)
|
nextchar = self.instream.read(1)
|
||||||
|
|
||||||
if not nextchar:
|
if not nextchar:
|
||||||
self.eof = True
|
self.eof = True
|
||||||
break
|
break
|
||||||
elif not state:
|
elif not state:
|
||||||
|
# First character of the token - determines if we're starting
|
||||||
|
# to parse a word, a number or something else.
|
||||||
token = nextchar
|
token = nextchar
|
||||||
if nextchar in wordchars:
|
if nextchar in wordchars:
|
||||||
state = 'a'
|
state = 'a'
|
||||||
|
@ -87,6 +129,8 @@ class _timelex(object):
|
||||||
else:
|
else:
|
||||||
break # emit token
|
break # emit token
|
||||||
elif state == 'a':
|
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
|
seenletters = True
|
||||||
if nextchar in wordchars:
|
if nextchar in wordchars:
|
||||||
token += nextchar
|
token += nextchar
|
||||||
|
@ -97,6 +141,8 @@ class _timelex(object):
|
||||||
self.charstack.append(nextchar)
|
self.charstack.append(nextchar)
|
||||||
break # emit token
|
break # emit token
|
||||||
elif state == '0':
|
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:
|
if nextchar in numchars:
|
||||||
token += nextchar
|
token += nextchar
|
||||||
elif nextchar == '.':
|
elif nextchar == '.':
|
||||||
|
@ -106,6 +152,8 @@ class _timelex(object):
|
||||||
self.charstack.append(nextchar)
|
self.charstack.append(nextchar)
|
||||||
break # emit token
|
break # emit token
|
||||||
elif state == 'a.':
|
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
|
seenletters = True
|
||||||
if nextchar == '.' or nextchar in wordchars:
|
if nextchar == '.' or nextchar in wordchars:
|
||||||
token += nextchar
|
token += nextchar
|
||||||
|
@ -116,6 +164,8 @@ class _timelex(object):
|
||||||
self.charstack.append(nextchar)
|
self.charstack.append(nextchar)
|
||||||
break # emit token
|
break # emit token
|
||||||
elif state == '0.':
|
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:
|
if nextchar == '.' or nextchar in numchars:
|
||||||
token += nextchar
|
token += nextchar
|
||||||
elif nextchar in wordchars and token[-1] == '.':
|
elif nextchar in wordchars and token[-1] == '.':
|
||||||
|
@ -124,14 +174,16 @@ class _timelex(object):
|
||||||
else:
|
else:
|
||||||
self.charstack.append(nextchar)
|
self.charstack.append(nextchar)
|
||||||
break # emit token
|
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('.')
|
l = token.split('.')
|
||||||
token = l[0]
|
token = l[0]
|
||||||
for tok in l[1:]:
|
for tok in l[1:]:
|
||||||
self.tokenstack.append('.')
|
self.tokenstack.append('.')
|
||||||
if tok:
|
if tok:
|
||||||
self.tokenstack.append(tok)
|
self.tokenstack.append(tok)
|
||||||
|
|
||||||
return token
|
return token
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
|
@ -141,6 +193,7 @@ class _timelex(object):
|
||||||
token = self.get_token()
|
token = self.get_token()
|
||||||
if token is None:
|
if token is None:
|
||||||
raise StopIteration
|
raise StopIteration
|
||||||
|
|
||||||
return token
|
return token
|
||||||
|
|
||||||
def next(self):
|
def next(self):
|
||||||
|
@ -170,6 +223,22 @@ class _resultbase(object):
|
||||||
|
|
||||||
|
|
||||||
class parserinfo(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
|
# m from a.m/p.m, t from ISO T separator
|
||||||
JUMP = [" ", ".", ",", ";", "-", "/", "'",
|
JUMP = [" ", ".", ",", ";", "-", "/", "'",
|
||||||
|
@ -204,7 +273,7 @@ class parserinfo(object):
|
||||||
PERTAIN = ["of"]
|
PERTAIN = ["of"]
|
||||||
TZOFFSET = {}
|
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._jump = self._convert(self.JUMP)
|
||||||
self._weekdays = self._convert(self.WEEKDAYS)
|
self._weekdays = self._convert(self.WEEKDAYS)
|
||||||
self._months = self._convert(self.MONTHS)
|
self._months = self._convert(self.MONTHS)
|
||||||
|
@ -215,14 +284,14 @@ class parserinfo(object):
|
||||||
|
|
||||||
self.dayfirst = dayfirst
|
self.dayfirst = dayfirst
|
||||||
self.yearfirst = yearfirst
|
self.yearfirst = yearfirst
|
||||||
|
self.smart_defaults = smart_defaults
|
||||||
|
|
||||||
self._year = time.localtime().tm_year
|
self._year = time.localtime().tm_year
|
||||||
self._century = self._year//100*100
|
self._century = self._year // 100*100
|
||||||
|
|
||||||
def _convert(self, lst):
|
def _convert(self, lst):
|
||||||
dct = {}
|
dct = {}
|
||||||
for i in range(len(lst)):
|
for i, v in enumerate(lst):
|
||||||
v = lst[i]
|
|
||||||
if isinstance(v, tuple):
|
if isinstance(v, tuple):
|
||||||
for v in v:
|
for v in v:
|
||||||
dct[v.lower()] = i
|
dct[v.lower()] = i
|
||||||
|
@ -270,6 +339,7 @@ class parserinfo(object):
|
||||||
def tzoffset(self, name):
|
def tzoffset(self, name):
|
||||||
if name in self._utczone:
|
if name in self._utczone:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
return self.TZOFFSET.get(name)
|
return self.TZOFFSET.get(name)
|
||||||
|
|
||||||
def convertyear(self, year):
|
def convertyear(self, year):
|
||||||
|
@ -286,6 +356,7 @@ class parserinfo(object):
|
||||||
# move to info
|
# move to info
|
||||||
if res.year is not None:
|
if res.year is not None:
|
||||||
res.year = self.convertyear(res.year)
|
res.year = self.convertyear(res.year)
|
||||||
|
|
||||||
if res.tzoffset == 0 and not res.tzname or res.tzname == 'Z':
|
if res.tzoffset == 0 and not res.tzname or res.tzname == 'Z':
|
||||||
res.tzname = "UTC"
|
res.tzname = "UTC"
|
||||||
res.tzoffset = 0
|
res.tzoffset = 0
|
||||||
|
@ -295,37 +366,192 @@ class parserinfo(object):
|
||||||
|
|
||||||
|
|
||||||
class parser(object):
|
class parser(object):
|
||||||
|
|
||||||
def __init__(self, info=None):
|
def __init__(self, info=None):
|
||||||
self.info = info or parserinfo()
|
self.info = info or parserinfo()
|
||||||
|
|
||||||
def parse(self, timestr, default=None,
|
def parse(self, timestr, default=None, ignoretz=False, tzinfos=None,
|
||||||
ignoretz=False, tzinfos=None,
|
smart_defaults=None, date_in_future=False,
|
||||||
**kwargs):
|
fallback_on_invalid_day=None, **kwargs):
|
||||||
if not default:
|
"""
|
||||||
|
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,
|
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)
|
res, skipped_tokens = self._parse(timestr, **kwargs)
|
||||||
|
else:
|
||||||
|
res = self._parse(timestr, **kwargs)
|
||||||
|
|
||||||
if res is None:
|
if res is None:
|
||||||
raise ValueError("unknown string format")
|
raise ValueError("Unknown string format")
|
||||||
|
|
||||||
repl = {}
|
repl = {}
|
||||||
for attr in ["year", "month", "day", "hour",
|
for attr in ("year", "month", "day", "hour",
|
||||||
"minute", "second", "microsecond"]:
|
"minute", "second", "microsecond"):
|
||||||
value = getattr(res, attr)
|
value = getattr(res, attr)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
repl[attr] = value
|
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)
|
ret = default.replace(**repl)
|
||||||
|
|
||||||
if res.weekday is not None and not res.day:
|
if res.weekday is not None and not res.day:
|
||||||
ret = ret+relativedelta.relativedelta(weekday=res.weekday)
|
ret = ret+relativedelta.relativedelta(weekday=res.weekday)
|
||||||
|
|
||||||
if not ignoretz:
|
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):
|
if isinstance(tzinfos, collections.Callable):
|
||||||
tzdata = tzinfos(res.tzname, res.tzoffset)
|
tzdata = tzinfos(res.tzname, res.tzoffset)
|
||||||
else:
|
else:
|
||||||
tzdata = tzinfos.get(res.tzname)
|
tzdata = tzinfos.get(res.tzname)
|
||||||
|
|
||||||
if isinstance(tzdata, datetime.tzinfo):
|
if isinstance(tzdata, datetime.tzinfo):
|
||||||
tzinfo = tzdata
|
tzinfo = tzdata
|
||||||
elif isinstance(tzdata, text_type):
|
elif isinstance(tzdata, text_type):
|
||||||
|
@ -333,8 +559,8 @@ class parser(object):
|
||||||
elif isinstance(tzdata, integer_types):
|
elif isinstance(tzdata, integer_types):
|
||||||
tzinfo = tz.tzoffset(res.tzname, tzdata)
|
tzinfo = tz.tzoffset(res.tzname, tzdata)
|
||||||
else:
|
else:
|
||||||
raise ValueError("offset must be tzinfo subclass, " \
|
raise ValueError("Offset must be tzinfo subclass, "
|
||||||
"tz string, or int offset")
|
"tz string, or int offset.")
|
||||||
ret = ret.replace(tzinfo=tzinfo)
|
ret = ret.replace(tzinfo=tzinfo)
|
||||||
elif res.tzname and res.tzname in time.tzname:
|
elif res.tzname and res.tzname in time.tzname:
|
||||||
ret = ret.replace(tzinfo=tz.tzlocal())
|
ret = ret.replace(tzinfo=tz.tzlocal())
|
||||||
|
@ -343,28 +569,64 @@ class parser(object):
|
||||||
elif res.tzoffset:
|
elif res.tzoffset:
|
||||||
ret = ret.replace(tzinfo=tz.tzoffset(res.tzname, 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
|
return ret, skipped_tokens
|
||||||
|
else:
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
class _result(_resultbase):
|
class _result(_resultbase):
|
||||||
__slots__ = ["year", "month", "day", "weekday",
|
__slots__ = ["year", "month", "day", "weekday",
|
||||||
"hour", "minute", "second", "microsecond",
|
"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:
|
if fuzzy_with_tokens:
|
||||||
fuzzy = True
|
fuzzy = True
|
||||||
|
|
||||||
info = self.info
|
info = self.info
|
||||||
|
|
||||||
if dayfirst is None:
|
if dayfirst is None:
|
||||||
dayfirst = info.dayfirst
|
dayfirst = info.dayfirst
|
||||||
|
|
||||||
if yearfirst is None:
|
if yearfirst is None:
|
||||||
yearfirst = info.yearfirst
|
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
|
# keep up with the last token skipped so we can recombine
|
||||||
# consecutively skipped tokens (-2 for when i begins at 0).
|
# consecutively skipped tokens (-2 for when i begins at 0).
|
||||||
|
@ -372,7 +634,6 @@ class parser(object):
|
||||||
skipped_tokens = list()
|
skipped_tokens = list()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
# year/month/day list
|
# year/month/day list
|
||||||
ymd = []
|
ymd = []
|
||||||
|
|
||||||
|
@ -394,17 +655,21 @@ class parser(object):
|
||||||
# Token is a number
|
# Token is a number
|
||||||
len_li = len(l[i])
|
len_li = len(l[i])
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
if (len(ymd) == 3 and len_li in (2, 4)
|
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))):
|
info.hms(l[i]) is None))):
|
||||||
# 19990101T23[59]
|
# 19990101T23[59]
|
||||||
s = l[i-1]
|
s = l[i-1]
|
||||||
res.hour = int(s[:2])
|
res.hour = int(s[:2])
|
||||||
|
|
||||||
if len_li == 4:
|
if len_li == 4:
|
||||||
res.minute = int(s[2:])
|
res.minute = int(s[2:])
|
||||||
|
|
||||||
elif len_li == 6 or (len_li > 6 and l[i-1].find('.') == 6):
|
elif len_li == 6 or (len_li > 6 and l[i-1].find('.') == 6):
|
||||||
# YYMMDD or HHMMSS[.ss]
|
# YYMMDD or HHMMSS[.ss]
|
||||||
s = l[i-1]
|
s = l[i-1]
|
||||||
|
|
||||||
if not ymd and l[i-1].find('.') == -1:
|
if not ymd and l[i-1].find('.') == -1:
|
||||||
ymd.append(info.convertyear(int(s[:2])))
|
ymd.append(info.convertyear(int(s[:2])))
|
||||||
ymd.append(int(s[2:4]))
|
ymd.append(int(s[2:4]))
|
||||||
|
@ -414,12 +679,14 @@ class parser(object):
|
||||||
res.hour = int(s[:2])
|
res.hour = int(s[:2])
|
||||||
res.minute = int(s[2:4])
|
res.minute = int(s[2:4])
|
||||||
res.second, res.microsecond = _parsems(s[4:])
|
res.second, res.microsecond = _parsems(s[4:])
|
||||||
|
|
||||||
elif len_li == 8:
|
elif len_li == 8:
|
||||||
# YYYYMMDD
|
# YYYYMMDD
|
||||||
s = l[i-1]
|
s = l[i-1]
|
||||||
ymd.append(int(s[:4]))
|
ymd.append(int(s[:4]))
|
||||||
ymd.append(int(s[4:6]))
|
ymd.append(int(s[4:6]))
|
||||||
ymd.append(int(s[6:]))
|
ymd.append(int(s[6:]))
|
||||||
|
|
||||||
elif len_li in (12, 14):
|
elif len_li in (12, 14):
|
||||||
# YYYYMMDDhhmm[ss]
|
# YYYYMMDDhhmm[ss]
|
||||||
s = l[i-1]
|
s = l[i-1]
|
||||||
|
@ -428,30 +695,42 @@ class parser(object):
|
||||||
ymd.append(int(s[6:8]))
|
ymd.append(int(s[6:8]))
|
||||||
res.hour = int(s[8:10])
|
res.hour = int(s[8:10])
|
||||||
res.minute = int(s[10:12])
|
res.minute = int(s[10:12])
|
||||||
|
|
||||||
if len_li == 14:
|
if len_li == 14:
|
||||||
res.second = int(s[12:])
|
res.second = int(s[12:])
|
||||||
|
|
||||||
elif ((i < len_l and info.hms(l[i]) is not None) or
|
elif ((i < len_l and info.hms(l[i]) is not None) or
|
||||||
(i+1 < len_l and l[i] == ' ' and
|
(i+1 < len_l and l[i] == ' ' and
|
||||||
info.hms(l[i+1]) is not None)):
|
info.hms(l[i+1]) is not None)):
|
||||||
|
|
||||||
# HH[ ]h or MM[ ]m or SS[.ss][ ]s
|
# HH[ ]h or MM[ ]m or SS[.ss][ ]s
|
||||||
if l[i] == ' ':
|
if l[i] == ' ':
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
idx = info.hms(l[i])
|
idx = info.hms(l[i])
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if idx == 0:
|
if idx == 0:
|
||||||
res.hour = int(value)
|
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:
|
elif idx == 1:
|
||||||
res.minute = int(value)
|
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:
|
elif idx == 2:
|
||||||
res.second, res.microsecond = \
|
res.second, res.microsecond = \
|
||||||
_parsems(value_repr)
|
_parsems(value_repr)
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
if i >= len_l or idx == 2:
|
if i >= len_l or idx == 2:
|
||||||
break
|
break
|
||||||
|
|
||||||
# 12h00
|
# 12h00
|
||||||
try:
|
try:
|
||||||
value_repr = l[i]
|
value_repr = l[i]
|
||||||
|
@ -461,37 +740,49 @@ class parser(object):
|
||||||
else:
|
else:
|
||||||
i += 1
|
i += 1
|
||||||
idx += 1
|
idx += 1
|
||||||
|
|
||||||
if i < len_l:
|
if i < len_l:
|
||||||
newidx = info.hms(l[i])
|
newidx = info.hms(l[i])
|
||||||
|
|
||||||
if newidx is not None:
|
if newidx is not None:
|
||||||
idx = newidx
|
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
|
# X h MM or X m SS
|
||||||
idx = info.hms(l[i-3]) + 1
|
idx = info.hms(l[i-3]) + 1
|
||||||
|
|
||||||
if idx == 1:
|
if idx == 1:
|
||||||
res.minute = int(value)
|
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:
|
elif idx == 2:
|
||||||
res.second, res.microsecond = \
|
res.second, res.microsecond = \
|
||||||
_parsems(value_repr)
|
_parsems(value_repr)
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
elif i+1 < len_l and l[i] == ':':
|
elif i+1 < len_l and l[i] == ':':
|
||||||
# HH:MM[:SS[.ss]]
|
# HH:MM[:SS[.ss]]
|
||||||
res.hour = int(value)
|
res.hour = int(value)
|
||||||
i += 1
|
i += 1
|
||||||
value = float(l[i])
|
value = float(l[i])
|
||||||
res.minute = int(value)
|
res.minute = int(value)
|
||||||
if value%1:
|
|
||||||
res.second = int(60*(value%1))
|
if value % 1:
|
||||||
|
res.second = int(60*(value % 1))
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
if i < len_l and l[i] == ':':
|
if i < len_l and l[i] == ':':
|
||||||
res.second, res.microsecond = _parsems(l[i+1])
|
res.second, res.microsecond = _parsems(l[i+1])
|
||||||
i += 2
|
i += 2
|
||||||
|
|
||||||
elif i < len_l and l[i] in ('-', '/', '.'):
|
elif i < len_l and l[i] in ('-', '/', '.'):
|
||||||
sep = l[i]
|
sep = l[i]
|
||||||
ymd.append(int(value))
|
ymd.append(int(value))
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
if i < len_l and not info.jump(l[i]):
|
if i < len_l and not info.jump(l[i]):
|
||||||
try:
|
try:
|
||||||
# 01-01[-01]
|
# 01-01[-01]
|
||||||
|
@ -499,45 +790,55 @@ class parser(object):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# 01-Jan[-01]
|
# 01-Jan[-01]
|
||||||
value = info.month(l[i])
|
value = info.month(l[i])
|
||||||
|
|
||||||
if value is not None:
|
if value is not None:
|
||||||
ymd.append(value)
|
ymd.append(value)
|
||||||
assert mstridx == -1
|
assert mstridx == -1
|
||||||
mstridx = len(ymd)-1
|
mstridx = len(ymd)-1
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
if i < len_l and l[i] == sep:
|
if i < len_l and l[i] == sep:
|
||||||
# We have three members
|
# We have three members
|
||||||
i += 1
|
i += 1
|
||||||
value = info.month(l[i])
|
value = info.month(l[i])
|
||||||
|
|
||||||
if value is not None:
|
if value is not None:
|
||||||
ymd.append(value)
|
ymd.append(value)
|
||||||
mstridx = len(ymd)-1
|
mstridx = len(ymd)-1
|
||||||
assert mstridx == -1
|
assert mstridx == -1
|
||||||
else:
|
else:
|
||||||
ymd.append(int(l[i]))
|
ymd.append(int(l[i]))
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
elif i >= len_l or info.jump(l[i]):
|
elif i >= len_l or info.jump(l[i]):
|
||||||
if i+1 < len_l and info.ampm(l[i+1]) is not None:
|
if i+1 < len_l and info.ampm(l[i+1]) is not None:
|
||||||
# 12 am
|
# 12 am
|
||||||
res.hour = int(value)
|
res.hour = int(value)
|
||||||
|
|
||||||
if res.hour < 12 and info.ampm(l[i+1]) == 1:
|
if res.hour < 12 and info.ampm(l[i+1]) == 1:
|
||||||
res.hour += 12
|
res.hour += 12
|
||||||
elif res.hour == 12 and info.ampm(l[i+1]) == 0:
|
elif res.hour == 12 and info.ampm(l[i+1]) == 0:
|
||||||
res.hour = 0
|
res.hour = 0
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
else:
|
else:
|
||||||
# Year, month or day
|
# Year, month or day
|
||||||
ymd.append(int(value))
|
ymd.append(int(value))
|
||||||
i += 1
|
i += 1
|
||||||
elif info.ampm(l[i]) is not None:
|
elif info.ampm(l[i]) is not None:
|
||||||
|
|
||||||
# 12am
|
# 12am
|
||||||
res.hour = int(value)
|
res.hour = int(value)
|
||||||
|
|
||||||
if res.hour < 12 and info.ampm(l[i]) == 1:
|
if res.hour < 12 and info.ampm(l[i]) == 1:
|
||||||
res.hour += 12
|
res.hour += 12
|
||||||
elif res.hour == 12 and info.ampm(l[i]) == 0:
|
elif res.hour == 12 and info.ampm(l[i]) == 0:
|
||||||
res.hour = 0
|
res.hour = 0
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
elif not fuzzy:
|
elif not fuzzy:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
|
@ -557,6 +858,7 @@ class parser(object):
|
||||||
ymd.append(value)
|
ymd.append(value)
|
||||||
assert mstridx == -1
|
assert mstridx == -1
|
||||||
mstridx = len(ymd)-1
|
mstridx = len(ymd)-1
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
if i < len_l:
|
if i < len_l:
|
||||||
if l[i] in ('-', '/'):
|
if l[i] in ('-', '/'):
|
||||||
|
@ -565,11 +867,13 @@ class parser(object):
|
||||||
i += 1
|
i += 1
|
||||||
ymd.append(int(l[i]))
|
ymd.append(int(l[i]))
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
if i < len_l and l[i] == sep:
|
if i < len_l and l[i] == sep:
|
||||||
# Jan-01-99
|
# Jan-01-99
|
||||||
i += 1
|
i += 1
|
||||||
ymd.append(int(l[i]))
|
ymd.append(int(l[i]))
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
elif (i+3 < len_l and l[i] == l[i+2] == ' '
|
elif (i+3 < len_l and l[i] == l[i+2] == ' '
|
||||||
and info.pertain(l[i+1])):
|
and info.pertain(l[i+1])):
|
||||||
# Jan of 01
|
# Jan of 01
|
||||||
|
@ -588,17 +892,47 @@ class parser(object):
|
||||||
# Check am/pm
|
# Check am/pm
|
||||||
value = info.ampm(l[i])
|
value = info.ampm(l[i])
|
||||||
if value is not None:
|
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:
|
if value == 1 and res.hour < 12:
|
||||||
res.hour += 12
|
res.hour += 12
|
||||||
elif value == 0 and res.hour == 12:
|
elif value == 0 and res.hour == 12:
|
||||||
res.hour = 0
|
res.hour = 0
|
||||||
|
|
||||||
|
res.ampm = value
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Check for a timezone name
|
# Check for a timezone name
|
||||||
if (res.hour is not None and len(l[i]) <= 5 and
|
if (res.hour is not None and len(l[i]) <= 5 and
|
||||||
res.tzname is None and res.tzoffset is None 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.tzname = l[i]
|
||||||
res.tzoffset = info.tzoffset(res.tzname)
|
res.tzoffset = info.tzoffset(res.tzname)
|
||||||
i += 1
|
i += 1
|
||||||
|
@ -623,6 +957,7 @@ class parser(object):
|
||||||
signal = (-1, 1)[l[i] == '+']
|
signal = (-1, 1)[l[i] == '+']
|
||||||
i += 1
|
i += 1
|
||||||
len_li = len(l[i])
|
len_li = len(l[i])
|
||||||
|
|
||||||
if len_li == 4:
|
if len_li == 4:
|
||||||
# -0300
|
# -0300
|
||||||
res.tzoffset = int(l[i][:2])*3600+int(l[i][2:])*60
|
res.tzoffset = int(l[i][:2])*3600+int(l[i][2:])*60
|
||||||
|
@ -636,6 +971,7 @@ class parser(object):
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
res.tzoffset *= signal
|
res.tzoffset *= signal
|
||||||
|
|
||||||
# Look for a timezone name between parenthesis
|
# Look for a timezone name between parenthesis
|
||||||
|
@ -672,11 +1008,13 @@ class parser(object):
|
||||||
if mstridx != -1:
|
if mstridx != -1:
|
||||||
res.month = ymd[mstridx]
|
res.month = ymd[mstridx]
|
||||||
del ymd[mstridx]
|
del ymd[mstridx]
|
||||||
|
|
||||||
if len_ymd > 1 or mstridx == -1:
|
if len_ymd > 1 or mstridx == -1:
|
||||||
if ymd[0] > 31:
|
if ymd[0] > 31:
|
||||||
res.year = ymd[0]
|
res.year = ymd[0]
|
||||||
else:
|
else:
|
||||||
res.day = ymd[0]
|
res.day = ymd[0]
|
||||||
|
|
||||||
elif len_ymd == 2:
|
elif len_ymd == 2:
|
||||||
# Two members with numbers
|
# Two members with numbers
|
||||||
if ymd[0] > 31:
|
if ymd[0] > 31:
|
||||||
|
@ -691,7 +1029,8 @@ class parser(object):
|
||||||
else:
|
else:
|
||||||
# 01-13
|
# 01-13
|
||||||
res.month, res.day = ymd
|
res.month, res.day = ymd
|
||||||
if len_ymd == 3:
|
|
||||||
|
elif len_ymd == 3:
|
||||||
# Three members
|
# Three members
|
||||||
if mstridx == 0:
|
if mstridx == 0:
|
||||||
res.month, res.day, res.year = ymd
|
res.month, res.day, res.year = ymd
|
||||||
|
@ -704,6 +1043,7 @@ class parser(object):
|
||||||
# Give precendence to day-first, since
|
# Give precendence to day-first, since
|
||||||
# two-digit years is usually hand-written.
|
# two-digit years is usually hand-written.
|
||||||
res.day, res.month, res.year = ymd
|
res.day, res.month, res.year = ymd
|
||||||
|
|
||||||
elif mstridx == 2:
|
elif mstridx == 2:
|
||||||
# WTF!?
|
# WTF!?
|
||||||
if ymd[1] > 31:
|
if ymd[1] > 31:
|
||||||
|
@ -712,6 +1052,7 @@ class parser(object):
|
||||||
else:
|
else:
|
||||||
# 99-01-Jan
|
# 99-01-Jan
|
||||||
res.year, res.day, res.month = ymd
|
res.year, res.day, res.month = ymd
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if ymd[0] > 31 or \
|
if ymd[0] > 31 or \
|
||||||
(yearfirst and ymd[1] <= 12 and ymd[2] <= 31):
|
(yearfirst and ymd[1] <= 12 and ymd[2] <= 31):
|
||||||
|
@ -732,16 +1073,66 @@ class parser(object):
|
||||||
|
|
||||||
if fuzzy_with_tokens:
|
if fuzzy_with_tokens:
|
||||||
return res, tuple(skipped_tokens)
|
return res, tuple(skipped_tokens)
|
||||||
|
else:
|
||||||
return res, None
|
return res
|
||||||
|
|
||||||
DEFAULTPARSER = parser()
|
DEFAULTPARSER = parser()
|
||||||
|
|
||||||
|
|
||||||
def parse(timestr, parserinfo=None, **kwargs):
|
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
|
Parse a string in one of the supported formats, using the `parserinfo`
|
||||||
# the parser will get both kinds. Internally we use unicode only.
|
parameters.
|
||||||
if isinstance(timestr, binary_type):
|
|
||||||
timestr = timestr.decode()
|
: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:
|
if parserinfo:
|
||||||
return parser(parserinfo).parse(timestr, **kwargs)
|
return parser(parserinfo).parse(timestr, **kwargs)
|
||||||
else:
|
else:
|
||||||
|
@ -789,8 +1180,8 @@ class _tzparser(object):
|
||||||
offattr = "dstoffset"
|
offattr = "dstoffset"
|
||||||
res.dstabbr = "".join(l[i:j])
|
res.dstabbr = "".join(l[i:j])
|
||||||
i = j
|
i = j
|
||||||
if (i < len_l and
|
if (i < len_l and (l[i] in ('+', '-') or l[i][0] in
|
||||||
(l[i] in ('+', '-') or l[i][0] in "0123456789")):
|
"0123456789")):
|
||||||
if l[i] in ('+', '-'):
|
if l[i] in ('+', '-'):
|
||||||
# Yes, that's right. See the TZ variable
|
# Yes, that's right. See the TZ variable
|
||||||
# documentation.
|
# documentation.
|
||||||
|
@ -801,8 +1192,8 @@ class _tzparser(object):
|
||||||
len_li = len(l[i])
|
len_li = len(l[i])
|
||||||
if len_li == 4:
|
if len_li == 4:
|
||||||
# -0300
|
# -0300
|
||||||
setattr(res, offattr,
|
setattr(res, offattr, (int(l[i][:2])*3600 +
|
||||||
(int(l[i][:2])*3600+int(l[i][2:])*60)*signal)
|
int(l[i][2:])*60)*signal)
|
||||||
elif i+1 < len_l and l[i+1] == ':':
|
elif i+1 < len_l and l[i+1] == ':':
|
||||||
# -03:00
|
# -03:00
|
||||||
setattr(res, offattr,
|
setattr(res, offattr,
|
||||||
|
@ -822,7 +1213,8 @@ class _tzparser(object):
|
||||||
|
|
||||||
if i < len_l:
|
if i < len_l:
|
||||||
for j in range(i, len_l):
|
for j in range(i, len_l):
|
||||||
if l[j] == ';': l[j] = ','
|
if l[j] == ';':
|
||||||
|
l[j] = ','
|
||||||
|
|
||||||
assert l[i] == ','
|
assert l[i] == ','
|
||||||
|
|
||||||
|
@ -845,7 +1237,7 @@ class _tzparser(object):
|
||||||
i += 2
|
i += 2
|
||||||
if value:
|
if value:
|
||||||
x.week = value
|
x.week = value
|
||||||
x.weekday = (int(l[i])-1)%7
|
x.weekday = (int(l[i])-1) % 7
|
||||||
else:
|
else:
|
||||||
x.day = int(l[i])
|
x.day = int(l[i])
|
||||||
i += 2
|
i += 2
|
||||||
|
@ -880,7 +1272,7 @@ class _tzparser(object):
|
||||||
i += 1
|
i += 1
|
||||||
assert l[i] in ('-', '.')
|
assert l[i] in ('-', '.')
|
||||||
i += 1
|
i += 1
|
||||||
x.weekday = (int(l[i])-1)%7
|
x.weekday = (int(l[i])-1) % 7
|
||||||
else:
|
else:
|
||||||
# year day (zero based)
|
# year day (zero based)
|
||||||
x.yday = int(l[i])+1
|
x.yday = int(l[i])+1
|
||||||
|
@ -921,6 +1313,8 @@ class _tzparser(object):
|
||||||
|
|
||||||
|
|
||||||
DEFAULTTZPARSER = _tzparser()
|
DEFAULTTZPARSER = _tzparser()
|
||||||
|
|
||||||
|
|
||||||
def _parsetz(tzstr):
|
def _parsetz(tzstr):
|
||||||
return DEFAULTTZPARSER.parse(tzstr)
|
return DEFAULTTZPARSER.parse(tzstr)
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,4 @@
|
||||||
"""
|
# -*- coding: utf-8 -*-
|
||||||
Copyright (c) 2003-2010 Gustavo Niemeyer <gustavo@niemeyer.net>
|
|
||||||
|
|
||||||
This module offers extensions to the standard Python
|
|
||||||
datetime module.
|
|
||||||
"""
|
|
||||||
__license__ = "Simplified BSD"
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import calendar
|
import calendar
|
||||||
|
|
||||||
|
@ -13,6 +6,7 @@ from six import integer_types
|
||||||
|
|
||||||
__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
|
__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
|
||||||
|
|
||||||
|
|
||||||
class weekday(object):
|
class weekday(object):
|
||||||
__slots__ = ["weekday", "n"]
|
__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)])
|
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)])
|
||||||
|
|
||||||
|
|
||||||
class relativedelta(object):
|
class relativedelta(object):
|
||||||
"""
|
"""
|
||||||
The relativedelta type is based on the specification of the excelent
|
The relativedelta type is based on the specification of the excellent
|
||||||
work done by M.-A. Lemburg in his mx.DateTime extension. However,
|
work done by M.-A. Lemburg in his
|
||||||
notice that this type does *NOT* implement the same algorithm as
|
`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.
|
his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.
|
||||||
|
|
||||||
There's two different ways to build a relativedelta instance. The
|
There are two different ways to build a relativedelta instance. The
|
||||||
first one is passing it two date/datetime classes:
|
first one is passing it two date/datetime classes::
|
||||||
|
|
||||||
relativedelta(datetime1, datetime2)
|
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:
|
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:
|
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:
|
weekday:
|
||||||
One of the weekday instances (MO, TU, etc). These instances may
|
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:
|
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.
|
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,
|
original datetime day, if the argument is not present. Then,
|
||||||
subtract from the day until it fits in the year and month
|
subtract from the day until it fits in the year and month
|
||||||
found after their operations.
|
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
|
that the 'weeks' argument is multiplied by 7 and added to
|
||||||
'days'.
|
'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.
|
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
|
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
|
weekday (0-6, 0=Mon), and nth is the number of weeks to add
|
||||||
forward or backward, depending on its signal. Notice that if
|
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,
|
yearday=None, nlyearday=None,
|
||||||
hour=None, minute=None, second=None, microsecond=None):
|
hour=None, minute=None, second=None, microsecond=None):
|
||||||
if dt1 and dt2:
|
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")
|
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):
|
if not isinstance(dt1, datetime.datetime):
|
||||||
dt1 = datetime.datetime.fromordinal(dt1.toordinal())
|
dt1 = datetime.datetime.fromordinal(dt1.toordinal())
|
||||||
elif not isinstance(dt2, datetime.datetime):
|
elif not isinstance(dt2, datetime.datetime):
|
||||||
|
@ -158,7 +167,7 @@ Here is the behavior of operations with relativedelta:
|
||||||
else:
|
else:
|
||||||
self.years = years
|
self.years = years
|
||||||
self.months = months
|
self.months = months
|
||||||
self.days = days+weeks*7
|
self.days = days + weeks * 7
|
||||||
self.leapdays = leapdays
|
self.leapdays = leapdays
|
||||||
self.hours = hours
|
self.hours = hours
|
||||||
self.minutes = minutes
|
self.minutes = minutes
|
||||||
|
@ -185,7 +194,8 @@ Here is the behavior of operations with relativedelta:
|
||||||
if yearday > 59:
|
if yearday > 59:
|
||||||
self.leapdays = -1
|
self.leapdays = -1
|
||||||
if yday:
|
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):
|
for idx, ydays in enumerate(ydayidx):
|
||||||
if yday <= ydays:
|
if yday <= ydays:
|
||||||
self.month = idx+1
|
self.month = idx+1
|
||||||
|
@ -225,13 +235,20 @@ Here is the behavior of operations with relativedelta:
|
||||||
div, mod = divmod(self.months*s, 12)
|
div, mod = divmod(self.months*s, 12)
|
||||||
self.months = mod*s
|
self.months = mod*s
|
||||||
self.years += div*s
|
self.years += div*s
|
||||||
if (self.hours or self.minutes or self.seconds or self.microseconds or
|
if (self.hours or self.minutes or self.seconds or self.microseconds
|
||||||
self.hour is not None or self.minute is not None or
|
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.second is not None or self.microsecond is not None):
|
||||||
self._has_time = 1
|
self._has_time = 1
|
||||||
else:
|
else:
|
||||||
self._has_time = 0
|
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):
|
def _set_months(self, months):
|
||||||
self.months = months
|
self.months = months
|
||||||
if abs(self.months) > 11:
|
if abs(self.months) > 11:
|
||||||
|
@ -244,13 +261,14 @@ Here is the behavior of operations with relativedelta:
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
if isinstance(other, relativedelta):
|
if isinstance(other, relativedelta):
|
||||||
return relativedelta(years=other.years+self.years,
|
return self.__class__(years=other.years+self.years,
|
||||||
months=other.months+self.months,
|
months=other.months+self.months,
|
||||||
days=other.days+self.days,
|
days=other.days+self.days,
|
||||||
hours=other.hours+self.hours,
|
hours=other.hours+self.hours,
|
||||||
minutes=other.minutes+self.minutes,
|
minutes=other.minutes+self.minutes,
|
||||||
seconds=other.seconds+self.seconds,
|
seconds=other.seconds+self.seconds,
|
||||||
microseconds=other.microseconds+self.microseconds,
|
microseconds=(other.microseconds +
|
||||||
|
self.microseconds),
|
||||||
leapdays=other.leapdays or self.leapdays,
|
leapdays=other.leapdays or self.leapdays,
|
||||||
year=other.year or self.year,
|
year=other.year or self.year,
|
||||||
month=other.month or self.month,
|
month=other.month or self.month,
|
||||||
|
@ -259,7 +277,8 @@ Here is the behavior of operations with relativedelta:
|
||||||
hour=other.hour or self.hour,
|
hour=other.hour or self.hour,
|
||||||
minute=other.minute or self.minute,
|
minute=other.minute or self.minute,
|
||||||
second=other.second or self.second,
|
second=other.second or self.second,
|
||||||
microsecond=other.microsecond or self.microsecond)
|
microsecond=(other.microsecond or
|
||||||
|
self.microsecond))
|
||||||
if not isinstance(other, datetime.date):
|
if not isinstance(other, datetime.date):
|
||||||
raise TypeError("unsupported type for add operation")
|
raise TypeError("unsupported type for add operation")
|
||||||
elif self._has_time and not isinstance(other, datetime.datetime):
|
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
|
weekday, nth = self.weekday.weekday, self.weekday.n or 1
|
||||||
jumpdays = (abs(nth)-1)*7
|
jumpdays = (abs(nth)-1)*7
|
||||||
if nth > 0:
|
if nth > 0:
|
||||||
jumpdays += (7-ret.weekday()+weekday)%7
|
jumpdays += (7-ret.weekday()+weekday) % 7
|
||||||
else:
|
else:
|
||||||
jumpdays += (ret.weekday()-weekday)%7
|
jumpdays += (ret.weekday()-weekday) % 7
|
||||||
jumpdays *= -1
|
jumpdays *= -1
|
||||||
ret += datetime.timedelta(days=jumpdays)
|
ret += datetime.timedelta(days=jumpdays)
|
||||||
return ret
|
return ret
|
||||||
|
@ -311,7 +330,7 @@ Here is the behavior of operations with relativedelta:
|
||||||
def __sub__(self, other):
|
def __sub__(self, other):
|
||||||
if not isinstance(other, relativedelta):
|
if not isinstance(other, relativedelta):
|
||||||
raise TypeError("unsupported type for sub operation")
|
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,
|
months=self.months-other.months,
|
||||||
days=self.days-other.days,
|
days=self.days-other.days,
|
||||||
hours=self.hours-other.hours,
|
hours=self.hours-other.hours,
|
||||||
|
@ -329,7 +348,7 @@ Here is the behavior of operations with relativedelta:
|
||||||
microsecond=self.microsecond or other.microsecond)
|
microsecond=self.microsecond or other.microsecond)
|
||||||
|
|
||||||
def __neg__(self):
|
def __neg__(self):
|
||||||
return relativedelta(years=-self.years,
|
return self.__class__(years=-self.years,
|
||||||
months=-self.months,
|
months=-self.months,
|
||||||
days=-self.days,
|
days=-self.days,
|
||||||
hours=-self.hours,
|
hours=-self.hours,
|
||||||
|
@ -363,10 +382,12 @@ Here is the behavior of operations with relativedelta:
|
||||||
self.minute is None and
|
self.minute is None and
|
||||||
self.second is None and
|
self.second is None and
|
||||||
self.microsecond is None)
|
self.microsecond is None)
|
||||||
|
# Compatibility with Python 2.x
|
||||||
|
__nonzero__ = __bool__
|
||||||
|
|
||||||
def __mul__(self, other):
|
def __mul__(self, other):
|
||||||
f = float(other)
|
f = float(other)
|
||||||
return relativedelta(years=int(self.years*f),
|
return self.__class__(years=int(self.years*f),
|
||||||
months=int(self.months*f),
|
months=int(self.months*f),
|
||||||
days=int(self.days*f),
|
days=int(self.days*f),
|
||||||
hours=int(self.hours*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 timezone implementations subclassing the abstract
|
||||||
|
:py:`datetime.tzinfo` type. There are classes to handle tzfile format files
|
||||||
This module offers extensions to the standard Python
|
(usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`, etc), TZ
|
||||||
datetime module.
|
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 datetime
|
||||||
import struct
|
import struct
|
||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from six import string_types, PY3
|
||||||
|
|
||||||
|
try:
|
||||||
|
from dateutil.tzwin import tzwin, tzwinlocal
|
||||||
|
except ImportError:
|
||||||
|
tzwin = tzwinlocal = None
|
||||||
|
|
||||||
relativedelta = None
|
relativedelta = None
|
||||||
parser = None
|
parser = None
|
||||||
rrule = None
|
rrule = None
|
||||||
|
@ -21,27 +27,26 @@ rrule = None
|
||||||
__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
|
__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
|
||||||
"tzstr", "tzical", "tzwin", "tzwinlocal", "gettz"]
|
"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
|
"""Change unicode output into bytestrings in Python 2
|
||||||
|
|
||||||
tzname() API changed in Python 3. It used to return bytes, but was changed
|
tzname() API changed in Python 3. It used to return bytes, but was changed
|
||||||
to unicode strings
|
to unicode strings
|
||||||
"""
|
"""
|
||||||
def inner_func(*args, **kwargs):
|
def adjust_encoding(*args, **kwargs):
|
||||||
if PY3:
|
name = namefunc(*args, **kwargs)
|
||||||
return myfunc(*args, **kwargs)
|
if name is not None and not PY3:
|
||||||
else:
|
name = name.encode()
|
||||||
return myfunc(*args, **kwargs).encode()
|
|
||||||
return inner_func
|
return name
|
||||||
|
|
||||||
|
return adjust_encoding
|
||||||
|
|
||||||
ZERO = datetime.timedelta(0)
|
ZERO = datetime.timedelta(0)
|
||||||
EPOCHORDINAL = datetime.datetime.utcfromtimestamp(0).toordinal()
|
EPOCHORDINAL = datetime.datetime.utcfromtimestamp(0).toordinal()
|
||||||
|
|
||||||
|
|
||||||
class tzutc(datetime.tzinfo):
|
class tzutc(datetime.tzinfo):
|
||||||
|
|
||||||
def utcoffset(self, dt):
|
def utcoffset(self, dt):
|
||||||
|
@ -66,6 +71,7 @@ class tzutc(datetime.tzinfo):
|
||||||
|
|
||||||
__reduce__ = object.__reduce__
|
__reduce__ = object.__reduce__
|
||||||
|
|
||||||
|
|
||||||
class tzoffset(datetime.tzinfo):
|
class tzoffset(datetime.tzinfo):
|
||||||
|
|
||||||
def __init__(self, name, offset):
|
def __init__(self, name, offset):
|
||||||
|
@ -96,6 +102,7 @@ class tzoffset(datetime.tzinfo):
|
||||||
|
|
||||||
__reduce__ = object.__reduce__
|
__reduce__ = object.__reduce__
|
||||||
|
|
||||||
|
|
||||||
class tzlocal(datetime.tzinfo):
|
class tzlocal(datetime.tzinfo):
|
||||||
|
|
||||||
_std_offset = datetime.timedelta(seconds=-time.timezone)
|
_std_offset = datetime.timedelta(seconds=-time.timezone)
|
||||||
|
@ -130,18 +137,18 @@ class tzlocal(datetime.tzinfo):
|
||||||
#
|
#
|
||||||
# The code above yields the following result:
|
# The code above yields the following result:
|
||||||
#
|
#
|
||||||
#>>> import tz, datetime
|
# >>> import tz, datetime
|
||||||
#>>> t = tz.tzlocal()
|
# >>> t = tz.tzlocal()
|
||||||
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
# >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||||
#'BRDT'
|
# 'BRDT'
|
||||||
#>>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()
|
# >>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()
|
||||||
#'BRST'
|
# 'BRST'
|
||||||
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
# >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||||
#'BRST'
|
# 'BRST'
|
||||||
#>>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()
|
# >>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()
|
||||||
#'BRDT'
|
# 'BRDT'
|
||||||
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
# >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||||
#'BRDT'
|
# 'BRDT'
|
||||||
#
|
#
|
||||||
# Here is a more stable implementation:
|
# Here is a more stable implementation:
|
||||||
#
|
#
|
||||||
|
@ -166,6 +173,7 @@ class tzlocal(datetime.tzinfo):
|
||||||
|
|
||||||
__reduce__ = object.__reduce__
|
__reduce__ = object.__reduce__
|
||||||
|
|
||||||
|
|
||||||
class _ttinfo(object):
|
class _ttinfo(object):
|
||||||
__slots__ = ["offset", "delta", "isdst", "abbr", "isstd", "isgmt"]
|
__slots__ = ["offset", "delta", "isdst", "abbr", "isstd", "isgmt"]
|
||||||
|
|
||||||
|
@ -205,15 +213,20 @@ class _ttinfo(object):
|
||||||
if name in state:
|
if name in state:
|
||||||
setattr(self, name, state[name])
|
setattr(self, name, state[name])
|
||||||
|
|
||||||
|
|
||||||
class tzfile(datetime.tzinfo):
|
class tzfile(datetime.tzinfo):
|
||||||
|
|
||||||
# http://www.twinsun.com/tz/tz-link.htm
|
# http://www.twinsun.com/tz/tz-link.htm
|
||||||
# ftp://ftp.iana.org/tz/tz*.tar.gz
|
# 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):
|
if isinstance(fileobj, string_types):
|
||||||
self._filename = fileobj
|
self._filename = fileobj
|
||||||
fileobj = open(fileobj, 'rb')
|
fileobj = open(fileobj, 'rb')
|
||||||
|
file_opened_here = True
|
||||||
|
elif filename is not None:
|
||||||
|
self._filename = filename
|
||||||
elif hasattr(fileobj, "name"):
|
elif hasattr(fileobj, "name"):
|
||||||
self._filename = fileobj.name
|
self._filename = fileobj.name
|
||||||
else:
|
else:
|
||||||
|
@ -228,7 +241,7 @@ class tzfile(datetime.tzinfo):
|
||||||
# six four-byte values of type long, written in a
|
# six four-byte values of type long, written in a
|
||||||
# ``standard'' byte order (the high-order byte
|
# ``standard'' byte order (the high-order byte
|
||||||
# of the value is written first).
|
# of the value is written first).
|
||||||
|
try:
|
||||||
if fileobj.read(4).decode() != "TZif":
|
if fileobj.read(4).decode() != "TZif":
|
||||||
raise ValueError("magic not found")
|
raise ValueError("magic not found")
|
||||||
|
|
||||||
|
@ -313,9 +326,9 @@ class tzfile(datetime.tzinfo):
|
||||||
# by time.
|
# by time.
|
||||||
|
|
||||||
# Not used, for now
|
# Not used, for now
|
||||||
if leapcnt:
|
# if leapcnt:
|
||||||
leap = struct.unpack(">%dl" % (leapcnt*2),
|
# leap = struct.unpack(">%dl" % (leapcnt*2),
|
||||||
fileobj.read(leapcnt*8))
|
# fileobj.read(leapcnt*8))
|
||||||
|
|
||||||
# Then there are tzh_ttisstdcnt standard/wall
|
# Then there are tzh_ttisstdcnt standard/wall
|
||||||
# indicators, each stored as a one-byte value;
|
# indicators, each stored as a one-byte value;
|
||||||
|
@ -342,6 +355,9 @@ class tzfile(datetime.tzinfo):
|
||||||
fileobj.read(ttisgmtcnt))
|
fileobj.read(ttisgmtcnt))
|
||||||
|
|
||||||
# ** Everything has been read **
|
# ** Everything has been read **
|
||||||
|
finally:
|
||||||
|
if file_opened_here:
|
||||||
|
fileobj.close()
|
||||||
|
|
||||||
# Build ttinfo list
|
# Build ttinfo list
|
||||||
self._ttinfo_list = []
|
self._ttinfo_list = []
|
||||||
|
@ -481,7 +497,6 @@ class tzfile(datetime.tzinfo):
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not self.__eq__(other)
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "%s(%s)" % (self.__class__.__name__, repr(self._filename))
|
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__)
|
raise ValueError("Unpickable %s class" % self.__class__.__name__)
|
||||||
return (self.__class__, (self._filename,))
|
return (self.__class__, (self._filename,))
|
||||||
|
|
||||||
class tzrange(datetime.tzinfo):
|
|
||||||
|
|
||||||
|
class tzrange(datetime.tzinfo):
|
||||||
def __init__(self, stdabbr, stdoffset=None,
|
def __init__(self, stdabbr, stdoffset=None,
|
||||||
dstabbr=None, dstoffset=None,
|
dstabbr=None, dstoffset=None,
|
||||||
start=None, end=None):
|
start=None, end=None):
|
||||||
|
@ -570,6 +585,7 @@ class tzrange(datetime.tzinfo):
|
||||||
|
|
||||||
__reduce__ = object.__reduce__
|
__reduce__ = object.__reduce__
|
||||||
|
|
||||||
|
|
||||||
class tzstr(tzrange):
|
class tzstr(tzrange):
|
||||||
|
|
||||||
def __init__(self, s):
|
def __init__(self, s):
|
||||||
|
@ -645,6 +661,7 @@ class tzstr(tzrange):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "%s(%s)" % (self.__class__.__name__, repr(self._s))
|
return "%s(%s)" % (self.__class__.__name__, repr(self._s))
|
||||||
|
|
||||||
|
|
||||||
class _tzicalvtzcomp(object):
|
class _tzicalvtzcomp(object):
|
||||||
def __init__(self, tzoffsetfrom, tzoffsetto, isdst,
|
def __init__(self, tzoffsetfrom, tzoffsetto, isdst,
|
||||||
tzname=None, rrule=None):
|
tzname=None, rrule=None):
|
||||||
|
@ -655,6 +672,7 @@ class _tzicalvtzcomp(object):
|
||||||
self.tzname = tzname
|
self.tzname = tzname
|
||||||
self.rrule = rrule
|
self.rrule = rrule
|
||||||
|
|
||||||
|
|
||||||
class _tzicalvtz(datetime.tzinfo):
|
class _tzicalvtz(datetime.tzinfo):
|
||||||
def __init__(self, tzid, comps=[]):
|
def __init__(self, tzid, comps=[]):
|
||||||
self._tzid = tzid
|
self._tzid = tzid
|
||||||
|
@ -718,6 +736,7 @@ class _tzicalvtz(datetime.tzinfo):
|
||||||
|
|
||||||
__reduce__ = object.__reduce__
|
__reduce__ = object.__reduce__
|
||||||
|
|
||||||
|
|
||||||
class tzical(object):
|
class tzical(object):
|
||||||
def __init__(self, fileobj):
|
def __init__(self, fileobj):
|
||||||
global rrule
|
global rrule
|
||||||
|
@ -726,7 +745,8 @@ class tzical(object):
|
||||||
|
|
||||||
if isinstance(fileobj, string_types):
|
if isinstance(fileobj, string_types):
|
||||||
self._s = fileobj
|
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"):
|
elif hasattr(fileobj, "name"):
|
||||||
self._s = fileobj.name
|
self._s = fileobj.name
|
||||||
else:
|
else:
|
||||||
|
@ -754,7 +774,7 @@ class tzical(object):
|
||||||
if not s:
|
if not s:
|
||||||
raise ValueError("empty offset")
|
raise ValueError("empty offset")
|
||||||
if s[0] in ('+', '-'):
|
if s[0] in ('+', '-'):
|
||||||
signal = (-1, +1)[s[0]=='+']
|
signal = (-1, +1)[s[0] == '+']
|
||||||
s = s[1:]
|
s = s[1:]
|
||||||
else:
|
else:
|
||||||
signal = +1
|
signal = +1
|
||||||
|
@ -815,7 +835,8 @@ class tzical(object):
|
||||||
if not tzid:
|
if not tzid:
|
||||||
raise ValueError("mandatory TZID not found")
|
raise ValueError("mandatory TZID not found")
|
||||||
if not comps:
|
if not comps:
|
||||||
raise ValueError("at least one component is needed")
|
raise ValueError(
|
||||||
|
"at least one component is needed")
|
||||||
# Process vtimezone
|
# Process vtimezone
|
||||||
self._vtz[tzid] = _tzicalvtz(tzid, comps)
|
self._vtz[tzid] = _tzicalvtz(tzid, comps)
|
||||||
invtz = False
|
invtz = False
|
||||||
|
@ -823,9 +844,11 @@ class tzical(object):
|
||||||
if not founddtstart:
|
if not founddtstart:
|
||||||
raise ValueError("mandatory DTSTART not found")
|
raise ValueError("mandatory DTSTART not found")
|
||||||
if tzoffsetfrom is None:
|
if tzoffsetfrom is None:
|
||||||
raise ValueError("mandatory TZOFFSETFROM not found")
|
raise ValueError(
|
||||||
|
"mandatory TZOFFSETFROM not found")
|
||||||
if tzoffsetto is None:
|
if tzoffsetto is None:
|
||||||
raise ValueError("mandatory TZOFFSETFROM not found")
|
raise ValueError(
|
||||||
|
"mandatory TZOFFSETFROM not found")
|
||||||
# Process component
|
# Process component
|
||||||
rr = None
|
rr = None
|
||||||
if rrulelines:
|
if rrulelines:
|
||||||
|
@ -848,15 +871,18 @@ class tzical(object):
|
||||||
rrulelines.append(line)
|
rrulelines.append(line)
|
||||||
elif name == "TZOFFSETFROM":
|
elif name == "TZOFFSETFROM":
|
||||||
if parms:
|
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)
|
tzoffsetfrom = self._parse_offset(value)
|
||||||
elif name == "TZOFFSETTO":
|
elif name == "TZOFFSETTO":
|
||||||
if parms:
|
if parms:
|
||||||
raise ValueError("unsupported TZOFFSETTO parm: "+parms[0])
|
raise ValueError(
|
||||||
|
"unsupported TZOFFSETTO parm: "+parms[0])
|
||||||
tzoffsetto = self._parse_offset(value)
|
tzoffsetto = self._parse_offset(value)
|
||||||
elif name == "TZNAME":
|
elif name == "TZNAME":
|
||||||
if parms:
|
if parms:
|
||||||
raise ValueError("unsupported TZNAME parm: "+parms[0])
|
raise ValueError(
|
||||||
|
"unsupported TZNAME parm: "+parms[0])
|
||||||
tzname = value
|
tzname = value
|
||||||
elif name == "COMMENT":
|
elif name == "COMMENT":
|
||||||
pass
|
pass
|
||||||
|
@ -865,7 +891,8 @@ class tzical(object):
|
||||||
else:
|
else:
|
||||||
if name == "TZID":
|
if name == "TZID":
|
||||||
if parms:
|
if parms:
|
||||||
raise ValueError("unsupported TZID parm: "+parms[0])
|
raise ValueError(
|
||||||
|
"unsupported TZID parm: "+parms[0])
|
||||||
tzid = value
|
tzid = value
|
||||||
elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"):
|
elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"):
|
||||||
pass
|
pass
|
||||||
|
@ -886,6 +913,7 @@ else:
|
||||||
TZFILES = []
|
TZFILES = []
|
||||||
TZPATHS = []
|
TZPATHS = []
|
||||||
|
|
||||||
|
|
||||||
def gettz(name=None):
|
def gettz(name=None):
|
||||||
tz = None
|
tz = None
|
||||||
if not name:
|
if not name:
|
||||||
|
@ -933,11 +961,11 @@ def gettz(name=None):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
tz = None
|
tz = None
|
||||||
if tzwin:
|
if tzwin is not None:
|
||||||
try:
|
try:
|
||||||
tz = tzwin(name)
|
tz = tzwin(name)
|
||||||
except OSError:
|
except WindowsError:
|
||||||
pass
|
tz = None
|
||||||
if not tz:
|
if not tz:
|
||||||
from dateutil.zoneinfo import gettz
|
from dateutil.zoneinfo import gettz
|
||||||
tz = gettz(name)
|
tz = gettz(name)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# This code was originally contributed by Jeffrey Harris.
|
# This code was originally contributed by Jeffrey Harris.
|
||||||
import datetime
|
import datetime
|
||||||
import struct
|
import struct
|
||||||
import winreg
|
|
||||||
|
|
||||||
|
from six.moves import winreg
|
||||||
|
|
||||||
__all__ = ["tzwin", "tzwinlocal"]
|
__all__ = ["tzwin", "tzwinlocal"]
|
||||||
|
|
||||||
|
@ -12,8 +12,8 @@ TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
|
||||||
TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones"
|
TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones"
|
||||||
TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
|
TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
|
||||||
|
|
||||||
|
|
||||||
def _settzkeyname():
|
def _settzkeyname():
|
||||||
global TZKEYNAME
|
|
||||||
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
|
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
|
||||||
try:
|
try:
|
||||||
winreg.OpenKey(handle, TZKEYNAMENT).Close()
|
winreg.OpenKey(handle, TZKEYNAMENT).Close()
|
||||||
|
@ -21,8 +21,10 @@ def _settzkeyname():
|
||||||
except WindowsError:
|
except WindowsError:
|
||||||
TZKEYNAME = TZKEYNAME9X
|
TZKEYNAME = TZKEYNAME9X
|
||||||
handle.Close()
|
handle.Close()
|
||||||
|
return TZKEYNAME
|
||||||
|
|
||||||
|
TZKEYNAME = _settzkeyname()
|
||||||
|
|
||||||
_settzkeyname()
|
|
||||||
|
|
||||||
class tzwinbase(datetime.tzinfo):
|
class tzwinbase(datetime.tzinfo):
|
||||||
"""tzinfo class based on win32's timezones available in the registry."""
|
"""tzinfo class based on win32's timezones available in the registry."""
|
||||||
|
@ -61,6 +63,9 @@ class tzwinbase(datetime.tzinfo):
|
||||||
return self._display
|
return self._display
|
||||||
|
|
||||||
def _isdst(self, dt):
|
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,
|
dston = picknthweekday(dt.year, self._dstmonth, self._dstdayofweek,
|
||||||
self._dsthour, self._dstminute,
|
self._dsthour, self._dstminute,
|
||||||
self._dstweeknumber)
|
self._dstweeknumber)
|
||||||
|
@ -78,11 +83,11 @@ class tzwin(tzwinbase):
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self._name = name
|
self._name = name
|
||||||
|
|
||||||
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
|
# multiple contexts only possible in 2.7 and 3.1, we still support 2.6
|
||||||
tzkey = winreg.OpenKey(handle, "%s\%s" % (TZKEYNAME, name))
|
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
||||||
|
with winreg.OpenKey(handle,
|
||||||
|
"%s\%s" % (TZKEYNAME, name)) as tzkey:
|
||||||
keydict = valuestodict(tzkey)
|
keydict = valuestodict(tzkey)
|
||||||
tzkey.Close()
|
|
||||||
handle.Close()
|
|
||||||
|
|
||||||
self._stdname = keydict["Std"].encode("iso-8859-1")
|
self._stdname = keydict["Std"].encode("iso-8859-1")
|
||||||
self._dstname = keydict["Dlt"].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._stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1
|
||||||
self._dstoffset = self._stdoffset-tup[2] # + DaylightBias * -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._stdmonth,
|
||||||
self._stddayofweek, # Sunday = 0
|
self._stddayofweek, # Sunday = 0
|
||||||
self._stdweeknumber, # Last = 5
|
self._stdweeknumber, # Last = 5
|
||||||
|
@ -117,29 +124,25 @@ class tzwinlocal(tzwinbase):
|
||||||
|
|
||||||
def __init__(self):
|
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)
|
keydict = valuestodict(tzlocalkey)
|
||||||
tzlocalkey.Close()
|
|
||||||
|
|
||||||
self._stdname = keydict["StandardName"].encode("iso-8859-1")
|
self._stdname = keydict["StandardName"].encode("iso-8859-1")
|
||||||
self._dstname = keydict["DaylightName"].encode("iso-8859-1")
|
self._dstname = keydict["DaylightName"].encode("iso-8859-1")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tzkey = winreg.OpenKey(handle, "%s\%s"%(TZKEYNAME, self._stdname))
|
with winreg.OpenKey(
|
||||||
|
handle, "%s\%s" % (TZKEYNAME, self._stdname)) as tzkey:
|
||||||
_keydict = valuestodict(tzkey)
|
_keydict = valuestodict(tzkey)
|
||||||
self._display = _keydict["Display"]
|
self._display = _keydict["Display"]
|
||||||
tzkey.Close()
|
|
||||||
except OSError:
|
except OSError:
|
||||||
self._display = None
|
self._display = None
|
||||||
|
|
||||||
handle.Close()
|
|
||||||
|
|
||||||
self._stdoffset = -keydict["Bias"]-keydict["StandardBias"]
|
self._stdoffset = -keydict["Bias"]-keydict["StandardBias"]
|
||||||
self._dstoffset = self._stdoffset-keydict["DaylightBias"]
|
self._dstoffset = self._stdoffset-keydict["DaylightBias"]
|
||||||
|
|
||||||
|
|
||||||
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
|
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
|
||||||
tup = struct.unpack("=8h", keydict["StandardStart"])
|
tup = struct.unpack("=8h", keydict["StandardStart"])
|
||||||
|
|
||||||
|
@ -160,15 +163,17 @@ class tzwinlocal(tzwinbase):
|
||||||
def __reduce__(self):
|
def __reduce__(self):
|
||||||
return (self.__class__, ())
|
return (self.__class__, ())
|
||||||
|
|
||||||
|
|
||||||
def picknthweekday(year, month, dayofweek, hour, minute, whichweek):
|
def picknthweekday(year, month, dayofweek, hour, minute, whichweek):
|
||||||
"""dayofweek == 0 means Sunday, whichweek 5 means last instance"""
|
"""dayofweek == 0 means Sunday, whichweek 5 means last instance"""
|
||||||
first = datetime.datetime(year, month, 1, hour, minute)
|
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):
|
for n in range(whichweek):
|
||||||
dt = weekdayone+(whichweek-n)*ONEWEEK
|
dt = weekdayone+(whichweek-n)*ONEWEEK
|
||||||
if dt.month == month:
|
if dt.month == month:
|
||||||
return dt
|
return dt
|
||||||
|
|
||||||
|
|
||||||
def valuestodict(key):
|
def valuestodict(key):
|
||||||
"""Convert a registry key's values to a dictionary."""
|
"""Convert a registry key's values to a dictionary."""
|
||||||
dict = {}
|
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 -*-
|
# -*- 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 logging
|
||||||
import os
|
import os
|
||||||
from subprocess import call
|
import warnings
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
|
import json
|
||||||
|
|
||||||
|
from subprocess import check_call
|
||||||
from tarfile import TarFile
|
from tarfile import TarFile
|
||||||
|
from pkgutil import get_data
|
||||||
|
from io import BytesIO
|
||||||
|
from contextlib import closing
|
||||||
|
|
||||||
from dateutil.tz import tzfile
|
from dateutil.tz import tzfile
|
||||||
|
|
||||||
__author__ = "Tomi Pieviläinen <tomi.pievilainen@iki.fi>"
|
__all__ = ["gettz", "gettz_db_metadata", "rebuild"]
|
||||||
__license__ = "Simplified BSD"
|
|
||||||
|
|
||||||
__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):
|
class tzfile(tzfile):
|
||||||
def __reduce__(self):
|
def __reduce__(self):
|
||||||
return (gettz, (self._filename,))
|
return (gettz, (self._filename,))
|
||||||
|
|
||||||
def getzoneinfofile():
|
|
||||||
filenames = sorted(os.listdir(os.path.join(os.path.dirname(__file__))))
|
def getzoneinfofile_stream():
|
||||||
filenames.reverse()
|
try:
|
||||||
for entry in filenames:
|
return BytesIO(get_data(__name__, _ZONEFILENAME))
|
||||||
if entry.startswith("zoneinfo") and ".tar." in entry:
|
except IOError as e: # TODO switch to FileNotFoundError?
|
||||||
return os.path.join(os.path.dirname(__file__), entry)
|
warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror))
|
||||||
return None
|
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):
|
def gettz(name):
|
||||||
tzinfo = None
|
if len(_CLASS_ZONE_INSTANCE) == 0:
|
||||||
if ZONEINFOFILE:
|
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
|
||||||
for cachedname, tzinfo in CACHE:
|
return _CLASS_ZONE_INSTANCE[0].zones.get(name)
|
||||||
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
|
|
||||||
|
|
||||||
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*
|
"""Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar*
|
||||||
|
|
||||||
filename is the timezone tarball from ftp.iana.org/tz.
|
filename is the timezone tarball from ftp.iana.org/tz.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import tempfile, shutil
|
|
||||||
tmpdir = tempfile.mkdtemp()
|
tmpdir = tempfile.mkdtemp()
|
||||||
zonedir = os.path.join(tmpdir, "zoneinfo")
|
zonedir = os.path.join(tmpdir, "zoneinfo")
|
||||||
moduledir = os.path.dirname(__file__)
|
moduledir = os.path.dirname(__file__)
|
||||||
if tag: tag = "-"+tag
|
|
||||||
targetname = "zoneinfo%s.tar.%s" % (tag, format)
|
|
||||||
try:
|
try:
|
||||||
tf = TarFile.open(filename)
|
with _tar_open(filename) as tf:
|
||||||
# The "backwards" zone file contains links to other files, so must be
|
for name in zonegroups:
|
||||||
# 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"):
|
|
||||||
tf.extract(name, tmpdir)
|
tf.extract(name, tmpdir)
|
||||||
filepath = os.path.join(tmpdir, name)
|
filepaths = [os.path.join(tmpdir, n) for n in zonegroups]
|
||||||
try:
|
try:
|
||||||
# zic will return errors for nontz files in the package
|
check_call(["zic", "-d", zonedir] + filepaths)
|
||||||
# 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])
|
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if e.errno == 2:
|
if e.errno == 2:
|
||||||
logging.error(
|
logging.error(
|
||||||
|
@ -95,15 +123,13 @@ def rebuild(filename, tag=None, format="gz"):
|
||||||
"libc-bin or some other package that provides it, "
|
"libc-bin or some other package that provides it, "
|
||||||
"or it's not in your PATH?")
|
"or it's not in your PATH?")
|
||||||
raise
|
raise
|
||||||
tf.close()
|
# write metadata file
|
||||||
target = os.path.join(moduledir, targetname)
|
with open(os.path.join(zonedir, _METADATA_FN), 'w') as f:
|
||||||
for entry in os.listdir(moduledir):
|
json.dump(metadata, f, indent=4, sort_keys=True)
|
||||||
if entry.startswith("zoneinfo") and ".tar." in entry:
|
target = os.path.join(moduledir, _ZONEFILENAME)
|
||||||
os.unlink(os.path.join(moduledir, entry))
|
with _tar_open(target, "w:%s" % format) as tf:
|
||||||
tf = TarFile.open(target, "w:%s" % format)
|
|
||||||
for entry in os.listdir(zonedir):
|
for entry in os.listdir(zonedir):
|
||||||
entrypath = os.path.join(zonedir, entry)
|
entrypath = os.path.join(zonedir, entry)
|
||||||
tf.add(entrypath, entry)
|
tf.add(entrypath, entry)
|
||||||
tf.close()
|
|
||||||
finally:
|
finally:
|
||||||
shutil.rmtree(tmpdir)
|
shutil.rmtree(tmpdir)
|
||||||
|
|
Loading…
Reference in a new issue