mirror of
https://github.com/ansible/awx.git
synced 2026-01-16 12:20:45 -03:30
Upgrade python dateutil to 2.4.0
This commit is contained in:
parent
48a7b5f80e
commit
6c65ba826d
@ -45,7 +45,7 @@ pexpect==3.1 (pexpect/*, excluded pxssh.py, fdpexpect.py, FSM.py, screen.py,
|
||||
pip==1.5.4 (pip/*, excluded bin/pip*)
|
||||
prettytable==0.7.2 (prettytable.py)
|
||||
pyrax==1.9.0 (pyrax/*)
|
||||
python-dateutil==2.2 (dateutil/*)
|
||||
python-dateutil==2.4.0 (dateutil/*)
|
||||
python-novaclient==2.18.1 (novaclient/*, excluded bin/nova)
|
||||
python-swiftclient==2.2.0 (swiftclient/*, excluded bin/swift)
|
||||
pytz==2014.10 (pytz/*)
|
||||
|
||||
@ -1,10 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2003-2010 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard Python
|
||||
datetime module.
|
||||
"""
|
||||
__author__ = "Tomi Pieviläinen <tomi.pievilainen@iki.fi>"
|
||||
__license__ = "Simplified BSD"
|
||||
__version__ = "2.2"
|
||||
__version__ = "2.4.0"
|
||||
|
||||
@ -1,18 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard Python
|
||||
datetime module.
|
||||
This module offers a generic easter computing method for any given year, using
|
||||
Western, Orthodox or Julian algorithms.
|
||||
"""
|
||||
__license__ = "Simplified BSD"
|
||||
|
||||
import datetime
|
||||
|
||||
__all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"]
|
||||
|
||||
EASTER_JULIAN = 1
|
||||
EASTER_JULIAN = 1
|
||||
EASTER_ORTHODOX = 2
|
||||
EASTER_WESTERN = 3
|
||||
EASTER_WESTERN = 3
|
||||
|
||||
|
||||
def easter(year, method=EASTER_WESTERN):
|
||||
"""
|
||||
@ -24,7 +23,7 @@ def easter(year, method=EASTER_WESTERN):
|
||||
|
||||
This algorithm implements three different easter
|
||||
calculation methods:
|
||||
|
||||
|
||||
1 - Original calculation in Julian calendar, valid in
|
||||
dates after 326 AD
|
||||
2 - Original method, with date converted to Gregorian
|
||||
@ -39,7 +38,7 @@ def easter(year, method=EASTER_WESTERN):
|
||||
EASTER_WESTERN = 3
|
||||
|
||||
The default method is method 3.
|
||||
|
||||
|
||||
More about the algorithm may be found at:
|
||||
|
||||
http://users.chariot.net.au/~gmarts/eastalg.htm
|
||||
@ -68,24 +67,23 @@ def easter(year, method=EASTER_WESTERN):
|
||||
e = 0
|
||||
if method < 3:
|
||||
# Old method
|
||||
i = (19*g+15)%30
|
||||
j = (y+y//4+i)%7
|
||||
i = (19*g + 15) % 30
|
||||
j = (y + y//4 + i) % 7
|
||||
if method == 2:
|
||||
# Extra dates to convert Julian to Gregorian date
|
||||
e = 10
|
||||
if y > 1600:
|
||||
e = e+y//100-16-(y//100-16)//4
|
||||
e = e + y//100 - 16 - (y//100 - 16)//4
|
||||
else:
|
||||
# New method
|
||||
c = y//100
|
||||
h = (c-c//4-(8*c+13)//25+19*g+15)%30
|
||||
i = h-(h//28)*(1-(h//28)*(29//(h+1))*((21-g)//11))
|
||||
j = (y+y//4+i+2-c+c//4)%7
|
||||
h = (c - c//4 - (8*c + 13)//25 + 19*g + 15) % 30
|
||||
i = h - (h//28)*(1 - (h//28)*(29//(h + 1))*((21 - g)//11))
|
||||
j = (y + y//4 + i + 2 - c + c//4) % 7
|
||||
|
||||
# p can be from -6 to 56 corresponding to dates 22 March to 23 May
|
||||
# (later dates apply to method 2, although 23 May never actually occurs)
|
||||
p = i-j+e
|
||||
d = 1+(p+27+(p+6)//40)%31
|
||||
m = 3+(p+26)//30
|
||||
p = i - j + e
|
||||
d = 1 + (p + 27 + (p + 6)//40) % 31
|
||||
m = 3 + (p + 26)//30
|
||||
return datetime.date(int(y), int(m), int(d))
|
||||
|
||||
|
||||
@ -1,32 +1,21 @@
|
||||
# -*- coding:iso-8859-1 -*-
|
||||
"""
|
||||
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard Python
|
||||
datetime module.
|
||||
This module offers a generic date/time string parser which is able to parse
|
||||
most known formats to represent a date and/or time.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
__license__ = "Simplified BSD"
|
||||
|
||||
|
||||
import datetime
|
||||
import string
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
import collections
|
||||
|
||||
try:
|
||||
from io import StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
from io import StringIO
|
||||
|
||||
from six import text_type, binary_type, integer_types
|
||||
|
||||
from . import relativedelta
|
||||
from . import tz
|
||||
|
||||
|
||||
__all__ = ["parse", "parserinfo"]
|
||||
|
||||
|
||||
@ -83,9 +72,9 @@ class _timelex(object):
|
||||
state = '0'
|
||||
elif nextchar in whitespace:
|
||||
token = ' '
|
||||
break # emit token
|
||||
break # emit token
|
||||
else:
|
||||
break # emit token
|
||||
break # emit token
|
||||
elif state == 'a':
|
||||
seenletters = True
|
||||
if nextchar in wordchars:
|
||||
@ -95,7 +84,7 @@ class _timelex(object):
|
||||
state = 'a.'
|
||||
else:
|
||||
self.charstack.append(nextchar)
|
||||
break # emit token
|
||||
break # emit token
|
||||
elif state == '0':
|
||||
if nextchar in numchars:
|
||||
token += nextchar
|
||||
@ -104,7 +93,7 @@ class _timelex(object):
|
||||
state = '0.'
|
||||
else:
|
||||
self.charstack.append(nextchar)
|
||||
break # emit token
|
||||
break # emit token
|
||||
elif state == 'a.':
|
||||
seenletters = True
|
||||
if nextchar == '.' or nextchar in wordchars:
|
||||
@ -114,7 +103,7 @@ class _timelex(object):
|
||||
state = '0.'
|
||||
else:
|
||||
self.charstack.append(nextchar)
|
||||
break # emit token
|
||||
break # emit token
|
||||
elif state == '0.':
|
||||
if nextchar == '.' or nextchar in numchars:
|
||||
token += nextchar
|
||||
@ -123,9 +112,9 @@ class _timelex(object):
|
||||
state = 'a.'
|
||||
else:
|
||||
self.charstack.append(nextchar)
|
||||
break # emit token
|
||||
if (state in ('a.', '0.') and
|
||||
(seenletters or token.count('.') > 1 or token[-1] == '.')):
|
||||
break # emit token
|
||||
if (state in ('a.', '0.') and (seenletters or token.count('.') > 1 or
|
||||
token[-1] == '.')):
|
||||
l = token.split('.')
|
||||
token = l[0]
|
||||
for tok in l[1:]:
|
||||
@ -183,18 +172,18 @@ class parserinfo(object):
|
||||
("Fri", "Friday"),
|
||||
("Sat", "Saturday"),
|
||||
("Sun", "Sunday")]
|
||||
MONTHS = [("Jan", "January"),
|
||||
("Feb", "February"),
|
||||
("Mar", "March"),
|
||||
("Apr", "April"),
|
||||
("May", "May"),
|
||||
("Jun", "June"),
|
||||
("Jul", "July"),
|
||||
("Aug", "August"),
|
||||
("Sep", "Sept", "September"),
|
||||
("Oct", "October"),
|
||||
("Nov", "November"),
|
||||
("Dec", "December")]
|
||||
MONTHS = [("Jan", "January"),
|
||||
("Feb", "February"),
|
||||
("Mar", "March"),
|
||||
("Apr", "April"),
|
||||
("May", "May"),
|
||||
("Jun", "June"),
|
||||
("Jul", "July"),
|
||||
("Aug", "August"),
|
||||
("Sep", "Sept", "September"),
|
||||
("Oct", "October"),
|
||||
("Nov", "November"),
|
||||
("Dec", "December")]
|
||||
HMS = [("h", "hour", "hours"),
|
||||
("m", "minute", "minutes"),
|
||||
("s", "second", "seconds")]
|
||||
@ -299,15 +288,16 @@ class parser(object):
|
||||
def __init__(self, info=None):
|
||||
self.info = info or parserinfo()
|
||||
|
||||
def parse(self, timestr, default=None,
|
||||
ignoretz=False, tzinfos=None,
|
||||
**kwargs):
|
||||
def parse(self, timestr, default=None, ignoretz=False, tzinfos=None,
|
||||
**kwargs):
|
||||
if not default:
|
||||
default = datetime.datetime.now().replace(hour=0, minute=0,
|
||||
second=0, microsecond=0)
|
||||
|
||||
|
||||
res, skipped_tokens = self._parse(timestr, **kwargs)
|
||||
if kwargs.get('fuzzy_with_tokens', False):
|
||||
res, skipped_tokens = self._parse(timestr, **kwargs)
|
||||
else:
|
||||
res = self._parse(timestr, **kwargs)
|
||||
|
||||
if res is None:
|
||||
raise ValueError("unknown string format")
|
||||
@ -321,7 +311,8 @@ class parser(object):
|
||||
if res.weekday is not None and not res.day:
|
||||
ret = ret+relativedelta.relativedelta(weekday=res.weekday)
|
||||
if not ignoretz:
|
||||
if isinstance(tzinfos, collections.Callable) or tzinfos and res.tzname in tzinfos:
|
||||
if (isinstance(tzinfos, collections.Callable) or
|
||||
tzinfos and res.tzname in tzinfos):
|
||||
if isinstance(tzinfos, collections.Callable):
|
||||
tzdata = tzinfos(res.tzname, res.tzoffset)
|
||||
else:
|
||||
@ -333,8 +324,8 @@ class parser(object):
|
||||
elif isinstance(tzdata, integer_types):
|
||||
tzinfo = tz.tzoffset(res.tzname, tzdata)
|
||||
else:
|
||||
raise ValueError("offset must be tzinfo subclass, " \
|
||||
"tz string, or int offset")
|
||||
raise ValueError("offset must be tzinfo subclass, "
|
||||
"tz string, or int offset")
|
||||
ret = ret.replace(tzinfo=tzinfo)
|
||||
elif res.tzname and res.tzname in time.tzname:
|
||||
ret = ret.replace(tzinfo=tz.tzlocal())
|
||||
@ -343,17 +334,18 @@ class parser(object):
|
||||
elif res.tzoffset:
|
||||
ret = ret.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset))
|
||||
|
||||
if skipped_tokens:
|
||||
if kwargs.get('fuzzy_with_tokens', False):
|
||||
return ret, skipped_tokens
|
||||
|
||||
return ret
|
||||
else:
|
||||
return ret
|
||||
|
||||
class _result(_resultbase):
|
||||
__slots__ = ["year", "month", "day", "weekday",
|
||||
"hour", "minute", "second", "microsecond",
|
||||
"tzname", "tzoffset"]
|
||||
|
||||
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):
|
||||
if fuzzy_with_tokens:
|
||||
fuzzy = True
|
||||
|
||||
@ -365,7 +357,6 @@ class parser(object):
|
||||
res = self._result()
|
||||
l = _timelex.split(timestr)
|
||||
|
||||
|
||||
# keep up with the last token skipped so we can recombine
|
||||
# consecutively skipped tokens (-2 for when i begins at 0).
|
||||
last_skipped_token_i = -2
|
||||
@ -440,12 +431,12 @@ class parser(object):
|
||||
while True:
|
||||
if idx == 0:
|
||||
res.hour = int(value)
|
||||
if value%1:
|
||||
res.minute = int(60*(value%1))
|
||||
if value % 1:
|
||||
res.minute = int(60*(value % 1))
|
||||
elif idx == 1:
|
||||
res.minute = int(value)
|
||||
if value%1:
|
||||
res.second = int(60*(value%1))
|
||||
if value % 1:
|
||||
res.second = int(60*(value % 1))
|
||||
elif idx == 2:
|
||||
res.second, res.microsecond = \
|
||||
_parsems(value_repr)
|
||||
@ -465,16 +456,17 @@ class parser(object):
|
||||
newidx = info.hms(l[i])
|
||||
if newidx is not None:
|
||||
idx = newidx
|
||||
elif i == len_l and l[i-2] == ' ' and info.hms(l[i-3]) is not None:
|
||||
elif (i == len_l and l[i-2] == ' ' and
|
||||
info.hms(l[i-3]) is not None):
|
||||
# X h MM or X m SS
|
||||
idx = info.hms(l[i-3]) + 1
|
||||
if idx == 1:
|
||||
res.minute = int(value)
|
||||
if value%1:
|
||||
res.second = int(60*(value%1))
|
||||
if value % 1:
|
||||
res.second = int(60*(value % 1))
|
||||
elif idx == 2:
|
||||
res.second, res.microsecond = \
|
||||
_parsems(value_repr)
|
||||
_parsems(value_repr)
|
||||
i += 1
|
||||
elif i+1 < len_l and l[i] == ':':
|
||||
# HH:MM[:SS[.ss]]
|
||||
@ -482,8 +474,8 @@ class parser(object):
|
||||
i += 1
|
||||
value = float(l[i])
|
||||
res.minute = int(value)
|
||||
if value%1:
|
||||
res.second = int(60*(value%1))
|
||||
if value % 1:
|
||||
res.second = int(60*(value % 1))
|
||||
i += 1
|
||||
if i < len_l and l[i] == ':':
|
||||
res.second, res.microsecond = _parsems(l[i+1])
|
||||
@ -597,8 +589,9 @@ class parser(object):
|
||||
|
||||
# Check for a timezone name
|
||||
if (res.hour is not None and len(l[i]) <= 5 and
|
||||
res.tzname is None and res.tzoffset is None and
|
||||
not [x for x in l[i] if x not in string.ascii_uppercase]):
|
||||
res.tzname is None and res.tzoffset is None and
|
||||
not [x for x in l[i] if x not in
|
||||
string.ascii_uppercase]):
|
||||
res.tzname = l[i]
|
||||
res.tzoffset = info.tzoffset(res.tzname)
|
||||
i += 1
|
||||
@ -643,7 +636,7 @@ class parser(object):
|
||||
info.jump(l[i]) and l[i+1] == '(' and l[i+3] == ')' and
|
||||
3 <= len(l[i+2]) <= 5 and
|
||||
not [x for x in l[i+2]
|
||||
if x not in string.ascii_uppercase]):
|
||||
if x not in string.ascii_uppercase]):
|
||||
# -0300 (BRST)
|
||||
res.tzname = l[i+2]
|
||||
i += 4
|
||||
@ -732,10 +725,12 @@ class parser(object):
|
||||
|
||||
if fuzzy_with_tokens:
|
||||
return res, tuple(skipped_tokens)
|
||||
|
||||
return res, None
|
||||
else:
|
||||
return res
|
||||
|
||||
DEFAULTPARSER = parser()
|
||||
|
||||
|
||||
def parse(timestr, parserinfo=None, **kwargs):
|
||||
# Python 2.x support: datetimes return their string presentation as
|
||||
# bytes in 2.x and unicode in 3.x, so it's reasonable to expect that
|
||||
@ -779,7 +774,7 @@ class _tzparser(object):
|
||||
# BRST+3[BRDT[+2]]
|
||||
j = i
|
||||
while j < len_l and not [x for x in l[j]
|
||||
if x in "0123456789:,-+"]:
|
||||
if x in "0123456789:,-+"]:
|
||||
j += 1
|
||||
if j != i:
|
||||
if not res.stdabbr:
|
||||
@ -789,8 +784,8 @@ class _tzparser(object):
|
||||
offattr = "dstoffset"
|
||||
res.dstabbr = "".join(l[i:j])
|
||||
i = j
|
||||
if (i < len_l and
|
||||
(l[i] in ('+', '-') or l[i][0] in "0123456789")):
|
||||
if (i < len_l and (l[i] in ('+', '-') or l[i][0] in
|
||||
"0123456789")):
|
||||
if l[i] in ('+', '-'):
|
||||
# Yes, that's right. See the TZ variable
|
||||
# documentation.
|
||||
@ -801,8 +796,8 @@ class _tzparser(object):
|
||||
len_li = len(l[i])
|
||||
if len_li == 4:
|
||||
# -0300
|
||||
setattr(res, offattr,
|
||||
(int(l[i][:2])*3600+int(l[i][2:])*60)*signal)
|
||||
setattr(res, offattr, (int(l[i][:2])*3600 +
|
||||
int(l[i][2:])*60)*signal)
|
||||
elif i+1 < len_l and l[i+1] == ':':
|
||||
# -03:00
|
||||
setattr(res, offattr,
|
||||
@ -822,7 +817,8 @@ class _tzparser(object):
|
||||
|
||||
if i < len_l:
|
||||
for j in range(i, len_l):
|
||||
if l[j] == ';': l[j] = ','
|
||||
if l[j] == ';':
|
||||
l[j] = ','
|
||||
|
||||
assert l[i] == ','
|
||||
|
||||
@ -831,7 +827,7 @@ class _tzparser(object):
|
||||
if i >= len_l:
|
||||
pass
|
||||
elif (8 <= l.count(',') <= 9 and
|
||||
not [y for x in l[i:] if x != ','
|
||||
not [y for x in l[i:] if x != ','
|
||||
for y in x if y not in "0123456789"]):
|
||||
# GMT0BST,3,0,30,3600,10,0,26,7200[,3600]
|
||||
for x in (res.start, res.end):
|
||||
@ -845,7 +841,7 @@ class _tzparser(object):
|
||||
i += 2
|
||||
if value:
|
||||
x.week = value
|
||||
x.weekday = (int(l[i])-1)%7
|
||||
x.weekday = (int(l[i])-1) % 7
|
||||
else:
|
||||
x.day = int(l[i])
|
||||
i += 2
|
||||
@ -861,7 +857,7 @@ class _tzparser(object):
|
||||
elif (l.count(',') == 2 and l[i:].count('/') <= 2 and
|
||||
not [y for x in l[i:] if x not in (',', '/', 'J', 'M',
|
||||
'.', '-', ':')
|
||||
for y in x if y not in "0123456789"]):
|
||||
for y in x if y not in "0123456789"]):
|
||||
for x in (res.start, res.end):
|
||||
if l[i] == 'J':
|
||||
# non-leap year day (1 based)
|
||||
@ -880,7 +876,7 @@ class _tzparser(object):
|
||||
i += 1
|
||||
assert l[i] in ('-', '.')
|
||||
i += 1
|
||||
x.weekday = (int(l[i])-1)%7
|
||||
x.weekday = (int(l[i])-1) % 7
|
||||
else:
|
||||
# year day (zero based)
|
||||
x.yday = int(l[i])+1
|
||||
@ -921,6 +917,8 @@ class _tzparser(object):
|
||||
|
||||
|
||||
DEFAULTTZPARSER = _tzparser()
|
||||
|
||||
|
||||
def _parsetz(tzstr):
|
||||
return DEFAULTTZPARSER.parse(tzstr)
|
||||
|
||||
|
||||
@ -1,11 +1,4 @@
|
||||
"""
|
||||
Copyright (c) 2003-2010 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard Python
|
||||
datetime module.
|
||||
"""
|
||||
__license__ = "Simplified BSD"
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
import calendar
|
||||
|
||||
@ -13,6 +6,7 @@ from six import integer_types
|
||||
|
||||
__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
|
||||
|
||||
|
||||
class weekday(object):
|
||||
__slots__ = ["weekday", "n"]
|
||||
|
||||
@ -43,25 +37,35 @@ class weekday(object):
|
||||
|
||||
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)])
|
||||
|
||||
|
||||
class relativedelta(object):
|
||||
"""
|
||||
The relativedelta type is based on the specification of the excelent
|
||||
work done by M.-A. Lemburg in his mx.DateTime extension. However,
|
||||
notice that this type does *NOT* implement the same algorithm as
|
||||
The relativedelta type is based on the specification of the excellent
|
||||
work done by M.-A. Lemburg in his
|
||||
`mx.DateTime <http://www.egenix.com/files/python/mxDateTime.html>`_ extension.
|
||||
However, notice that this type does *NOT* implement the same algorithm as
|
||||
his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.
|
||||
|
||||
There's two different ways to build a relativedelta instance. The
|
||||
first one is passing it two date/datetime classes:
|
||||
There are two different ways to build a relativedelta instance. The
|
||||
first one is passing it two date/datetime classes::
|
||||
|
||||
relativedelta(datetime1, datetime2)
|
||||
|
||||
And the other way is to use the following keyword arguments:
|
||||
The second one is passing it any number of the following keyword arguments::
|
||||
|
||||
relativedelta(arg1=x,arg2=y,arg3=z...)
|
||||
|
||||
year, month, day, hour, minute, second, microsecond:
|
||||
Absolute information.
|
||||
Absolute information (argument is singular); adding or subtracting a
|
||||
relativedelta with absolute information does not perform an aritmetic
|
||||
operation, but rather REPLACES the corresponding value in the
|
||||
original datetime with the value(s) in relativedelta.
|
||||
|
||||
years, months, weeks, days, hours, minutes, seconds, microseconds:
|
||||
Relative information, may be negative.
|
||||
Relative information, may be negative (argument is plural); adding
|
||||
or subtracting a relativedelta with relative information performs
|
||||
the corresponding aritmetic operation on the original datetime value
|
||||
with the information in the relativedelta.
|
||||
|
||||
weekday:
|
||||
One of the weekday instances (MO, TU, etc). These instances may
|
||||
@ -80,26 +84,26 @@ And the other way is to use the following keyword arguments:
|
||||
|
||||
Here is the behavior of operations with relativedelta:
|
||||
|
||||
1) Calculate the absolute year, using the 'year' argument, or the
|
||||
1. Calculate the absolute year, using the 'year' argument, or the
|
||||
original datetime year, if the argument is not present.
|
||||
|
||||
2) Add the relative 'years' argument to the absolute year.
|
||||
2. Add the relative 'years' argument to the absolute year.
|
||||
|
||||
3) Do steps 1 and 2 for month/months.
|
||||
3. Do steps 1 and 2 for month/months.
|
||||
|
||||
4) Calculate the absolute day, using the 'day' argument, or the
|
||||
4. Calculate the absolute day, using the 'day' argument, or the
|
||||
original datetime day, if the argument is not present. Then,
|
||||
subtract from the day until it fits in the year and month
|
||||
found after their operations.
|
||||
|
||||
5) Add the relative 'days' argument to the absolute day. Notice
|
||||
5. Add the relative 'days' argument to the absolute day. Notice
|
||||
that the 'weeks' argument is multiplied by 7 and added to
|
||||
'days'.
|
||||
|
||||
6) Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds,
|
||||
6. Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds,
|
||||
microsecond/microseconds.
|
||||
|
||||
7) If the 'weekday' argument is present, calculate the weekday,
|
||||
7. If the 'weekday' argument is present, calculate the weekday,
|
||||
with the given (wday, nth) tuple. wday is the index of the
|
||||
weekday (0-6, 0=Mon), and nth is the number of weeks to add
|
||||
forward or backward, depending on its signal. Notice that if
|
||||
@ -114,9 +118,14 @@ Here is the behavior of operations with relativedelta:
|
||||
yearday=None, nlyearday=None,
|
||||
hour=None, minute=None, second=None, microsecond=None):
|
||||
if dt1 and dt2:
|
||||
if (not isinstance(dt1, datetime.date)) or (not isinstance(dt2, datetime.date)):
|
||||
# datetime is a subclass of date. So both must be date
|
||||
if not (isinstance(dt1, datetime.date) and
|
||||
isinstance(dt2, datetime.date)):
|
||||
raise TypeError("relativedelta only diffs datetime/date")
|
||||
if not type(dt1) == type(dt2): #isinstance(dt1, type(dt2)):
|
||||
# We allow two dates, or two datetimes, so we coerce them to be
|
||||
# of the same type
|
||||
if (isinstance(dt1, datetime.datetime) !=
|
||||
isinstance(dt2, datetime.datetime)):
|
||||
if not isinstance(dt1, datetime.datetime):
|
||||
dt1 = datetime.datetime.fromordinal(dt1.toordinal())
|
||||
elif not isinstance(dt2, datetime.datetime):
|
||||
@ -185,7 +194,8 @@ Here is the behavior of operations with relativedelta:
|
||||
if yearday > 59:
|
||||
self.leapdays = -1
|
||||
if yday:
|
||||
ydayidx = [31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 366]
|
||||
ydayidx = [31, 59, 90, 120, 151, 181, 212,
|
||||
243, 273, 304, 334, 366]
|
||||
for idx, ydays in enumerate(ydayidx):
|
||||
if yday <= ydays:
|
||||
self.month = idx+1
|
||||
@ -225,9 +235,9 @@ Here is the behavior of operations with relativedelta:
|
||||
div, mod = divmod(self.months*s, 12)
|
||||
self.months = mod*s
|
||||
self.years += div*s
|
||||
if (self.hours or self.minutes or self.seconds or self.microseconds or
|
||||
self.hour is not None or self.minute is not None or
|
||||
self.second is not None or self.microsecond is not None):
|
||||
if (self.hours or self.minutes or self.seconds or self.microseconds
|
||||
or self.hour is not None or self.minute is not None or
|
||||
self.second is not None or self.microsecond is not None):
|
||||
self._has_time = 1
|
||||
else:
|
||||
self._has_time = 0
|
||||
@ -245,21 +255,23 @@ Here is the behavior of operations with relativedelta:
|
||||
def __add__(self, other):
|
||||
if isinstance(other, relativedelta):
|
||||
return relativedelta(years=other.years+self.years,
|
||||
months=other.months+self.months,
|
||||
days=other.days+self.days,
|
||||
hours=other.hours+self.hours,
|
||||
minutes=other.minutes+self.minutes,
|
||||
seconds=other.seconds+self.seconds,
|
||||
microseconds=other.microseconds+self.microseconds,
|
||||
leapdays=other.leapdays or self.leapdays,
|
||||
year=other.year or self.year,
|
||||
month=other.month or self.month,
|
||||
day=other.day or self.day,
|
||||
weekday=other.weekday or self.weekday,
|
||||
hour=other.hour or self.hour,
|
||||
minute=other.minute or self.minute,
|
||||
second=other.second or self.second,
|
||||
microsecond=other.microsecond or self.microsecond)
|
||||
months=other.months+self.months,
|
||||
days=other.days+self.days,
|
||||
hours=other.hours+self.hours,
|
||||
minutes=other.minutes+self.minutes,
|
||||
seconds=other.seconds+self.seconds,
|
||||
microseconds=(other.microseconds +
|
||||
self.microseconds),
|
||||
leapdays=other.leapdays or self.leapdays,
|
||||
year=other.year or self.year,
|
||||
month=other.month or self.month,
|
||||
day=other.day or self.day,
|
||||
weekday=other.weekday or self.weekday,
|
||||
hour=other.hour or self.hour,
|
||||
minute=other.minute or self.minute,
|
||||
second=other.second or self.second,
|
||||
microsecond=(other.microsecond or
|
||||
self.microsecond))
|
||||
if not isinstance(other, datetime.date):
|
||||
raise TypeError("unsupported type for add operation")
|
||||
elif self._has_time and not isinstance(other, datetime.datetime):
|
||||
@ -295,9 +307,9 @@ Here is the behavior of operations with relativedelta:
|
||||
weekday, nth = self.weekday.weekday, self.weekday.n or 1
|
||||
jumpdays = (abs(nth)-1)*7
|
||||
if nth > 0:
|
||||
jumpdays += (7-ret.weekday()+weekday)%7
|
||||
jumpdays += (7-ret.weekday()+weekday) % 7
|
||||
else:
|
||||
jumpdays += (ret.weekday()-weekday)%7
|
||||
jumpdays += (ret.weekday()-weekday) % 7
|
||||
jumpdays *= -1
|
||||
ret += datetime.timedelta(days=jumpdays)
|
||||
return ret
|
||||
|
||||
@ -1,21 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2003-2010 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard Python
|
||||
datetime module.
|
||||
The rrule module offers a small, complete, and very fast, implementation of
|
||||
the recurrence rules documented in the
|
||||
`iCalendar RFC <http://www.ietf.org/rfc/rfc2445.txt>`_,
|
||||
including support for caching of results.
|
||||
"""
|
||||
__license__ = "Simplified BSD"
|
||||
|
||||
import itertools
|
||||
import datetime
|
||||
import calendar
|
||||
try:
|
||||
import _thread
|
||||
except ImportError:
|
||||
import thread as _thread
|
||||
import sys
|
||||
|
||||
from fractions import gcd
|
||||
|
||||
from six import advance_iterator, integer_types
|
||||
from six.moves import _thread
|
||||
|
||||
__all__ = ["rrule", "rruleset", "rrulestr",
|
||||
"YEARLY", "MONTHLY", "WEEKLY", "DAILY",
|
||||
@ -23,7 +21,7 @@ __all__ = ["rrule", "rruleset", "rrulestr",
|
||||
"MO", "TU", "WE", "TH", "FR", "SA", "SU"]
|
||||
|
||||
# Every mask is 7 days longer to handle cross-year weekly periods.
|
||||
M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30+
|
||||
M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30 +
|
||||
[7]*31+[8]*31+[9]*30+[10]*31+[11]*30+[12]*31+[1]*7)
|
||||
M365MASK = list(M366MASK)
|
||||
M29, M30, M31 = list(range(1, 30)), list(range(1, 31)), list(range(1, 32))
|
||||
@ -51,6 +49,7 @@ M365MASK = tuple(M365MASK)
|
||||
easter = None
|
||||
parser = None
|
||||
|
||||
|
||||
class weekday(object):
|
||||
__slots__ = ["weekday", "n"]
|
||||
|
||||
@ -83,12 +82,13 @@ class weekday(object):
|
||||
|
||||
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)])
|
||||
|
||||
|
||||
class rrulebase(object):
|
||||
def __init__(self, cache=False):
|
||||
if cache:
|
||||
self._cache = []
|
||||
self._cache_lock = _thread.allocate_lock()
|
||||
self._cache_gen = self._iter()
|
||||
self._cache_gen = self._iter()
|
||||
self._cache_complete = False
|
||||
else:
|
||||
self._cache = None
|
||||
@ -163,11 +163,17 @@ class rrulebase(object):
|
||||
|
||||
# __len__() introduces a large performance penality.
|
||||
def count(self):
|
||||
""" Returns the number of recurrences in this set. It will have go
|
||||
trough the whole recurrence, if this hasn't been done before. """
|
||||
if self._len is None:
|
||||
for x in self: pass
|
||||
for x in self:
|
||||
pass
|
||||
return self._len
|
||||
|
||||
def before(self, dt, inc=False):
|
||||
""" Returns the last recurrence before the given datetime instance. The
|
||||
inc keyword defines what happens if dt is an occurrence. With
|
||||
inc=True, if dt itself is an occurrence, it will be returned. """
|
||||
if self._cache_complete:
|
||||
gen = self._cache
|
||||
else:
|
||||
@ -186,6 +192,9 @@ class rrulebase(object):
|
||||
return last
|
||||
|
||||
def after(self, dt, inc=False):
|
||||
""" Returns the first recurrence after the given datetime instance. The
|
||||
inc keyword defines what happens if dt is an occurrence. With
|
||||
inc=True, if dt itself is an occurrence, it will be returned. """
|
||||
if self._cache_complete:
|
||||
gen = self._cache
|
||||
else:
|
||||
@ -201,6 +210,10 @@ class rrulebase(object):
|
||||
return None
|
||||
|
||||
def between(self, after, before, inc=False):
|
||||
""" Returns all the occurrences of the rrule between after and before.
|
||||
The inc keyword defines what happens if after and/or before are
|
||||
themselves occurrences. With inc=True, they will be included in the
|
||||
list, if they are found in the recurrence set. """
|
||||
if self._cache_complete:
|
||||
gen = self._cache
|
||||
else:
|
||||
@ -229,7 +242,93 @@ class rrulebase(object):
|
||||
l.append(i)
|
||||
return l
|
||||
|
||||
|
||||
class rrule(rrulebase):
|
||||
"""
|
||||
That's the base of the rrule operation. It accepts all the keywords
|
||||
defined in the RFC as its constructor parameters (except byday,
|
||||
which was renamed to byweekday) and more. The constructor prototype is::
|
||||
|
||||
rrule(freq)
|
||||
|
||||
Where freq must be one of YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY,
|
||||
or SECONDLY.
|
||||
|
||||
Additionally, it supports the following keyword arguments:
|
||||
|
||||
:param cache:
|
||||
If given, it must be a boolean value specifying to enable or disable
|
||||
caching of results. If you will use the same rrule instance multiple
|
||||
times, enabling caching will improve the performance considerably.
|
||||
:param dtstart:
|
||||
The recurrence start. Besides being the base for the recurrence,
|
||||
missing parameters in the final recurrence instances will also be
|
||||
extracted from this date. If not given, datetime.now() will be used
|
||||
instead.
|
||||
:param interval:
|
||||
The interval between each freq iteration. For example, when using
|
||||
YEARLY, an interval of 2 means once every two years, but with HOURLY,
|
||||
it means once every two hours. The default interval is 1.
|
||||
:param wkst:
|
||||
The week start day. Must be one of the MO, TU, WE constants, or an
|
||||
integer, specifying the first day of the week. This will affect
|
||||
recurrences based on weekly periods. The default week start is got
|
||||
from calendar.firstweekday(), and may be modified by
|
||||
calendar.setfirstweekday().
|
||||
:param count:
|
||||
How many occurrences will be generated.
|
||||
:param until:
|
||||
If given, this must be a datetime instance, that will specify the
|
||||
limit of the recurrence. If a recurrence instance happens to be the
|
||||
same as the datetime instance given in the until keyword, this will
|
||||
be the last occurrence.
|
||||
:param bysetpos:
|
||||
If given, it must be either an integer, or a sequence of integers,
|
||||
positive or negative. Each given integer will specify an occurrence
|
||||
number, corresponding to the nth occurrence of the rule inside the
|
||||
frequency period. For example, a bysetpos of -1 if combined with a
|
||||
MONTHLY frequency, and a byweekday of (MO, TU, WE, TH, FR), will
|
||||
result in the last work day of every month.
|
||||
:param bymonth:
|
||||
If given, it must be either an integer, or a sequence of integers,
|
||||
meaning the months to apply the recurrence to.
|
||||
:param bymonthday:
|
||||
If given, it must be either an integer, or a sequence of integers,
|
||||
meaning the month days to apply the recurrence to.
|
||||
:param byyearday:
|
||||
If given, it must be either an integer, or a sequence of integers,
|
||||
meaning the year days to apply the recurrence to.
|
||||
:param byweekno:
|
||||
If given, it must be either an integer, or a sequence of integers,
|
||||
meaning the week numbers to apply the recurrence to. Week numbers
|
||||
have the meaning described in ISO8601, that is, the first week of
|
||||
the year is that containing at least four days of the new year.
|
||||
:param byweekday:
|
||||
If given, it must be either an integer (0 == MO), a sequence of
|
||||
integers, one of the weekday constants (MO, TU, etc), or a sequence
|
||||
of these constants. When given, these variables will define the
|
||||
weekdays where the recurrence will be applied. It's also possible to
|
||||
use an argument n for the weekday instances, which will mean the nth
|
||||
occurrence of this weekday in the period. For example, with MONTHLY,
|
||||
or with YEARLY and BYMONTH, using FR(+1) in byweekday will specify the
|
||||
first friday of the month where the recurrence happens. Notice that in
|
||||
the RFC documentation, this is specified as BYDAY, but was renamed to
|
||||
avoid the ambiguity of that keyword.
|
||||
:param byhour:
|
||||
If given, it must be either an integer, or a sequence of integers,
|
||||
meaning the hours to apply the recurrence to.
|
||||
:param byminute:
|
||||
If given, it must be either an integer, or a sequence of integers,
|
||||
meaning the minutes to apply the recurrence to.
|
||||
:param bysecond:
|
||||
If given, it must be either an integer, or a sequence of integers,
|
||||
meaning the seconds to apply the recurrence to.
|
||||
:param byeaster:
|
||||
If given, it must be either an integer, or a sequence of integers,
|
||||
positive or negative. Each integer will define an offset from the
|
||||
Easter Sunday. Passing the offset 0 to byeaster will yield the Easter
|
||||
Sunday itself. This is an extension to the RFC specification.
|
||||
"""
|
||||
def __init__(self, freq, dtstart=None,
|
||||
interval=1, wkst=None, count=None, until=None, bysetpos=None,
|
||||
bymonth=None, bymonthday=None, byyearday=None, byeaster=None,
|
||||
@ -249,15 +348,18 @@ class rrule(rrulebase):
|
||||
self._freq = freq
|
||||
self._interval = interval
|
||||
self._count = count
|
||||
|
||||
if until and not isinstance(until, datetime.datetime):
|
||||
until = datetime.datetime.fromordinal(until.toordinal())
|
||||
self._until = until
|
||||
|
||||
if wkst is None:
|
||||
self._wkst = calendar.firstweekday()
|
||||
elif isinstance(wkst, integer_types):
|
||||
self._wkst = wkst
|
||||
else:
|
||||
self._wkst = wkst.weekday
|
||||
|
||||
if bysetpos is None:
|
||||
self._bysetpos = None
|
||||
elif isinstance(bysetpos, integer_types):
|
||||
@ -271,30 +373,36 @@ class rrule(rrulebase):
|
||||
if pos == 0 or not (-366 <= pos <= 366):
|
||||
raise ValueError("bysetpos must be between 1 and 366, "
|
||||
"or between -366 and -1")
|
||||
if not (byweekno or byyearday or bymonthday or
|
||||
byweekday is not None or byeaster is not None):
|
||||
|
||||
if (byweekno is None and byyearday is None and bymonthday is None and
|
||||
byweekday is None and byeaster is None):
|
||||
if freq == YEARLY:
|
||||
if not bymonth:
|
||||
if bymonth is None:
|
||||
bymonth = dtstart.month
|
||||
bymonthday = dtstart.day
|
||||
elif freq == MONTHLY:
|
||||
bymonthday = dtstart.day
|
||||
elif freq == WEEKLY:
|
||||
byweekday = dtstart.weekday()
|
||||
|
||||
# bymonth
|
||||
if not bymonth:
|
||||
if bymonth is None:
|
||||
self._bymonth = None
|
||||
elif isinstance(bymonth, integer_types):
|
||||
self._bymonth = (bymonth,)
|
||||
else:
|
||||
self._bymonth = tuple(bymonth)
|
||||
if isinstance(bymonth, integer_types):
|
||||
bymonth = (bymonth,)
|
||||
|
||||
self._bymonth = set(bymonth)
|
||||
|
||||
# byyearday
|
||||
if not byyearday:
|
||||
if byyearday is None:
|
||||
self._byyearday = None
|
||||
elif isinstance(byyearday, integer_types):
|
||||
self._byyearday = (byyearday,)
|
||||
else:
|
||||
self._byyearday = tuple(byyearday)
|
||||
if isinstance(byyearday, integer_types):
|
||||
byyearday = (byyearday,)
|
||||
|
||||
self._byyearday = set(byyearday)
|
||||
|
||||
# byeaster
|
||||
if byeaster is not None:
|
||||
if not easter:
|
||||
@ -305,87 +413,104 @@ class rrule(rrulebase):
|
||||
self._byeaster = tuple(byeaster)
|
||||
else:
|
||||
self._byeaster = None
|
||||
|
||||
# bymonthay
|
||||
if not bymonthday:
|
||||
if bymonthday is None:
|
||||
self._bymonthday = ()
|
||||
self._bynmonthday = ()
|
||||
elif isinstance(bymonthday, integer_types):
|
||||
if bymonthday < 0:
|
||||
self._bynmonthday = (bymonthday,)
|
||||
self._bymonthday = ()
|
||||
else:
|
||||
self._bymonthday = (bymonthday,)
|
||||
self._bynmonthday = ()
|
||||
else:
|
||||
self._bymonthday = tuple([x for x in bymonthday if x > 0])
|
||||
self._bynmonthday = tuple([x for x in bymonthday if x < 0])
|
||||
if isinstance(bymonthday, integer_types):
|
||||
bymonthday = (bymonthday,)
|
||||
|
||||
self._bymonthday = set([x for x in bymonthday if x > 0])
|
||||
self._bynmonthday = set([x for x in bymonthday if x < 0])
|
||||
|
||||
# byweekno
|
||||
if byweekno is None:
|
||||
self._byweekno = None
|
||||
elif isinstance(byweekno, integer_types):
|
||||
self._byweekno = (byweekno,)
|
||||
else:
|
||||
self._byweekno = tuple(byweekno)
|
||||
if isinstance(byweekno, integer_types):
|
||||
byweekno = (byweekno,)
|
||||
|
||||
self._byweekno = set(byweekno)
|
||||
|
||||
# byweekday / bynweekday
|
||||
if byweekday is None:
|
||||
self._byweekday = None
|
||||
self._bynweekday = None
|
||||
elif isinstance(byweekday, integer_types):
|
||||
self._byweekday = (byweekday,)
|
||||
self._bynweekday = None
|
||||
elif hasattr(byweekday, "n"):
|
||||
if not byweekday.n or freq > MONTHLY:
|
||||
self._byweekday = (byweekday.weekday,)
|
||||
self._bynweekday = None
|
||||
else:
|
||||
self._bynweekday = ((byweekday.weekday, byweekday.n),)
|
||||
self._byweekday = None
|
||||
else:
|
||||
self._byweekday = []
|
||||
self._bynweekday = []
|
||||
if isinstance(byweekday, integer_types):
|
||||
byweekday = (byweekday,)
|
||||
elif hasattr(byweekday, "n"):
|
||||
byweekday = (byweekday.weekday,)
|
||||
|
||||
self._byweekday = set()
|
||||
self._bynweekday = set()
|
||||
for wday in byweekday:
|
||||
if isinstance(wday, integer_types):
|
||||
self._byweekday.append(wday)
|
||||
self._byweekday.add(wday)
|
||||
elif not wday.n or freq > MONTHLY:
|
||||
self._byweekday.append(wday.weekday)
|
||||
self._byweekday.add(wday.weekday)
|
||||
else:
|
||||
self._bynweekday.append((wday.weekday, wday.n))
|
||||
self._byweekday = tuple(self._byweekday)
|
||||
self._bynweekday = tuple(self._bynweekday)
|
||||
self._bynweekday.add((wday.weekday, wday.n))
|
||||
|
||||
if not self._byweekday:
|
||||
self._byweekday = None
|
||||
elif not self._bynweekday:
|
||||
self._bynweekday = None
|
||||
|
||||
# byhour
|
||||
if byhour is None:
|
||||
if freq < HOURLY:
|
||||
self._byhour = (dtstart.hour,)
|
||||
self._byhour = set((dtstart.hour,))
|
||||
else:
|
||||
self._byhour = None
|
||||
elif isinstance(byhour, integer_types):
|
||||
self._byhour = (byhour,)
|
||||
else:
|
||||
self._byhour = tuple(byhour)
|
||||
if isinstance(byhour, integer_types):
|
||||
byhour = (byhour,)
|
||||
|
||||
if freq == HOURLY:
|
||||
self._byhour = self.__construct_byset(start=dtstart.hour,
|
||||
byxxx=byhour,
|
||||
base=24)
|
||||
else:
|
||||
self._byhour = set(byhour)
|
||||
|
||||
# byminute
|
||||
if byminute is None:
|
||||
if freq < MINUTELY:
|
||||
self._byminute = (dtstart.minute,)
|
||||
self._byminute = set((dtstart.minute,))
|
||||
else:
|
||||
self._byminute = None
|
||||
elif isinstance(byminute, integer_types):
|
||||
self._byminute = (byminute,)
|
||||
else:
|
||||
self._byminute = tuple(byminute)
|
||||
if isinstance(byminute, integer_types):
|
||||
byminute = (byminute,)
|
||||
|
||||
if freq == MINUTELY:
|
||||
self._byminute = self.__construct_byset(start=dtstart.minute,
|
||||
byxxx=byminute,
|
||||
base=60)
|
||||
else:
|
||||
self._byminute = set(byminute)
|
||||
|
||||
# bysecond
|
||||
if bysecond is None:
|
||||
if freq < SECONDLY:
|
||||
self._bysecond = (dtstart.second,)
|
||||
self._bysecond = ((dtstart.second,))
|
||||
else:
|
||||
self._bysecond = None
|
||||
elif isinstance(bysecond, integer_types):
|
||||
self._bysecond = (bysecond,)
|
||||
else:
|
||||
self._bysecond = tuple(bysecond)
|
||||
if isinstance(bysecond, integer_types):
|
||||
bysecond = (bysecond,)
|
||||
|
||||
self._bysecond = set(bysecond)
|
||||
|
||||
if freq == SECONDLY:
|
||||
self._bysecond = self.__construct_byset(start=dtstart.second,
|
||||
byxxx=bysecond,
|
||||
base=60)
|
||||
else:
|
||||
self._bysecond = set(bysecond)
|
||||
|
||||
if self._freq >= HOURLY:
|
||||
self._timeset = None
|
||||
@ -395,8 +520,8 @@ class rrule(rrulebase):
|
||||
for minute in self._byminute:
|
||||
for second in self._bysecond:
|
||||
self._timeset.append(
|
||||
datetime.time(hour, minute, second,
|
||||
tzinfo=self._tzinfo))
|
||||
datetime.time(hour, minute, second,
|
||||
tzinfo=self._tzinfo))
|
||||
self._timeset.sort()
|
||||
self._timeset = tuple(self._timeset)
|
||||
|
||||
@ -424,20 +549,20 @@ class rrule(rrulebase):
|
||||
ii = _iterinfo(self)
|
||||
ii.rebuild(year, month)
|
||||
|
||||
getdayset = {YEARLY:ii.ydayset,
|
||||
MONTHLY:ii.mdayset,
|
||||
WEEKLY:ii.wdayset,
|
||||
DAILY:ii.ddayset,
|
||||
HOURLY:ii.ddayset,
|
||||
MINUTELY:ii.ddayset,
|
||||
SECONDLY:ii.ddayset}[freq]
|
||||
|
||||
getdayset = {YEARLY: ii.ydayset,
|
||||
MONTHLY: ii.mdayset,
|
||||
WEEKLY: ii.wdayset,
|
||||
DAILY: ii.ddayset,
|
||||
HOURLY: ii.ddayset,
|
||||
MINUTELY: ii.ddayset,
|
||||
SECONDLY: ii.ddayset}[freq]
|
||||
|
||||
if freq < HOURLY:
|
||||
timeset = self._timeset
|
||||
else:
|
||||
gettimeset = {HOURLY:ii.htimeset,
|
||||
MINUTELY:ii.mtimeset,
|
||||
SECONDLY:ii.stimeset}[freq]
|
||||
gettimeset = {HOURLY: ii.htimeset,
|
||||
MINUTELY: ii.mtimeset,
|
||||
SECONDLY: ii.stimeset}[freq]
|
||||
if ((freq >= HOURLY and
|
||||
self._byhour and hour not in self._byhour) or
|
||||
(freq >= MINUTELY and
|
||||
@ -466,11 +591,10 @@ class rrule(rrulebase):
|
||||
ii.mdaymask[i] not in bymonthday and
|
||||
ii.nmdaymask[i] not in bynmonthday) or
|
||||
(byyearday and
|
||||
((i < ii.yearlen and i+1 not in byyearday
|
||||
and -ii.yearlen+i not in byyearday) or
|
||||
(i >= ii.yearlen and i+1-ii.yearlen not in byyearday
|
||||
and -ii.nextyearlen+i-ii.yearlen
|
||||
not in byyearday)))):
|
||||
((i < ii.yearlen and i+1 not in byyearday and
|
||||
-ii.yearlen+i not in byyearday) or
|
||||
(i >= ii.yearlen and i+1-ii.yearlen not in byyearday and
|
||||
-ii.nextyearlen+i-ii.yearlen not in byyearday)))):
|
||||
dayset[i] = None
|
||||
filtered = True
|
||||
|
||||
@ -484,7 +608,7 @@ class rrule(rrulebase):
|
||||
daypos, timepos = divmod(pos-1, len(timeset))
|
||||
try:
|
||||
i = [x for x in dayset[start:end]
|
||||
if x is not None][daypos]
|
||||
if x is not None][daypos]
|
||||
time = timeset[timepos]
|
||||
except IndexError:
|
||||
pass
|
||||
@ -559,60 +683,86 @@ class rrule(rrulebase):
|
||||
if filtered:
|
||||
# Jump to one iteration before next day
|
||||
hour += ((23-hour)//interval)*interval
|
||||
while True:
|
||||
hour += interval
|
||||
div, mod = divmod(hour, 24)
|
||||
if div:
|
||||
hour = mod
|
||||
day += div
|
||||
fixday = True
|
||||
if not byhour or hour in byhour:
|
||||
break
|
||||
|
||||
if byhour:
|
||||
ndays, hour = self.__mod_distance(value=hour,
|
||||
byxxx=self._byhour,
|
||||
base=24)
|
||||
else:
|
||||
ndays, hour = divmod(hour+interval, 24)
|
||||
|
||||
if ndays:
|
||||
day += ndays
|
||||
fixday = True
|
||||
|
||||
timeset = gettimeset(hour, minute, second)
|
||||
elif freq == MINUTELY:
|
||||
if filtered:
|
||||
# Jump to one iteration before next day
|
||||
minute += ((1439-(hour*60+minute))//interval)*interval
|
||||
while True:
|
||||
minute += interval
|
||||
div, mod = divmod(minute, 60)
|
||||
|
||||
valid = False
|
||||
rep_rate = (24*60)
|
||||
for j in range(rep_rate // gcd(interval, rep_rate)):
|
||||
if byminute:
|
||||
nhours, minute = \
|
||||
self.__mod_distance(value=minute,
|
||||
byxxx=self._byminute,
|
||||
base=60)
|
||||
else:
|
||||
nhours, minute = divmod(minute+interval, 60)
|
||||
|
||||
div, hour = divmod(hour+nhours, 24)
|
||||
if div:
|
||||
minute = mod
|
||||
hour += div
|
||||
div, mod = divmod(hour, 24)
|
||||
if div:
|
||||
hour = mod
|
||||
day += div
|
||||
fixday = True
|
||||
filtered = False
|
||||
if ((not byhour or hour in byhour) and
|
||||
(not byminute or minute in byminute)):
|
||||
day += div
|
||||
fixday = True
|
||||
filtered = False
|
||||
|
||||
if not byhour or hour in byhour:
|
||||
valid = True
|
||||
break
|
||||
|
||||
if not valid:
|
||||
raise ValueError('Invalid combination of interval and ' +
|
||||
'byhour resulting in empty rule.')
|
||||
|
||||
timeset = gettimeset(hour, minute, second)
|
||||
elif freq == SECONDLY:
|
||||
if filtered:
|
||||
# Jump to one iteration before next day
|
||||
second += (((86399-(hour*3600+minute*60+second))
|
||||
//interval)*interval)
|
||||
while True:
|
||||
second += self._interval
|
||||
div, mod = divmod(second, 60)
|
||||
// interval)*interval)
|
||||
|
||||
rep_rate = (24*3600)
|
||||
valid = False
|
||||
for j in range(0, rep_rate // gcd(interval, rep_rate)):
|
||||
if bysecond:
|
||||
nminutes, second = \
|
||||
self.__mod_distance(value=second,
|
||||
byxxx=self._bysecond,
|
||||
base=60)
|
||||
else:
|
||||
nminutes, second = divmod(second+interval, 60)
|
||||
|
||||
div, minute = divmod(minute+nminutes, 60)
|
||||
if div:
|
||||
second = mod
|
||||
minute += div
|
||||
div, mod = divmod(minute, 60)
|
||||
hour += div
|
||||
div, hour = divmod(hour, 24)
|
||||
if div:
|
||||
minute = mod
|
||||
hour += div
|
||||
div, mod = divmod(hour, 24)
|
||||
if div:
|
||||
hour = mod
|
||||
day += div
|
||||
fixday = True
|
||||
day += div
|
||||
fixday = True
|
||||
|
||||
if ((not byhour or hour in byhour) and
|
||||
(not byminute or minute in byminute) and
|
||||
(not bysecond or second in bysecond)):
|
||||
(not byminute or minute in byminute) and
|
||||
(not bysecond or second in bysecond)):
|
||||
valid = True
|
||||
break
|
||||
|
||||
if not valid:
|
||||
raise ValueError('Invalid combination of interval, ' +
|
||||
'byhour and byminute resulting in empty' +
|
||||
' rule.')
|
||||
|
||||
timeset = gettimeset(hour, minute, second)
|
||||
|
||||
if fixday and day > 28:
|
||||
@ -630,6 +780,80 @@ class rrule(rrulebase):
|
||||
daysinmonth = calendar.monthrange(year, month)[1]
|
||||
ii.rebuild(year, month)
|
||||
|
||||
def __construct_byset(self, start, byxxx, base):
|
||||
"""
|
||||
If a `BYXXX` sequence is passed to the constructor at the same level as
|
||||
`FREQ` (e.g. `FREQ=HOURLY,BYHOUR={2,4,7},INTERVAL=3`), there are some
|
||||
specifications which cannot be reached given some starting conditions.
|
||||
|
||||
This occurs whenever the interval is not coprime with the base of a
|
||||
given unit and the difference between the starting position and the
|
||||
ending position is not coprime with the greatest common denominator
|
||||
between the interval and the base. For example, with a FREQ of hourly
|
||||
starting at 17:00 and an interval of 4, the only valid values for
|
||||
BYHOUR would be {21, 1, 5, 9, 13, 17}, because 4 and 24 are not
|
||||
coprime.
|
||||
|
||||
:param:`start` specifies the starting position.
|
||||
:param:`byxxx` is an iterable containing the list of allowed values.
|
||||
:param:`base` is the largest allowable value for the specified
|
||||
frequency (e.g. 24 hours, 60 minutes).
|
||||
|
||||
This does not preserve the type of the iterable, returning a set, since
|
||||
the values should be unique and the order is irrelevant, this will
|
||||
speed up later lookups.
|
||||
|
||||
In the event of an empty set, raises a :exception:`ValueError`, as this
|
||||
results in an empty rrule.
|
||||
"""
|
||||
|
||||
cset = set()
|
||||
|
||||
# Support a single byxxx value.
|
||||
if isinstance(byxxx, integer_types):
|
||||
byxxx = (byxxx)
|
||||
|
||||
for num in byxxx:
|
||||
i_gcd = gcd(self._interval, base)
|
||||
# Use divmod rather than % because we need to wrap negative nums.
|
||||
if i_gcd == 1 or divmod(num - start, i_gcd)[1] == 0:
|
||||
cset.add(num)
|
||||
|
||||
if len(cset) == 0:
|
||||
raise ValueError("Invalid rrule byxxx generates an empty set.")
|
||||
|
||||
return cset
|
||||
|
||||
def __mod_distance(self, value, byxxx, base):
|
||||
"""
|
||||
Calculates the next value in a sequence where the `FREQ` parameter is
|
||||
specified along with a `BYXXX` parameter at the same "level"
|
||||
(e.g. `HOURLY` specified with `BYHOUR`).
|
||||
|
||||
:param:`value` is the old value of the component.
|
||||
:param:`byxxx` is the `BYXXX` set, which should have been generated
|
||||
by `rrule._construct_byset`, or something else which
|
||||
checks that a valid rule is present.
|
||||
:param:`base` is the largest allowable value for the specified
|
||||
frequency (e.g. 24 hours, 60 minutes).
|
||||
|
||||
If a valid value is not found after `base` iterations (the maximum
|
||||
number before the sequence would start to repeat), this raises a
|
||||
:exception:`ValueError`, as no valid values were found.
|
||||
|
||||
This returns a tuple of `divmod(n*interval, base)`, where `n` is the
|
||||
smallest number of `interval` repetitions until the next specified
|
||||
value in `byxxx` is found.
|
||||
"""
|
||||
accumulator = 0
|
||||
for ii in range(1, base + 1):
|
||||
# Using divmod() over % to account for negative intervals
|
||||
div, value = divmod(value + self._interval, base)
|
||||
accumulator += div
|
||||
if value in byxxx:
|
||||
return (accumulator, value)
|
||||
|
||||
|
||||
class _iterinfo(object):
|
||||
__slots__ = ["rrule", "lastyear", "lastmonth",
|
||||
"yearlen", "nextyearlen", "yearordinal", "yearweekday",
|
||||
@ -669,13 +893,13 @@ class _iterinfo(object):
|
||||
self.wnomask = None
|
||||
else:
|
||||
self.wnomask = [0]*(self.yearlen+7)
|
||||
#no1wkst = firstwkst = self.wdaymask.index(rr._wkst)
|
||||
no1wkst = firstwkst = (7-self.yearweekday+rr._wkst)%7
|
||||
# no1wkst = firstwkst = self.wdaymask.index(rr._wkst)
|
||||
no1wkst = firstwkst = (7-self.yearweekday+rr._wkst) % 7
|
||||
if no1wkst >= 4:
|
||||
no1wkst = 0
|
||||
# Number of days in the year, plus the days we got
|
||||
# from last year.
|
||||
wyearlen = self.yearlen+(self.yearweekday-rr._wkst)%7
|
||||
wyearlen = self.yearlen+(self.yearweekday-rr._wkst) % 7
|
||||
else:
|
||||
# Number of days in the year, minus the days we
|
||||
# left in last year.
|
||||
@ -721,22 +945,22 @@ class _iterinfo(object):
|
||||
# this year.
|
||||
if -1 not in rr._byweekno:
|
||||
lyearweekday = datetime.date(year-1, 1, 1).weekday()
|
||||
lno1wkst = (7-lyearweekday+rr._wkst)%7
|
||||
lno1wkst = (7-lyearweekday+rr._wkst) % 7
|
||||
lyearlen = 365+calendar.isleap(year-1)
|
||||
if lno1wkst >= 4:
|
||||
lno1wkst = 0
|
||||
lnumweeks = 52+(lyearlen+
|
||||
(lyearweekday-rr._wkst)%7)%7//4
|
||||
lnumweeks = 52+(lyearlen +
|
||||
(lyearweekday-rr._wkst) % 7) % 7//4
|
||||
else:
|
||||
lnumweeks = 52+(self.yearlen-no1wkst)%7//4
|
||||
lnumweeks = 52+(self.yearlen-no1wkst) % 7//4
|
||||
else:
|
||||
lnumweeks = -1
|
||||
if lnumweeks in rr._byweekno:
|
||||
for i in range(no1wkst):
|
||||
self.wnomask[i] = 1
|
||||
|
||||
if (rr._bynweekday and
|
||||
(month != self.lastmonth or year != self.lastyear)):
|
||||
if (rr._bynweekday and (month != self.lastmonth or
|
||||
year != self.lastyear)):
|
||||
ranges = []
|
||||
if rr._freq == YEARLY:
|
||||
if rr._bymonth:
|
||||
@ -755,10 +979,10 @@ class _iterinfo(object):
|
||||
for wday, n in rr._bynweekday:
|
||||
if n < 0:
|
||||
i = last+(n+1)*7
|
||||
i -= (self.wdaymask[i]-wday)%7
|
||||
i -= (self.wdaymask[i]-wday) % 7
|
||||
else:
|
||||
i = first+(n-1)*7
|
||||
i += (7-self.wdaymask[i]+wday)%7
|
||||
i += (7-self.wdaymask[i]+wday) % 7
|
||||
if first <= i <= last:
|
||||
self.nwdaymask[i] = 1
|
||||
|
||||
@ -775,50 +999,50 @@ class _iterinfo(object):
|
||||
return list(range(self.yearlen)), 0, self.yearlen
|
||||
|
||||
def mdayset(self, year, month, day):
|
||||
set = [None]*self.yearlen
|
||||
dset = [None]*self.yearlen
|
||||
start, end = self.mrange[month-1:month+1]
|
||||
for i in range(start, end):
|
||||
set[i] = i
|
||||
return set, start, end
|
||||
dset[i] = i
|
||||
return dset, start, end
|
||||
|
||||
def wdayset(self, year, month, day):
|
||||
# We need to handle cross-year weeks here.
|
||||
set = [None]*(self.yearlen+7)
|
||||
dset = [None]*(self.yearlen+7)
|
||||
i = datetime.date(year, month, day).toordinal()-self.yearordinal
|
||||
start = i
|
||||
for j in range(7):
|
||||
set[i] = i
|
||||
dset[i] = i
|
||||
i += 1
|
||||
#if (not (0 <= i < self.yearlen) or
|
||||
# if (not (0 <= i < self.yearlen) or
|
||||
# self.wdaymask[i] == self.rrule._wkst):
|
||||
# This will cross the year boundary, if necessary.
|
||||
if self.wdaymask[i] == self.rrule._wkst:
|
||||
break
|
||||
return set, start, i
|
||||
return dset, start, i
|
||||
|
||||
def ddayset(self, year, month, day):
|
||||
set = [None]*self.yearlen
|
||||
dset = [None]*self.yearlen
|
||||
i = datetime.date(year, month, day).toordinal()-self.yearordinal
|
||||
set[i] = i
|
||||
return set, i, i+1
|
||||
dset[i] = i
|
||||
return dset, i, i+1
|
||||
|
||||
def htimeset(self, hour, minute, second):
|
||||
set = []
|
||||
tset = []
|
||||
rr = self.rrule
|
||||
for minute in rr._byminute:
|
||||
for second in rr._bysecond:
|
||||
set.append(datetime.time(hour, minute, second,
|
||||
tset.append(datetime.time(hour, minute, second,
|
||||
tzinfo=rr._tzinfo))
|
||||
set.sort()
|
||||
return set
|
||||
tset.sort()
|
||||
return tset
|
||||
|
||||
def mtimeset(self, hour, minute, second):
|
||||
set = []
|
||||
tset = []
|
||||
rr = self.rrule
|
||||
for second in rr._bysecond:
|
||||
set.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo))
|
||||
set.sort()
|
||||
return set
|
||||
tset.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo))
|
||||
tset.sort()
|
||||
return tset
|
||||
|
||||
def stimeset(self, hour, minute, second):
|
||||
return (datetime.time(hour, minute, second,
|
||||
@ -826,6 +1050,12 @@ class _iterinfo(object):
|
||||
|
||||
|
||||
class rruleset(rrulebase):
|
||||
""" The rruleset type allows more complex recurrence setups, mixing
|
||||
multiple rules, dates, exclusion rules, and exclusion dates. The type
|
||||
constructor takes the following keyword arguments:
|
||||
|
||||
:param cache: If True, caching of results will be enabled, improving
|
||||
performance of multiple queries considerably. """
|
||||
|
||||
class _genitem(object):
|
||||
def __init__(self, genlist, gen):
|
||||
@ -865,15 +1095,26 @@ class rruleset(rrulebase):
|
||||
self._exdate = []
|
||||
|
||||
def rrule(self, rrule):
|
||||
""" Include the given :py:class:`rrule` instance in the recurrence set
|
||||
generation. """
|
||||
self._rrule.append(rrule)
|
||||
|
||||
def rdate(self, rdate):
|
||||
""" Include the given :py:class:`datetime` instance in the recurrence
|
||||
set generation. """
|
||||
self._rdate.append(rdate)
|
||||
|
||||
def exrule(self, exrule):
|
||||
""" Include the given rrule instance in the recurrence set exclusion
|
||||
list. Dates which are part of the given recurrence rules will not
|
||||
be generated, even if some inclusive rrule or rdate matches them.
|
||||
"""
|
||||
self._exrule.append(exrule)
|
||||
|
||||
def exdate(self, exdate):
|
||||
""" Include the given datetime instance in the recurrence set
|
||||
exclusion list. Dates included that way will not be generated,
|
||||
even if some inclusive rrule or rdate matches them. """
|
||||
self._exdate.append(exdate)
|
||||
|
||||
def _iter(self):
|
||||
@ -905,6 +1146,7 @@ class rruleset(rrulebase):
|
||||
rlist.sort()
|
||||
self._len = total
|
||||
|
||||
|
||||
class _rrulestr(object):
|
||||
|
||||
_freq_map = {"YEARLY": YEARLY,
|
||||
@ -915,7 +1157,8 @@ class _rrulestr(object):
|
||||
"MINUTELY": MINUTELY,
|
||||
"SECONDLY": SECONDLY}
|
||||
|
||||
_weekday_map = {"MO":0,"TU":1,"WE":2,"TH":3,"FR":4,"SA":5,"SU":6}
|
||||
_weekday_map = {"MO": 0, "TU": 1, "WE": 2, "TH": 3,
|
||||
"FR": 4, "SA": 5, "SU": 6}
|
||||
|
||||
def _handle_int(self, rrkwargs, name, value, **kwargs):
|
||||
rrkwargs[name.lower()] = int(value)
|
||||
@ -923,17 +1166,17 @@ class _rrulestr(object):
|
||||
def _handle_int_list(self, rrkwargs, name, value, **kwargs):
|
||||
rrkwargs[name.lower()] = [int(x) for x in value.split(',')]
|
||||
|
||||
_handle_INTERVAL = _handle_int
|
||||
_handle_COUNT = _handle_int
|
||||
_handle_BYSETPOS = _handle_int_list
|
||||
_handle_BYMONTH = _handle_int_list
|
||||
_handle_INTERVAL = _handle_int
|
||||
_handle_COUNT = _handle_int
|
||||
_handle_BYSETPOS = _handle_int_list
|
||||
_handle_BYMONTH = _handle_int_list
|
||||
_handle_BYMONTHDAY = _handle_int_list
|
||||
_handle_BYYEARDAY = _handle_int_list
|
||||
_handle_BYEASTER = _handle_int_list
|
||||
_handle_BYWEEKNO = _handle_int_list
|
||||
_handle_BYHOUR = _handle_int_list
|
||||
_handle_BYMINUTE = _handle_int_list
|
||||
_handle_BYSECOND = _handle_int_list
|
||||
_handle_BYYEARDAY = _handle_int_list
|
||||
_handle_BYEASTER = _handle_int_list
|
||||
_handle_BYWEEKNO = _handle_int_list
|
||||
_handle_BYHOUR = _handle_int_list
|
||||
_handle_BYMINUTE = _handle_int_list
|
||||
_handle_BYSECOND = _handle_int_list
|
||||
|
||||
def _handle_FREQ(self, rrkwargs, name, value, **kwargs):
|
||||
rrkwargs["freq"] = self._freq_map[value]
|
||||
@ -944,8 +1187,8 @@ class _rrulestr(object):
|
||||
from dateutil import parser
|
||||
try:
|
||||
rrkwargs["until"] = parser.parse(value,
|
||||
ignoretz=kwargs.get("ignoretz"),
|
||||
tzinfos=kwargs.get("tzinfos"))
|
||||
ignoretz=kwargs.get("ignoretz"),
|
||||
tzinfos=kwargs.get("tzinfos"))
|
||||
except ValueError:
|
||||
raise ValueError("invalid until date")
|
||||
|
||||
@ -960,7 +1203,8 @@ class _rrulestr(object):
|
||||
break
|
||||
n = wday[:i] or None
|
||||
w = wday[i:]
|
||||
if n: n = int(n)
|
||||
if n:
|
||||
n = int(n)
|
||||
l.append(weekdays[self._weekday_map[w]](n))
|
||||
rrkwargs["byweekday"] = l
|
||||
|
||||
@ -1021,8 +1265,8 @@ class _rrulestr(object):
|
||||
i += 1
|
||||
else:
|
||||
lines = s.split()
|
||||
if (not forceset and len(lines) == 1 and
|
||||
(s.find(':') == -1 or s.startswith('RRULE:'))):
|
||||
if (not forceset and len(lines) == 1 and (s.find(':') == -1 or
|
||||
s.startswith('RRULE:'))):
|
||||
return self._parse_rfc_rrule(lines[0], cache=cache,
|
||||
dtstart=dtstart, ignoretz=ignoretz,
|
||||
tzinfos=tzinfos)
|
||||
@ -1071,32 +1315,32 @@ class _rrulestr(object):
|
||||
tzinfos=tzinfos)
|
||||
else:
|
||||
raise ValueError("unsupported property: "+name)
|
||||
if (forceset or len(rrulevals) > 1 or
|
||||
rdatevals or exrulevals or exdatevals):
|
||||
if (forceset or len(rrulevals) > 1 or rdatevals
|
||||
or exrulevals or exdatevals):
|
||||
if not parser and (rdatevals or exdatevals):
|
||||
from dateutil import parser
|
||||
set = rruleset(cache=cache)
|
||||
rset = rruleset(cache=cache)
|
||||
for value in rrulevals:
|
||||
set.rrule(self._parse_rfc_rrule(value, dtstart=dtstart,
|
||||
ignoretz=ignoretz,
|
||||
tzinfos=tzinfos))
|
||||
for value in rdatevals:
|
||||
for datestr in value.split(','):
|
||||
set.rdate(parser.parse(datestr,
|
||||
ignoretz=ignoretz,
|
||||
tzinfos=tzinfos))
|
||||
for value in exrulevals:
|
||||
set.exrule(self._parse_rfc_rrule(value, dtstart=dtstart,
|
||||
rset.rrule(self._parse_rfc_rrule(value, dtstart=dtstart,
|
||||
ignoretz=ignoretz,
|
||||
tzinfos=tzinfos))
|
||||
for value in exdatevals:
|
||||
for value in rdatevals:
|
||||
for datestr in value.split(','):
|
||||
set.exdate(parser.parse(datestr,
|
||||
rset.rdate(parser.parse(datestr,
|
||||
ignoretz=ignoretz,
|
||||
tzinfos=tzinfos))
|
||||
for value in exrulevals:
|
||||
rset.exrule(self._parse_rfc_rrule(value, dtstart=dtstart,
|
||||
ignoretz=ignoretz,
|
||||
tzinfos=tzinfos))
|
||||
for value in exdatevals:
|
||||
for datestr in value.split(','):
|
||||
rset.exdate(parser.parse(datestr,
|
||||
ignoretz=ignoretz,
|
||||
tzinfos=tzinfos))
|
||||
if compatible and dtstart:
|
||||
set.rdate(dtstart)
|
||||
return set
|
||||
rset.rdate(dtstart)
|
||||
return rset
|
||||
else:
|
||||
return self._parse_rfc_rrule(rrulevals[0],
|
||||
dtstart=dtstart,
|
||||
|
||||
@ -1,19 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard Python
|
||||
datetime module.
|
||||
This module offers timezone implementations subclassing the abstract
|
||||
:py:`datetime.tzinfo` type. There are classes to handle tzfile format files
|
||||
(usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`, etc), TZ
|
||||
environment string (in all known formats), given ranges (with help from
|
||||
relative deltas), local machine timezone, fixed offset timezone, and UTC
|
||||
timezone.
|
||||
"""
|
||||
__license__ = "Simplified BSD"
|
||||
|
||||
from six import string_types, PY3
|
||||
|
||||
import datetime
|
||||
import struct
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
|
||||
from six import string_types, PY3
|
||||
|
||||
try:
|
||||
from dateutil.tzwin import tzwin, tzwinlocal
|
||||
except ImportError:
|
||||
tzwin = tzwinlocal = None
|
||||
|
||||
relativedelta = None
|
||||
parser = None
|
||||
rrule = None
|
||||
@ -21,10 +27,6 @@ rrule = None
|
||||
__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
|
||||
"tzstr", "tzical", "tzwin", "tzwinlocal", "gettz"]
|
||||
|
||||
try:
|
||||
from dateutil.tzwin import tzwin, tzwinlocal
|
||||
except (ImportError, OSError):
|
||||
tzwin, tzwinlocal = None, None
|
||||
|
||||
def tzname_in_python2(myfunc):
|
||||
"""Change unicode output into bytestrings in Python 2
|
||||
@ -42,11 +44,12 @@ def tzname_in_python2(myfunc):
|
||||
ZERO = datetime.timedelta(0)
|
||||
EPOCHORDINAL = datetime.datetime.utcfromtimestamp(0).toordinal()
|
||||
|
||||
|
||||
class tzutc(datetime.tzinfo):
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return ZERO
|
||||
|
||||
|
||||
def dst(self, dt):
|
||||
return ZERO
|
||||
|
||||
@ -66,6 +69,7 @@ class tzutc(datetime.tzinfo):
|
||||
|
||||
__reduce__ = object.__reduce__
|
||||
|
||||
|
||||
class tzoffset(datetime.tzinfo):
|
||||
|
||||
def __init__(self, name, offset):
|
||||
@ -96,6 +100,7 @@ class tzoffset(datetime.tzinfo):
|
||||
|
||||
__reduce__ = object.__reduce__
|
||||
|
||||
|
||||
class tzlocal(datetime.tzinfo):
|
||||
|
||||
_std_offset = datetime.timedelta(seconds=-time.timezone)
|
||||
@ -123,25 +128,25 @@ class tzlocal(datetime.tzinfo):
|
||||
def _isdst(self, dt):
|
||||
# We can't use mktime here. It is unstable when deciding if
|
||||
# the hour near to a change is DST or not.
|
||||
#
|
||||
#
|
||||
# timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour,
|
||||
# dt.minute, dt.second, dt.weekday(), 0, -1))
|
||||
# return time.localtime(timestamp).tm_isdst
|
||||
#
|
||||
# The code above yields the following result:
|
||||
#
|
||||
#>>> import tz, datetime
|
||||
#>>> t = tz.tzlocal()
|
||||
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||
#'BRDT'
|
||||
#>>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()
|
||||
#'BRST'
|
||||
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||
#'BRST'
|
||||
#>>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()
|
||||
#'BRDT'
|
||||
#>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||
#'BRDT'
|
||||
# >>> import tz, datetime
|
||||
# >>> t = tz.tzlocal()
|
||||
# >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||
# 'BRDT'
|
||||
# >>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()
|
||||
# 'BRST'
|
||||
# >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||
# 'BRST'
|
||||
# >>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()
|
||||
# 'BRDT'
|
||||
# >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
||||
# 'BRDT'
|
||||
#
|
||||
# Here is a more stable implementation:
|
||||
#
|
||||
@ -166,6 +171,7 @@ class tzlocal(datetime.tzinfo):
|
||||
|
||||
__reduce__ = object.__reduce__
|
||||
|
||||
|
||||
class _ttinfo(object):
|
||||
__slots__ = ["offset", "delta", "isdst", "abbr", "isstd", "isgmt"]
|
||||
|
||||
@ -205,15 +211,20 @@ class _ttinfo(object):
|
||||
if name in state:
|
||||
setattr(self, name, state[name])
|
||||
|
||||
|
||||
class tzfile(datetime.tzinfo):
|
||||
|
||||
# http://www.twinsun.com/tz/tz-link.htm
|
||||
# ftp://ftp.iana.org/tz/tz*.tar.gz
|
||||
|
||||
def __init__(self, fileobj):
|
||||
|
||||
def __init__(self, fileobj, filename=None):
|
||||
file_opened_here = False
|
||||
if isinstance(fileobj, string_types):
|
||||
self._filename = fileobj
|
||||
fileobj = open(fileobj, 'rb')
|
||||
file_opened_here = True
|
||||
elif filename is not None:
|
||||
self._filename = filename
|
||||
elif hasattr(fileobj, "name"):
|
||||
self._filename = fileobj.name
|
||||
else:
|
||||
@ -228,125 +239,128 @@ class tzfile(datetime.tzinfo):
|
||||
# six four-byte values of type long, written in a
|
||||
# ``standard'' byte order (the high-order byte
|
||||
# of the value is written first).
|
||||
try:
|
||||
if fileobj.read(4).decode() != "TZif":
|
||||
raise ValueError("magic not found")
|
||||
|
||||
if fileobj.read(4).decode() != "TZif":
|
||||
raise ValueError("magic not found")
|
||||
fileobj.read(16)
|
||||
|
||||
fileobj.read(16)
|
||||
(
|
||||
# The number of UTC/local indicators stored in the file.
|
||||
ttisgmtcnt,
|
||||
|
||||
(
|
||||
# The number of UTC/local indicators stored in the file.
|
||||
ttisgmtcnt,
|
||||
# The number of standard/wall indicators stored in the file.
|
||||
ttisstdcnt,
|
||||
|
||||
# The number of standard/wall indicators stored in the file.
|
||||
ttisstdcnt,
|
||||
|
||||
# The number of leap seconds for which data is
|
||||
# stored in the file.
|
||||
leapcnt,
|
||||
# The number of leap seconds for which data is
|
||||
# stored in the file.
|
||||
leapcnt,
|
||||
|
||||
# The number of "transition times" for which data
|
||||
# is stored in the file.
|
||||
timecnt,
|
||||
# The number of "transition times" for which data
|
||||
# is stored in the file.
|
||||
timecnt,
|
||||
|
||||
# The number of "local time types" for which data
|
||||
# is stored in the file (must not be zero).
|
||||
typecnt,
|
||||
# The number of "local time types" for which data
|
||||
# is stored in the file (must not be zero).
|
||||
typecnt,
|
||||
|
||||
# The number of characters of "time zone
|
||||
# abbreviation strings" stored in the file.
|
||||
charcnt,
|
||||
# The number of characters of "time zone
|
||||
# abbreviation strings" stored in the file.
|
||||
charcnt,
|
||||
|
||||
) = struct.unpack(">6l", fileobj.read(24))
|
||||
) = struct.unpack(">6l", fileobj.read(24))
|
||||
|
||||
# The above header is followed by tzh_timecnt four-byte
|
||||
# values of type long, sorted in ascending order.
|
||||
# These values are written in ``standard'' byte order.
|
||||
# Each is used as a transition time (as returned by
|
||||
# time(2)) at which the rules for computing local time
|
||||
# change.
|
||||
# The above header is followed by tzh_timecnt four-byte
|
||||
# values of type long, sorted in ascending order.
|
||||
# These values are written in ``standard'' byte order.
|
||||
# Each is used as a transition time (as returned by
|
||||
# time(2)) at which the rules for computing local time
|
||||
# change.
|
||||
|
||||
if timecnt:
|
||||
self._trans_list = struct.unpack(">%dl" % timecnt,
|
||||
fileobj.read(timecnt*4))
|
||||
else:
|
||||
self._trans_list = []
|
||||
if timecnt:
|
||||
self._trans_list = struct.unpack(">%dl" % timecnt,
|
||||
fileobj.read(timecnt*4))
|
||||
else:
|
||||
self._trans_list = []
|
||||
|
||||
# Next come tzh_timecnt one-byte values of type unsigned
|
||||
# char; each one tells which of the different types of
|
||||
# ``local time'' types described in the file is associated
|
||||
# with the same-indexed transition time. These values
|
||||
# serve as indices into an array of ttinfo structures that
|
||||
# appears next in the file.
|
||||
|
||||
if timecnt:
|
||||
self._trans_idx = struct.unpack(">%dB" % timecnt,
|
||||
fileobj.read(timecnt))
|
||||
else:
|
||||
self._trans_idx = []
|
||||
|
||||
# Each ttinfo structure is written as a four-byte value
|
||||
# for tt_gmtoff of type long, in a standard byte
|
||||
# order, followed by a one-byte value for tt_isdst
|
||||
# and a one-byte value for tt_abbrind. In each
|
||||
# structure, tt_gmtoff gives the number of
|
||||
# seconds to be added to UTC, tt_isdst tells whether
|
||||
# tm_isdst should be set by localtime(3), and
|
||||
# tt_abbrind serves as an index into the array of
|
||||
# time zone abbreviation characters that follow the
|
||||
# ttinfo structure(s) in the file.
|
||||
# Next come tzh_timecnt one-byte values of type unsigned
|
||||
# char; each one tells which of the different types of
|
||||
# ``local time'' types described in the file is associated
|
||||
# with the same-indexed transition time. These values
|
||||
# serve as indices into an array of ttinfo structures that
|
||||
# appears next in the file.
|
||||
|
||||
ttinfo = []
|
||||
if timecnt:
|
||||
self._trans_idx = struct.unpack(">%dB" % timecnt,
|
||||
fileobj.read(timecnt))
|
||||
else:
|
||||
self._trans_idx = []
|
||||
|
||||
for i in range(typecnt):
|
||||
ttinfo.append(struct.unpack(">lbb", fileobj.read(6)))
|
||||
# Each ttinfo structure is written as a four-byte value
|
||||
# for tt_gmtoff of type long, in a standard byte
|
||||
# order, followed by a one-byte value for tt_isdst
|
||||
# and a one-byte value for tt_abbrind. In each
|
||||
# structure, tt_gmtoff gives the number of
|
||||
# seconds to be added to UTC, tt_isdst tells whether
|
||||
# tm_isdst should be set by localtime(3), and
|
||||
# tt_abbrind serves as an index into the array of
|
||||
# time zone abbreviation characters that follow the
|
||||
# ttinfo structure(s) in the file.
|
||||
|
||||
abbr = fileobj.read(charcnt).decode()
|
||||
ttinfo = []
|
||||
|
||||
# Then there are tzh_leapcnt pairs of four-byte
|
||||
# values, written in standard byte order; the
|
||||
# first value of each pair gives the time (as
|
||||
# returned by time(2)) at which a leap second
|
||||
# occurs; the second gives the total number of
|
||||
# leap seconds to be applied after the given time.
|
||||
# The pairs of values are sorted in ascending order
|
||||
# by time.
|
||||
for i in range(typecnt):
|
||||
ttinfo.append(struct.unpack(">lbb", fileobj.read(6)))
|
||||
|
||||
# Not used, for now
|
||||
if leapcnt:
|
||||
leap = struct.unpack(">%dl" % (leapcnt*2),
|
||||
fileobj.read(leapcnt*8))
|
||||
abbr = fileobj.read(charcnt).decode()
|
||||
|
||||
# Then there are tzh_ttisstdcnt standard/wall
|
||||
# indicators, each stored as a one-byte value;
|
||||
# they tell whether the transition times associated
|
||||
# with local time types were specified as standard
|
||||
# time or wall clock time, and are used when
|
||||
# a time zone file is used in handling POSIX-style
|
||||
# time zone environment variables.
|
||||
# Then there are tzh_leapcnt pairs of four-byte
|
||||
# values, written in standard byte order; the
|
||||
# first value of each pair gives the time (as
|
||||
# returned by time(2)) at which a leap second
|
||||
# occurs; the second gives the total number of
|
||||
# leap seconds to be applied after the given time.
|
||||
# The pairs of values are sorted in ascending order
|
||||
# by time.
|
||||
|
||||
if ttisstdcnt:
|
||||
isstd = struct.unpack(">%db" % ttisstdcnt,
|
||||
fileobj.read(ttisstdcnt))
|
||||
# Not used, for now
|
||||
# if leapcnt:
|
||||
# leap = struct.unpack(">%dl" % (leapcnt*2),
|
||||
# fileobj.read(leapcnt*8))
|
||||
|
||||
# Finally, there are tzh_ttisgmtcnt UTC/local
|
||||
# indicators, each stored as a one-byte value;
|
||||
# they tell whether the transition times associated
|
||||
# with local time types were specified as UTC or
|
||||
# local time, and are used when a time zone file
|
||||
# is used in handling POSIX-style time zone envi-
|
||||
# ronment variables.
|
||||
# Then there are tzh_ttisstdcnt standard/wall
|
||||
# indicators, each stored as a one-byte value;
|
||||
# they tell whether the transition times associated
|
||||
# with local time types were specified as standard
|
||||
# time or wall clock time, and are used when
|
||||
# a time zone file is used in handling POSIX-style
|
||||
# time zone environment variables.
|
||||
|
||||
if ttisgmtcnt:
|
||||
isgmt = struct.unpack(">%db" % ttisgmtcnt,
|
||||
fileobj.read(ttisgmtcnt))
|
||||
if ttisstdcnt:
|
||||
isstd = struct.unpack(">%db" % ttisstdcnt,
|
||||
fileobj.read(ttisstdcnt))
|
||||
|
||||
# ** Everything has been read **
|
||||
# Finally, there are tzh_ttisgmtcnt UTC/local
|
||||
# indicators, each stored as a one-byte value;
|
||||
# they tell whether the transition times associated
|
||||
# with local time types were specified as UTC or
|
||||
# local time, and are used when a time zone file
|
||||
# is used in handling POSIX-style time zone envi-
|
||||
# ronment variables.
|
||||
|
||||
if ttisgmtcnt:
|
||||
isgmt = struct.unpack(">%db" % ttisgmtcnt,
|
||||
fileobj.read(ttisgmtcnt))
|
||||
|
||||
# ** Everything has been read **
|
||||
finally:
|
||||
if file_opened_here:
|
||||
fileobj.close()
|
||||
|
||||
# Build ttinfo list
|
||||
self._ttinfo_list = []
|
||||
for i in range(typecnt):
|
||||
gmtoff, isdst, abbrind = ttinfo[i]
|
||||
gmtoff, isdst, abbrind = ttinfo[i]
|
||||
# Round to full-minutes if that's not the case. Python's
|
||||
# datetime doesn't accept sub-minute timezones. Check
|
||||
# http://python.org/sf/1447945 for some information.
|
||||
@ -464,7 +478,7 @@ class tzfile(datetime.tzinfo):
|
||||
# However, this class stores historical changes in the
|
||||
# dst offset, so I belive that this wouldn't be the right
|
||||
# way to implement this.
|
||||
|
||||
|
||||
@tzname_in_python2
|
||||
def tzname(self, dt):
|
||||
if not self._ttinfo_std:
|
||||
@ -481,7 +495,6 @@ class tzfile(datetime.tzinfo):
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s)" % (self.__class__.__name__, repr(self._filename))
|
||||
|
||||
@ -490,8 +503,8 @@ class tzfile(datetime.tzinfo):
|
||||
raise ValueError("Unpickable %s class" % self.__class__.__name__)
|
||||
return (self.__class__, (self._filename,))
|
||||
|
||||
class tzrange(datetime.tzinfo):
|
||||
|
||||
class tzrange(datetime.tzinfo):
|
||||
def __init__(self, stdabbr, stdoffset=None,
|
||||
dstabbr=None, dstoffset=None,
|
||||
start=None, end=None):
|
||||
@ -512,12 +525,12 @@ class tzrange(datetime.tzinfo):
|
||||
self._dst_offset = ZERO
|
||||
if dstabbr and start is None:
|
||||
self._start_delta = relativedelta.relativedelta(
|
||||
hours=+2, month=4, day=1, weekday=relativedelta.SU(+1))
|
||||
hours=+2, month=4, day=1, weekday=relativedelta.SU(+1))
|
||||
else:
|
||||
self._start_delta = start
|
||||
if dstabbr and end is None:
|
||||
self._end_delta = relativedelta.relativedelta(
|
||||
hours=+1, month=10, day=31, weekday=relativedelta.SU(-1))
|
||||
hours=+1, month=10, day=31, weekday=relativedelta.SU(-1))
|
||||
else:
|
||||
self._end_delta = end
|
||||
|
||||
@ -570,8 +583,9 @@ class tzrange(datetime.tzinfo):
|
||||
|
||||
__reduce__ = object.__reduce__
|
||||
|
||||
|
||||
class tzstr(tzrange):
|
||||
|
||||
|
||||
def __init__(self, s):
|
||||
global parser
|
||||
if not parser:
|
||||
@ -645,9 +659,10 @@ class tzstr(tzrange):
|
||||
def __repr__(self):
|
||||
return "%s(%s)" % (self.__class__.__name__, repr(self._s))
|
||||
|
||||
|
||||
class _tzicalvtzcomp(object):
|
||||
def __init__(self, tzoffsetfrom, tzoffsetto, isdst,
|
||||
tzname=None, rrule=None):
|
||||
tzname=None, rrule=None):
|
||||
self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom)
|
||||
self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto)
|
||||
self.tzoffsetdiff = self.tzoffsetto-self.tzoffsetfrom
|
||||
@ -655,6 +670,7 @@ class _tzicalvtzcomp(object):
|
||||
self.tzname = tzname
|
||||
self.rrule = rrule
|
||||
|
||||
|
||||
class _tzicalvtz(datetime.tzinfo):
|
||||
def __init__(self, tzid, comps=[]):
|
||||
self._tzid = tzid
|
||||
@ -718,6 +734,7 @@ class _tzicalvtz(datetime.tzinfo):
|
||||
|
||||
__reduce__ = object.__reduce__
|
||||
|
||||
|
||||
class tzical(object):
|
||||
def __init__(self, fileobj):
|
||||
global rrule
|
||||
@ -726,7 +743,8 @@ class tzical(object):
|
||||
|
||||
if isinstance(fileobj, string_types):
|
||||
self._s = fileobj
|
||||
fileobj = open(fileobj, 'r') # ical should be encoded in UTF-8 with CRLF
|
||||
# ical should be encoded in UTF-8 with CRLF
|
||||
fileobj = open(fileobj, 'r')
|
||||
elif hasattr(fileobj, "name"):
|
||||
self._s = fileobj.name
|
||||
else:
|
||||
@ -754,7 +772,7 @@ class tzical(object):
|
||||
if not s:
|
||||
raise ValueError("empty offset")
|
||||
if s[0] in ('+', '-'):
|
||||
signal = (-1, +1)[s[0]=='+']
|
||||
signal = (-1, +1)[s[0] == '+']
|
||||
s = s[1:]
|
||||
else:
|
||||
signal = +1
|
||||
@ -815,7 +833,8 @@ class tzical(object):
|
||||
if not tzid:
|
||||
raise ValueError("mandatory TZID not found")
|
||||
if not comps:
|
||||
raise ValueError("at least one component is needed")
|
||||
raise ValueError(
|
||||
"at least one component is needed")
|
||||
# Process vtimezone
|
||||
self._vtz[tzid] = _tzicalvtz(tzid, comps)
|
||||
invtz = False
|
||||
@ -823,9 +842,11 @@ class tzical(object):
|
||||
if not founddtstart:
|
||||
raise ValueError("mandatory DTSTART not found")
|
||||
if tzoffsetfrom is None:
|
||||
raise ValueError("mandatory TZOFFSETFROM not found")
|
||||
raise ValueError(
|
||||
"mandatory TZOFFSETFROM not found")
|
||||
if tzoffsetto is None:
|
||||
raise ValueError("mandatory TZOFFSETFROM not found")
|
||||
raise ValueError(
|
||||
"mandatory TZOFFSETFROM not found")
|
||||
# Process component
|
||||
rr = None
|
||||
if rrulelines:
|
||||
@ -848,15 +869,18 @@ class tzical(object):
|
||||
rrulelines.append(line)
|
||||
elif name == "TZOFFSETFROM":
|
||||
if parms:
|
||||
raise ValueError("unsupported %s parm: %s "%(name, parms[0]))
|
||||
raise ValueError(
|
||||
"unsupported %s parm: %s " % (name, parms[0]))
|
||||
tzoffsetfrom = self._parse_offset(value)
|
||||
elif name == "TZOFFSETTO":
|
||||
if parms:
|
||||
raise ValueError("unsupported TZOFFSETTO parm: "+parms[0])
|
||||
raise ValueError(
|
||||
"unsupported TZOFFSETTO parm: "+parms[0])
|
||||
tzoffsetto = self._parse_offset(value)
|
||||
elif name == "TZNAME":
|
||||
if parms:
|
||||
raise ValueError("unsupported TZNAME parm: "+parms[0])
|
||||
raise ValueError(
|
||||
"unsupported TZNAME parm: "+parms[0])
|
||||
tzname = value
|
||||
elif name == "COMMENT":
|
||||
pass
|
||||
@ -865,7 +889,8 @@ class tzical(object):
|
||||
else:
|
||||
if name == "TZID":
|
||||
if parms:
|
||||
raise ValueError("unsupported TZID parm: "+parms[0])
|
||||
raise ValueError(
|
||||
"unsupported TZID parm: "+parms[0])
|
||||
tzid = value
|
||||
elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"):
|
||||
pass
|
||||
@ -886,6 +911,7 @@ else:
|
||||
TZFILES = []
|
||||
TZPATHS = []
|
||||
|
||||
|
||||
def gettz(name=None):
|
||||
tz = None
|
||||
if not name:
|
||||
@ -933,11 +959,11 @@ def gettz(name=None):
|
||||
pass
|
||||
else:
|
||||
tz = None
|
||||
if tzwin:
|
||||
if tzwin is not None:
|
||||
try:
|
||||
tz = tzwin(name)
|
||||
except OSError:
|
||||
pass
|
||||
except WindowsError:
|
||||
tz = None
|
||||
if not tz:
|
||||
from dateutil.zoneinfo import gettz
|
||||
tz = gettz(name)
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
# This code was originally contributed by Jeffrey Harris.
|
||||
import datetime
|
||||
import struct
|
||||
import winreg
|
||||
|
||||
from six.moves import winreg
|
||||
|
||||
__all__ = ["tzwin", "tzwinlocal"]
|
||||
|
||||
@ -12,8 +12,8 @@ TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
|
||||
TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones"
|
||||
TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
|
||||
|
||||
|
||||
def _settzkeyname():
|
||||
global TZKEYNAME
|
||||
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
|
||||
try:
|
||||
winreg.OpenKey(handle, TZKEYNAMENT).Close()
|
||||
@ -21,8 +21,10 @@ def _settzkeyname():
|
||||
except WindowsError:
|
||||
TZKEYNAME = TZKEYNAME9X
|
||||
handle.Close()
|
||||
return TZKEYNAME
|
||||
|
||||
TZKEYNAME = _settzkeyname()
|
||||
|
||||
_settzkeyname()
|
||||
|
||||
class tzwinbase(datetime.tzinfo):
|
||||
"""tzinfo class based on win32's timezones available in the registry."""
|
||||
@ -39,7 +41,7 @@ class tzwinbase(datetime.tzinfo):
|
||||
return datetime.timedelta(minutes=minutes)
|
||||
else:
|
||||
return datetime.timedelta(0)
|
||||
|
||||
|
||||
def tzname(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self._dstname
|
||||
@ -59,8 +61,11 @@ class tzwinbase(datetime.tzinfo):
|
||||
|
||||
def display(self):
|
||||
return self._display
|
||||
|
||||
|
||||
def _isdst(self, dt):
|
||||
if not self._dstmonth:
|
||||
# dstmonth == 0 signals the zone has no daylight saving time
|
||||
return False
|
||||
dston = picknthweekday(dt.year, self._dstmonth, self._dstdayofweek,
|
||||
self._dsthour, self._dstminute,
|
||||
self._dstweeknumber)
|
||||
@ -78,31 +83,33 @@ class tzwin(tzwinbase):
|
||||
def __init__(self, name):
|
||||
self._name = name
|
||||
|
||||
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
|
||||
tzkey = winreg.OpenKey(handle, "%s\%s" % (TZKEYNAME, name))
|
||||
keydict = valuestodict(tzkey)
|
||||
tzkey.Close()
|
||||
handle.Close()
|
||||
# multiple contexts only possible in 2.7 and 3.1, we still support 2.6
|
||||
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
||||
with winreg.OpenKey(handle,
|
||||
"%s\%s" % (TZKEYNAME, name)) as tzkey:
|
||||
keydict = valuestodict(tzkey)
|
||||
|
||||
self._stdname = keydict["Std"].encode("iso-8859-1")
|
||||
self._dstname = keydict["Dlt"].encode("iso-8859-1")
|
||||
|
||||
self._display = keydict["Display"]
|
||||
|
||||
|
||||
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
|
||||
tup = struct.unpack("=3l16h", keydict["TZI"])
|
||||
self._stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1
|
||||
self._dstoffset = self._stdoffset-tup[2] # + DaylightBias * -1
|
||||
|
||||
self._stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1
|
||||
self._dstoffset = self._stdoffset-tup[2] # + DaylightBias * -1
|
||||
|
||||
# for the meaning see the win32 TIME_ZONE_INFORMATION structure docs
|
||||
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx
|
||||
(self._stdmonth,
|
||||
self._stddayofweek, # Sunday = 0
|
||||
self._stdweeknumber, # Last = 5
|
||||
self._stddayofweek, # Sunday = 0
|
||||
self._stdweeknumber, # Last = 5
|
||||
self._stdhour,
|
||||
self._stdminute) = tup[4:9]
|
||||
|
||||
(self._dstmonth,
|
||||
self._dstdayofweek, # Sunday = 0
|
||||
self._dstweeknumber, # Last = 5
|
||||
self._dstdayofweek, # Sunday = 0
|
||||
self._dstweeknumber, # Last = 5
|
||||
self._dsthour,
|
||||
self._dstminute) = tup[12:17]
|
||||
|
||||
@ -114,61 +121,59 @@ class tzwin(tzwinbase):
|
||||
|
||||
|
||||
class tzwinlocal(tzwinbase):
|
||||
|
||||
|
||||
def __init__(self):
|
||||
|
||||
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
|
||||
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
||||
|
||||
tzlocalkey = winreg.OpenKey(handle, TZLOCALKEYNAME)
|
||||
keydict = valuestodict(tzlocalkey)
|
||||
tzlocalkey.Close()
|
||||
with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey:
|
||||
keydict = valuestodict(tzlocalkey)
|
||||
|
||||
self._stdname = keydict["StandardName"].encode("iso-8859-1")
|
||||
self._dstname = keydict["DaylightName"].encode("iso-8859-1")
|
||||
self._stdname = keydict["StandardName"].encode("iso-8859-1")
|
||||
self._dstname = keydict["DaylightName"].encode("iso-8859-1")
|
||||
|
||||
try:
|
||||
tzkey = winreg.OpenKey(handle, "%s\%s"%(TZKEYNAME, self._stdname))
|
||||
_keydict = valuestodict(tzkey)
|
||||
self._display = _keydict["Display"]
|
||||
tzkey.Close()
|
||||
except OSError:
|
||||
self._display = None
|
||||
try:
|
||||
with winreg.OpenKey(
|
||||
handle, "%s\%s" % (TZKEYNAME, self._stdname)) as tzkey:
|
||||
_keydict = valuestodict(tzkey)
|
||||
self._display = _keydict["Display"]
|
||||
except OSError:
|
||||
self._display = None
|
||||
|
||||
handle.Close()
|
||||
|
||||
self._stdoffset = -keydict["Bias"]-keydict["StandardBias"]
|
||||
self._dstoffset = self._stdoffset-keydict["DaylightBias"]
|
||||
|
||||
|
||||
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
|
||||
tup = struct.unpack("=8h", keydict["StandardStart"])
|
||||
|
||||
(self._stdmonth,
|
||||
self._stddayofweek, # Sunday = 0
|
||||
self._stdweeknumber, # Last = 5
|
||||
self._stddayofweek, # Sunday = 0
|
||||
self._stdweeknumber, # Last = 5
|
||||
self._stdhour,
|
||||
self._stdminute) = tup[1:6]
|
||||
|
||||
tup = struct.unpack("=8h", keydict["DaylightStart"])
|
||||
|
||||
(self._dstmonth,
|
||||
self._dstdayofweek, # Sunday = 0
|
||||
self._dstweeknumber, # Last = 5
|
||||
self._dstdayofweek, # Sunday = 0
|
||||
self._dstweeknumber, # Last = 5
|
||||
self._dsthour,
|
||||
self._dstminute) = tup[1:6]
|
||||
|
||||
def __reduce__(self):
|
||||
return (self.__class__, ())
|
||||
|
||||
|
||||
def picknthweekday(year, month, dayofweek, hour, minute, whichweek):
|
||||
"""dayofweek == 0 means Sunday, whichweek 5 means last instance"""
|
||||
first = datetime.datetime(year, month, 1, hour, minute)
|
||||
weekdayone = first.replace(day=((dayofweek-first.isoweekday())%7+1))
|
||||
weekdayone = first.replace(day=((dayofweek-first.isoweekday()) % 7+1))
|
||||
for n in range(whichweek):
|
||||
dt = weekdayone+(whichweek-n)*ONEWEEK
|
||||
if dt.month == month:
|
||||
return dt
|
||||
|
||||
|
||||
def valuestodict(key):
|
||||
"""Convert a registry key's values to a dictionary."""
|
||||
dict = {}
|
||||
|
||||
@ -1,109 +1,108 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (c) 2003-2005 Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
|
||||
This module offers extensions to the standard Python
|
||||
datetime module.
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
from subprocess import call
|
||||
import warnings
|
||||
import tempfile
|
||||
import shutil
|
||||
from subprocess import check_call
|
||||
from tarfile import TarFile
|
||||
from pkgutil import get_data
|
||||
from io import BytesIO
|
||||
from contextlib import closing
|
||||
|
||||
from dateutil.tz import tzfile
|
||||
|
||||
__author__ = "Tomi Pieviläinen <tomi.pievilainen@iki.fi>"
|
||||
__license__ = "Simplified BSD"
|
||||
|
||||
__all__ = ["setcachesize", "gettz", "rebuild"]
|
||||
|
||||
CACHE = []
|
||||
CACHESIZE = 10
|
||||
_ZONEFILENAME = "dateutil-zoneinfo.tar.gz"
|
||||
|
||||
# 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))
|
||||
|
||||
|
||||
class tzfile(tzfile):
|
||||
def __reduce__(self):
|
||||
return (gettz, (self._filename,))
|
||||
|
||||
def getzoneinfofile():
|
||||
filenames = sorted(os.listdir(os.path.join(os.path.dirname(__file__))))
|
||||
filenames.reverse()
|
||||
for entry in filenames:
|
||||
if entry.startswith("zoneinfo") and ".tar." in entry:
|
||||
return os.path.join(os.path.dirname(__file__), entry)
|
||||
return None
|
||||
|
||||
ZONEINFOFILE = getzoneinfofile()
|
||||
def getzoneinfofile_stream():
|
||||
try:
|
||||
return BytesIO(get_data(__name__, _ZONEFILENAME))
|
||||
except IOError as e: # TODO switch to FileNotFoundError?
|
||||
warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror))
|
||||
return None
|
||||
|
||||
del getzoneinfofile
|
||||
|
||||
def setcachesize(size):
|
||||
global CACHESIZE, CACHE
|
||||
CACHESIZE = size
|
||||
del CACHE[size:]
|
||||
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())
|
||||
# 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)
|
||||
else:
|
||||
self.zones = dict()
|
||||
|
||||
|
||||
# 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 gettz(name):
|
||||
tzinfo = None
|
||||
if ZONEINFOFILE:
|
||||
for cachedname, tzinfo in CACHE:
|
||||
if cachedname == name:
|
||||
break
|
||||
else:
|
||||
tf = TarFile.open(ZONEINFOFILE)
|
||||
try:
|
||||
zonefile = tf.extractfile(name)
|
||||
except KeyError:
|
||||
tzinfo = None
|
||||
else:
|
||||
tzinfo = tzfile(zonefile)
|
||||
tf.close()
|
||||
CACHE.insert(0, (name, tzinfo))
|
||||
del CACHE[CACHESIZE:]
|
||||
return tzinfo
|
||||
if len(_CLASS_ZONE_INSTANCE) == 0:
|
||||
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
|
||||
return _CLASS_ZONE_INSTANCE[0].zones.get(name)
|
||||
|
||||
def rebuild(filename, tag=None, format="gz"):
|
||||
|
||||
def rebuild(filename, tag=None, format="gz", zonegroups=[]):
|
||||
"""Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar*
|
||||
|
||||
filename is the timezone tarball from ftp.iana.org/tz.
|
||||
|
||||
"""
|
||||
import tempfile, shutil
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
zonedir = os.path.join(tmpdir, "zoneinfo")
|
||||
moduledir = os.path.dirname(__file__)
|
||||
if tag: tag = "-"+tag
|
||||
targetname = "zoneinfo%s.tar.%s" % (tag, format)
|
||||
try:
|
||||
tf = TarFile.open(filename)
|
||||
# The "backwards" zone file contains links to other files, so must be
|
||||
# processed as last
|
||||
for name in sorted(tf.getnames(),
|
||||
key=lambda k: k != "backward" and k or "z"):
|
||||
if not (name.endswith(".sh") or
|
||||
name.endswith(".tab") or
|
||||
name == "leapseconds"):
|
||||
with _tar_open(filename) as tf:
|
||||
for name in zonegroups:
|
||||
tf.extract(name, tmpdir)
|
||||
filepath = os.path.join(tmpdir, name)
|
||||
try:
|
||||
# zic will return errors for nontz files in the package
|
||||
# such as the Makefile or README, so check_call cannot
|
||||
# be used (or at least extra checks would be needed)
|
||||
call(["zic", "-d", zonedir, filepath])
|
||||
except OSError as e:
|
||||
if e.errno == 2:
|
||||
logging.error(
|
||||
"Could not find zic. Perhaps you need to install "
|
||||
"libc-bin or some other package that provides it, "
|
||||
"or it's not in your PATH?")
|
||||
filepaths = [os.path.join(tmpdir, n) for n in zonegroups]
|
||||
try:
|
||||
check_call(["zic", "-d", zonedir] + filepaths)
|
||||
except OSError as e:
|
||||
if e.errno == 2:
|
||||
logging.error(
|
||||
"Could not find zic. Perhaps you need to install "
|
||||
"libc-bin or some other package that provides it, "
|
||||
"or it's not in your PATH?")
|
||||
raise
|
||||
tf.close()
|
||||
target = os.path.join(moduledir, targetname)
|
||||
for entry in os.listdir(moduledir):
|
||||
if entry.startswith("zoneinfo") and ".tar." in entry:
|
||||
os.unlink(os.path.join(moduledir, entry))
|
||||
tf = TarFile.open(target, "w:%s" % format)
|
||||
for entry in os.listdir(zonedir):
|
||||
entrypath = os.path.join(zonedir, entry)
|
||||
tf.add(entrypath, entry)
|
||||
tf.close()
|
||||
target = os.path.join(moduledir, _ZONEFILENAME)
|
||||
with _tar_open(target, "w:%s" % format) as tf:
|
||||
for entry in os.listdir(zonedir):
|
||||
entrypath = os.path.join(zonedir, entry)
|
||||
tf.add(entrypath, entry)
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
BIN
awx/lib/site-packages/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz
Normal file
BIN
awx/lib/site-packages/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz
Normal file
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user