mirror of
https://github.com/SickGear/SickGear.git
synced 2024-12-02 17:33:37 +00:00
Update dateutil library 2.4.2 (d4baf97) to 2.6.1 (2f3a160).
This commit is contained in:
parent
18d8bac4a1
commit
ad3f8c9584
16 changed files with 6287 additions and 5013 deletions
|
@ -1,2 +1,2 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
__version__ = "2.4.2"
|
from ._version import VERSION as __version__
|
||||||
|
|
43
lib/dateutil/_common.py
Normal file
43
lib/dateutil/_common.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
"""
|
||||||
|
Common code used in multiple modules.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class weekday(object):
|
||||||
|
__slots__ = ["weekday", "n"]
|
||||||
|
|
||||||
|
def __init__(self, weekday, n=None):
|
||||||
|
self.weekday = weekday
|
||||||
|
self.n = n
|
||||||
|
|
||||||
|
def __call__(self, n):
|
||||||
|
if n == self.n:
|
||||||
|
return self
|
||||||
|
else:
|
||||||
|
return self.__class__(self.weekday, n)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
try:
|
||||||
|
if self.weekday != other.weekday or self.n != other.n:
|
||||||
|
return False
|
||||||
|
except AttributeError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((
|
||||||
|
self.weekday,
|
||||||
|
self.n,
|
||||||
|
))
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not (self == other)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
|
||||||
|
if not self.n:
|
||||||
|
return s
|
||||||
|
else:
|
||||||
|
return "%s(%+d)" % (s, self.n)
|
||||||
|
|
||||||
|
# vim:ts=4:sw=4:et
|
10
lib/dateutil/_version.py
Normal file
10
lib/dateutil/_version.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
"""
|
||||||
|
Contains information about the dateutil version.
|
||||||
|
"""
|
||||||
|
|
||||||
|
VERSION_MAJOR = 2
|
||||||
|
VERSION_MINOR = 6
|
||||||
|
VERSION_PATCH = 1
|
||||||
|
|
||||||
|
VERSION_TUPLE = (VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH)
|
||||||
|
VERSION = '.'.join(map(str, VERSION_TUPLE))
|
|
@ -33,9 +33,9 @@ def easter(year, method=EASTER_WESTERN):
|
||||||
|
|
||||||
These methods are represented by the constants:
|
These methods are represented by the constants:
|
||||||
|
|
||||||
EASTER_JULIAN = 1
|
* ``EASTER_JULIAN = 1``
|
||||||
EASTER_ORTHODOX = 2
|
* ``EASTER_ORTHODOX = 2``
|
||||||
EASTER_WESTERN = 3
|
* ``EASTER_WESTERN = 3``
|
||||||
|
|
||||||
The default method is method 3.
|
The default method is method 3.
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,42 +2,19 @@
|
||||||
import datetime
|
import datetime
|
||||||
import calendar
|
import calendar
|
||||||
|
|
||||||
|
import operator
|
||||||
|
from math import copysign
|
||||||
|
|
||||||
from six import integer_types
|
from six import integer_types
|
||||||
|
from warnings import warn
|
||||||
|
|
||||||
|
from ._common import weekday
|
||||||
|
|
||||||
|
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7))
|
||||||
|
|
||||||
__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
|
__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
|
||||||
|
|
||||||
|
|
||||||
class weekday(object):
|
|
||||||
__slots__ = ["weekday", "n"]
|
|
||||||
|
|
||||||
def __init__(self, weekday, n=None):
|
|
||||||
self.weekday = weekday
|
|
||||||
self.n = n
|
|
||||||
|
|
||||||
def __call__(self, n):
|
|
||||||
if n == self.n:
|
|
||||||
return self
|
|
||||||
else:
|
|
||||||
return self.__class__(self.weekday, n)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
try:
|
|
||||||
if self.weekday != other.weekday or self.n != other.n:
|
|
||||||
return False
|
|
||||||
except AttributeError:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
|
|
||||||
if not self.n:
|
|
||||||
return s
|
|
||||||
else:
|
|
||||||
return "%s(%+d)" % (s, self.n)
|
|
||||||
|
|
||||||
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 excellent
|
The relativedelta type is based on the specification of the excellent
|
||||||
|
@ -117,11 +94,18 @@ class relativedelta(object):
|
||||||
year=None, month=None, day=None, weekday=None,
|
year=None, month=None, day=None, weekday=None,
|
||||||
yearday=None, nlyearday=None,
|
yearday=None, nlyearday=None,
|
||||||
hour=None, minute=None, second=None, microsecond=None):
|
hour=None, minute=None, second=None, microsecond=None):
|
||||||
|
|
||||||
|
# Check for non-integer values in integer-only quantities
|
||||||
|
if any(x is not None and x != int(x) for x in (years, months)):
|
||||||
|
raise ValueError("Non-integer years and months are "
|
||||||
|
"ambiguous and not currently supported.")
|
||||||
|
|
||||||
if dt1 and dt2:
|
if dt1 and dt2:
|
||||||
# datetime is a subclass of date. So both must be date
|
# datetime is a subclass of date. So both must be date
|
||||||
if not (isinstance(dt1, datetime.date) and
|
if not (isinstance(dt1, datetime.date) and
|
||||||
isinstance(dt2, datetime.date)):
|
isinstance(dt2, datetime.date)):
|
||||||
raise TypeError("relativedelta only diffs datetime/date")
|
raise TypeError("relativedelta only diffs datetime/date")
|
||||||
|
|
||||||
# We allow two dates, or two datetimes, so we coerce them to be
|
# We allow two dates, or two datetimes, so we coerce them to be
|
||||||
# of the same type
|
# of the same type
|
||||||
if (isinstance(dt1, datetime.datetime) !=
|
if (isinstance(dt1, datetime.datetime) !=
|
||||||
|
@ -130,6 +114,7 @@ class relativedelta(object):
|
||||||
dt1 = datetime.datetime.fromordinal(dt1.toordinal())
|
dt1 = datetime.datetime.fromordinal(dt1.toordinal())
|
||||||
elif not isinstance(dt2, datetime.datetime):
|
elif not isinstance(dt2, datetime.datetime):
|
||||||
dt2 = datetime.datetime.fromordinal(dt2.toordinal())
|
dt2 = datetime.datetime.fromordinal(dt2.toordinal())
|
||||||
|
|
||||||
self.years = 0
|
self.years = 0
|
||||||
self.months = 0
|
self.months = 0
|
||||||
self.days = 0
|
self.days = 0
|
||||||
|
@ -148,23 +133,33 @@ class relativedelta(object):
|
||||||
self.microsecond = None
|
self.microsecond = None
|
||||||
self._has_time = 0
|
self._has_time = 0
|
||||||
|
|
||||||
months = (dt1.year*12+dt1.month)-(dt2.year*12+dt2.month)
|
# Get year / month delta between the two
|
||||||
|
months = (dt1.year - dt2.year) * 12 + (dt1.month - dt2.month)
|
||||||
self._set_months(months)
|
self._set_months(months)
|
||||||
|
|
||||||
|
# Remove the year/month delta so the timedelta is just well-defined
|
||||||
|
# time units (seconds, days and microseconds)
|
||||||
dtm = self.__radd__(dt2)
|
dtm = self.__radd__(dt2)
|
||||||
|
|
||||||
|
# If we've overshot our target, make an adjustment
|
||||||
if dt1 < dt2:
|
if dt1 < dt2:
|
||||||
while dt1 > dtm:
|
compare = operator.gt
|
||||||
months += 1
|
increment = 1
|
||||||
self._set_months(months)
|
|
||||||
dtm = self.__radd__(dt2)
|
|
||||||
else:
|
else:
|
||||||
while dt1 < dtm:
|
compare = operator.lt
|
||||||
months -= 1
|
increment = -1
|
||||||
|
|
||||||
|
while compare(dt1, dtm):
|
||||||
|
months += increment
|
||||||
self._set_months(months)
|
self._set_months(months)
|
||||||
dtm = self.__radd__(dt2)
|
dtm = self.__radd__(dt2)
|
||||||
|
|
||||||
|
# Get the timedelta between the "months-adjusted" date and dt1
|
||||||
delta = dt1 - dtm
|
delta = dt1 - dtm
|
||||||
self.seconds = delta.seconds + delta.days * 86400
|
self.seconds = delta.seconds + delta.days * 86400
|
||||||
self.microseconds = delta.microseconds
|
self.microseconds = delta.microseconds
|
||||||
else:
|
else:
|
||||||
|
# Relative information
|
||||||
self.years = years
|
self.years = years
|
||||||
self.months = months
|
self.months = months
|
||||||
self.days = days + weeks * 7
|
self.days = days + weeks * 7
|
||||||
|
@ -173,6 +168,8 @@ class relativedelta(object):
|
||||||
self.minutes = minutes
|
self.minutes = minutes
|
||||||
self.seconds = seconds
|
self.seconds = seconds
|
||||||
self.microseconds = microseconds
|
self.microseconds = microseconds
|
||||||
|
|
||||||
|
# Absolute information
|
||||||
self.year = year
|
self.year = year
|
||||||
self.month = month
|
self.month = month
|
||||||
self.day = day
|
self.day = day
|
||||||
|
@ -181,6 +178,14 @@ class relativedelta(object):
|
||||||
self.second = second
|
self.second = second
|
||||||
self.microsecond = microsecond
|
self.microsecond = microsecond
|
||||||
|
|
||||||
|
if any(x is not None and int(x) != x
|
||||||
|
for x in (year, month, day, hour,
|
||||||
|
minute, second, microsecond)):
|
||||||
|
# For now we'll deprecate floats - later it'll be an error.
|
||||||
|
warn("Non-integer value passed as absolute information. " +
|
||||||
|
"This is not a well-defined condition and will raise " +
|
||||||
|
"errors in future versions.", DeprecationWarning)
|
||||||
|
|
||||||
if isinstance(weekday, integer_types):
|
if isinstance(weekday, integer_types):
|
||||||
self.weekday = weekdays[weekday]
|
self.weekday = weekdays[weekday]
|
||||||
else:
|
else:
|
||||||
|
@ -211,27 +216,27 @@ class relativedelta(object):
|
||||||
|
|
||||||
def _fix(self):
|
def _fix(self):
|
||||||
if abs(self.microseconds) > 999999:
|
if abs(self.microseconds) > 999999:
|
||||||
s = self.microseconds//abs(self.microseconds)
|
s = _sign(self.microseconds)
|
||||||
div, mod = divmod(self.microseconds * s, 1000000)
|
div, mod = divmod(self.microseconds * s, 1000000)
|
||||||
self.microseconds = mod * s
|
self.microseconds = mod * s
|
||||||
self.seconds += div * s
|
self.seconds += div * s
|
||||||
if abs(self.seconds) > 59:
|
if abs(self.seconds) > 59:
|
||||||
s = self.seconds//abs(self.seconds)
|
s = _sign(self.seconds)
|
||||||
div, mod = divmod(self.seconds * s, 60)
|
div, mod = divmod(self.seconds * s, 60)
|
||||||
self.seconds = mod * s
|
self.seconds = mod * s
|
||||||
self.minutes += div * s
|
self.minutes += div * s
|
||||||
if abs(self.minutes) > 59:
|
if abs(self.minutes) > 59:
|
||||||
s = self.minutes//abs(self.minutes)
|
s = _sign(self.minutes)
|
||||||
div, mod = divmod(self.minutes * s, 60)
|
div, mod = divmod(self.minutes * s, 60)
|
||||||
self.minutes = mod * s
|
self.minutes = mod * s
|
||||||
self.hours += div * s
|
self.hours += div * s
|
||||||
if abs(self.hours) > 23:
|
if abs(self.hours) > 23:
|
||||||
s = self.hours//abs(self.hours)
|
s = _sign(self.hours)
|
||||||
div, mod = divmod(self.hours * s, 24)
|
div, mod = divmod(self.hours * s, 24)
|
||||||
self.hours = mod * s
|
self.hours = mod * s
|
||||||
self.days += div * s
|
self.days += div * s
|
||||||
if abs(self.months) > 11:
|
if abs(self.months) > 11:
|
||||||
s = self.months//abs(self.months)
|
s = _sign(self.months)
|
||||||
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
|
||||||
|
@ -245,6 +250,7 @@ class relativedelta(object):
|
||||||
@property
|
@property
|
||||||
def weeks(self):
|
def weeks(self):
|
||||||
return self.days // 7
|
return self.days // 7
|
||||||
|
|
||||||
@weeks.setter
|
@weeks.setter
|
||||||
def weeks(self, value):
|
def weeks(self, value):
|
||||||
self.days = self.days - (self.weeks * 7) + value * 7
|
self.days = self.days - (self.weeks * 7) + value * 7
|
||||||
|
@ -252,13 +258,48 @@ class relativedelta(object):
|
||||||
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:
|
||||||
s = self.months//abs(self.months)
|
s = _sign(self.months)
|
||||||
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
|
||||||
else:
|
else:
|
||||||
self.years = 0
|
self.years = 0
|
||||||
|
|
||||||
|
def normalized(self):
|
||||||
|
"""
|
||||||
|
Return a version of this object represented entirely using integer
|
||||||
|
values for the relative attributes.
|
||||||
|
|
||||||
|
>>> relativedelta(days=1.5, hours=2).normalized()
|
||||||
|
relativedelta(days=1, hours=14)
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns a :class:`dateutil.relativedelta.relativedelta` object.
|
||||||
|
"""
|
||||||
|
# Cascade remainders down (rounding each to roughly nearest microsecond)
|
||||||
|
days = int(self.days)
|
||||||
|
|
||||||
|
hours_f = round(self.hours + 24 * (self.days - days), 11)
|
||||||
|
hours = int(hours_f)
|
||||||
|
|
||||||
|
minutes_f = round(self.minutes + 60 * (hours_f - hours), 10)
|
||||||
|
minutes = int(minutes_f)
|
||||||
|
|
||||||
|
seconds_f = round(self.seconds + 60 * (minutes_f - minutes), 8)
|
||||||
|
seconds = int(seconds_f)
|
||||||
|
|
||||||
|
microseconds = round(self.microseconds + 1e6 * (seconds_f - seconds))
|
||||||
|
|
||||||
|
# Constructor carries overflow back up with call to _fix()
|
||||||
|
return self.__class__(years=self.years, months=self.months,
|
||||||
|
days=days, hours=hours, minutes=minutes,
|
||||||
|
seconds=seconds, microseconds=microseconds,
|
||||||
|
leapdays=self.leapdays, year=self.year,
|
||||||
|
month=self.month, day=self.day,
|
||||||
|
weekday=self.weekday, hour=self.hour,
|
||||||
|
minute=self.minute, second=self.second,
|
||||||
|
microsecond=self.microsecond)
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
if isinstance(other, relativedelta):
|
if isinstance(other, relativedelta):
|
||||||
return self.__class__(years=other.years + self.years,
|
return self.__class__(years=other.years + self.years,
|
||||||
|
@ -270,17 +311,42 @@ class relativedelta(object):
|
||||||
microseconds=(other.microseconds +
|
microseconds=(other.microseconds +
|
||||||
self.microseconds),
|
self.microseconds),
|
||||||
leapdays=other.leapdays or self.leapdays,
|
leapdays=other.leapdays or self.leapdays,
|
||||||
year=other.year or self.year,
|
year=(other.year if other.year is not None
|
||||||
month=other.month or self.month,
|
else self.year),
|
||||||
day=other.day or self.day,
|
month=(other.month if other.month is not None
|
||||||
weekday=other.weekday or self.weekday,
|
else self.month),
|
||||||
hour=other.hour or self.hour,
|
day=(other.day if other.day is not None
|
||||||
minute=other.minute or self.minute,
|
else self.day),
|
||||||
second=other.second or self.second,
|
weekday=(other.weekday if other.weekday is not None
|
||||||
microsecond=(other.microsecond or
|
else self.weekday),
|
||||||
|
hour=(other.hour if other.hour is not None
|
||||||
|
else self.hour),
|
||||||
|
minute=(other.minute if other.minute is not None
|
||||||
|
else self.minute),
|
||||||
|
second=(other.second if other.second is not None
|
||||||
|
else self.second),
|
||||||
|
microsecond=(other.microsecond if other.microsecond
|
||||||
|
is not None else
|
||||||
self.microsecond))
|
self.microsecond))
|
||||||
|
if isinstance(other, datetime.timedelta):
|
||||||
|
return self.__class__(years=self.years,
|
||||||
|
months=self.months,
|
||||||
|
days=self.days + other.days,
|
||||||
|
hours=self.hours,
|
||||||
|
minutes=self.minutes,
|
||||||
|
seconds=self.seconds + other.seconds,
|
||||||
|
microseconds=self.microseconds + other.microseconds,
|
||||||
|
leapdays=self.leapdays,
|
||||||
|
year=self.year,
|
||||||
|
month=self.month,
|
||||||
|
day=self.day,
|
||||||
|
weekday=self.weekday,
|
||||||
|
hour=self.hour,
|
||||||
|
minute=self.minute,
|
||||||
|
second=self.second,
|
||||||
|
microsecond=self.microsecond)
|
||||||
if not isinstance(other, datetime.date):
|
if not isinstance(other, datetime.date):
|
||||||
raise TypeError("unsupported type for add operation")
|
return NotImplemented
|
||||||
elif self._has_time and not isinstance(other, datetime.datetime):
|
elif self._has_time and not isinstance(other, datetime.datetime):
|
||||||
other = datetime.datetime.fromordinal(other.toordinal())
|
other = datetime.datetime.fromordinal(other.toordinal())
|
||||||
year = (self.year or other.year)+self.years
|
year = (self.year or other.year)+self.years
|
||||||
|
@ -329,7 +395,7 @@ class relativedelta(object):
|
||||||
|
|
||||||
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")
|
return NotImplemented # In case the other object defines __rsub__
|
||||||
return self.__class__(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,
|
||||||
|
@ -338,14 +404,23 @@ class relativedelta(object):
|
||||||
seconds=self.seconds - other.seconds,
|
seconds=self.seconds - other.seconds,
|
||||||
microseconds=self.microseconds - other.microseconds,
|
microseconds=self.microseconds - other.microseconds,
|
||||||
leapdays=self.leapdays or other.leapdays,
|
leapdays=self.leapdays or other.leapdays,
|
||||||
year=self.year or other.year,
|
year=(self.year if self.year is not None
|
||||||
month=self.month or other.month,
|
else other.year),
|
||||||
day=self.day or other.day,
|
month=(self.month if self.month is not None else
|
||||||
weekday=self.weekday or other.weekday,
|
other.month),
|
||||||
hour=self.hour or other.hour,
|
day=(self.day if self.day is not None else
|
||||||
minute=self.minute or other.minute,
|
other.day),
|
||||||
second=self.second or other.second,
|
weekday=(self.weekday if self.weekday is not None else
|
||||||
microsecond=self.microsecond or other.microsecond)
|
other.weekday),
|
||||||
|
hour=(self.hour if self.hour is not None else
|
||||||
|
other.hour),
|
||||||
|
minute=(self.minute if self.minute is not None else
|
||||||
|
other.minute),
|
||||||
|
second=(self.second if self.second is not None else
|
||||||
|
other.second),
|
||||||
|
microsecond=(self.microsecond if self.microsecond
|
||||||
|
is not None else
|
||||||
|
other.microsecond))
|
||||||
|
|
||||||
def __neg__(self):
|
def __neg__(self):
|
||||||
return self.__class__(years=-self.years,
|
return self.__class__(years=-self.years,
|
||||||
|
@ -386,7 +461,11 @@ class relativedelta(object):
|
||||||
__nonzero__ = __bool__
|
__nonzero__ = __bool__
|
||||||
|
|
||||||
def __mul__(self, other):
|
def __mul__(self, other):
|
||||||
|
try:
|
||||||
f = float(other)
|
f = float(other)
|
||||||
|
except TypeError:
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
return self.__class__(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),
|
||||||
|
@ -408,7 +487,7 @@ class relativedelta(object):
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if not isinstance(other, relativedelta):
|
if not isinstance(other, relativedelta):
|
||||||
return False
|
return NotImplemented
|
||||||
if self.weekday or other.weekday:
|
if self.weekday or other.weekday:
|
||||||
if not self.weekday or not other.weekday:
|
if not self.weekday or not other.weekday:
|
||||||
return False
|
return False
|
||||||
|
@ -433,11 +512,36 @@ class relativedelta(object):
|
||||||
self.second == other.second and
|
self.second == other.second and
|
||||||
self.microsecond == other.microsecond)
|
self.microsecond == other.microsecond)
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((
|
||||||
|
self.weekday,
|
||||||
|
self.years,
|
||||||
|
self.months,
|
||||||
|
self.days,
|
||||||
|
self.hours,
|
||||||
|
self.minutes,
|
||||||
|
self.seconds,
|
||||||
|
self.microseconds,
|
||||||
|
self.leapdays,
|
||||||
|
self.year,
|
||||||
|
self.month,
|
||||||
|
self.day,
|
||||||
|
self.hour,
|
||||||
|
self.minute,
|
||||||
|
self.second,
|
||||||
|
self.microsecond,
|
||||||
|
))
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not self.__eq__(other)
|
return not self.__eq__(other)
|
||||||
|
|
||||||
def __div__(self, other):
|
def __div__(self, other):
|
||||||
return self.__mul__(1/float(other))
|
try:
|
||||||
|
reciprocal = 1 / float(other)
|
||||||
|
except TypeError:
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
return self.__mul__(reciprocal)
|
||||||
|
|
||||||
__truediv__ = __div__
|
__truediv__ = __div__
|
||||||
|
|
||||||
|
@ -447,12 +551,17 @@ class relativedelta(object):
|
||||||
"hours", "minutes", "seconds", "microseconds"]:
|
"hours", "minutes", "seconds", "microseconds"]:
|
||||||
value = getattr(self, attr)
|
value = getattr(self, attr)
|
||||||
if value:
|
if value:
|
||||||
l.append("%s=%+d" % (attr, value))
|
l.append("{attr}={value:+g}".format(attr=attr, value=value))
|
||||||
for attr in ["year", "month", "day", "weekday",
|
for attr in ["year", "month", "day", "weekday",
|
||||||
"hour", "minute", "second", "microsecond"]:
|
"hour", "minute", "second", "microsecond"]:
|
||||||
value = getattr(self, attr)
|
value = getattr(self, attr)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
l.append("%s=%s" % (attr, repr(value)))
|
l.append("{attr}={value}".format(attr=attr, value=repr(value)))
|
||||||
return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
|
return "{classname}({attrs})".format(classname=self.__class__.__name__,
|
||||||
|
attrs=", ".join(l))
|
||||||
|
|
||||||
|
|
||||||
|
def _sign(x):
|
||||||
|
return int(copysign(1, x))
|
||||||
|
|
||||||
# vim:ts=4:sw=4:et
|
# vim:ts=4:sw=4:et
|
||||||
|
|
|
@ -16,7 +16,13 @@ except ImportError:
|
||||||
from fractions import gcd
|
from fractions import gcd
|
||||||
|
|
||||||
from six import advance_iterator, integer_types
|
from six import advance_iterator, integer_types
|
||||||
from six.moves import _thread
|
from six.moves import _thread, range
|
||||||
|
import heapq
|
||||||
|
|
||||||
|
from ._common import weekday as weekdaybase
|
||||||
|
|
||||||
|
# For warning about deprecation of until and count
|
||||||
|
from warnings import warn
|
||||||
|
|
||||||
__all__ = ["rrule", "rruleset", "rrulestr",
|
__all__ = ["rrule", "rruleset", "rrulestr",
|
||||||
"YEARLY", "MONTHLY", "WEEKLY", "DAILY",
|
"YEARLY", "MONTHLY", "WEEKLY", "DAILY",
|
||||||
|
@ -55,37 +61,31 @@ easter = None
|
||||||
parser = None
|
parser = None
|
||||||
|
|
||||||
|
|
||||||
class weekday(object):
|
class weekday(weekdaybase):
|
||||||
__slots__ = ["weekday", "n"]
|
"""
|
||||||
|
This version of weekday does not allow n = 0.
|
||||||
def __init__(self, weekday, n=None):
|
"""
|
||||||
|
def __init__(self, wkday, n=None):
|
||||||
if n == 0:
|
if n == 0:
|
||||||
raise ValueError("Can't create weekday with n==0")
|
raise ValueError("Can't create weekday with n==0")
|
||||||
self.weekday = weekday
|
|
||||||
self.n = n
|
|
||||||
|
|
||||||
def __call__(self, n):
|
super(weekday, self).__init__(wkday, n)
|
||||||
if n == self.n:
|
|
||||||
return self
|
|
||||||
else:
|
|
||||||
return self.__class__(self.weekday, n)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
try:
|
|
||||||
if self.weekday != other.weekday or self.n != other.n:
|
|
||||||
return False
|
|
||||||
except AttributeError:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __repr__(self):
|
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7))
|
||||||
s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
|
|
||||||
if not self.n:
|
|
||||||
return s
|
|
||||||
else:
|
|
||||||
return "%s(%+d)" % (s, self.n)
|
|
||||||
|
|
||||||
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)])
|
|
||||||
|
def _invalidates_cache(f):
|
||||||
|
"""
|
||||||
|
Decorator for rruleset methods which may invalidate the
|
||||||
|
cached length.
|
||||||
|
"""
|
||||||
|
def inner_func(self, *args, **kwargs):
|
||||||
|
rv = f(self, *args, **kwargs)
|
||||||
|
self._invalidate_cache()
|
||||||
|
return rv
|
||||||
|
|
||||||
|
return inner_func
|
||||||
|
|
||||||
|
|
||||||
class rrulebase(object):
|
class rrulebase(object):
|
||||||
|
@ -93,8 +93,7 @@ class rrulebase(object):
|
||||||
if cache:
|
if cache:
|
||||||
self._cache = []
|
self._cache = []
|
||||||
self._cache_lock = _thread.allocate_lock()
|
self._cache_lock = _thread.allocate_lock()
|
||||||
self._cache_gen = self._iter()
|
self._invalidate_cache()
|
||||||
self._cache_complete = False
|
|
||||||
else:
|
else:
|
||||||
self._cache = None
|
self._cache = None
|
||||||
self._cache_complete = False
|
self._cache_complete = False
|
||||||
|
@ -108,6 +107,17 @@ class rrulebase(object):
|
||||||
else:
|
else:
|
||||||
return self._iter_cached()
|
return self._iter_cached()
|
||||||
|
|
||||||
|
def _invalidate_cache(self):
|
||||||
|
if self._cache is not None:
|
||||||
|
self._cache = []
|
||||||
|
self._cache_complete = False
|
||||||
|
self._cache_gen = self._iter()
|
||||||
|
|
||||||
|
if self._cache_lock.locked():
|
||||||
|
self._cache_lock.release()
|
||||||
|
|
||||||
|
self._len = None
|
||||||
|
|
||||||
def _iter_cached(self):
|
def _iter_cached(self):
|
||||||
i = 0
|
i = 0
|
||||||
gen = self._cache_gen
|
gen = self._cache_gen
|
||||||
|
@ -248,13 +258,13 @@ class rrulebase(object):
|
||||||
n = 0
|
n = 0
|
||||||
for d in gen:
|
for d in gen:
|
||||||
if comp(d, dt):
|
if comp(d, dt):
|
||||||
yield d
|
|
||||||
|
|
||||||
if count is not None:
|
if count is not None:
|
||||||
n += 1
|
n += 1
|
||||||
if n >= count:
|
if n > count:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
yield d
|
||||||
|
|
||||||
def between(self, after, before, inc=False, count=1):
|
def between(self, after, before, inc=False, count=1):
|
||||||
""" Returns all the occurrences of the rrule between after and before.
|
""" Returns all the occurrences of the rrule between after and before.
|
||||||
The inc keyword defines what happens if after and/or before are
|
The inc keyword defines what happens if after and/or before are
|
||||||
|
@ -300,6 +310,29 @@ class rrule(rrulebase):
|
||||||
Where freq must be one of YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY,
|
Where freq must be one of YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY,
|
||||||
or SECONDLY.
|
or SECONDLY.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Per RFC section 3.3.10, recurrence instances falling on invalid dates
|
||||||
|
and times are ignored rather than coerced:
|
||||||
|
|
||||||
|
Recurrence rules may generate recurrence instances with an invalid
|
||||||
|
date (e.g., February 30) or nonexistent local time (e.g., 1:30 AM
|
||||||
|
on a day where the local time is moved forward by an hour at 1:00
|
||||||
|
AM). Such recurrence instances MUST be ignored and MUST NOT be
|
||||||
|
counted as part of the recurrence set.
|
||||||
|
|
||||||
|
This can lead to possibly surprising behavior when, for example, the
|
||||||
|
start date occurs at the end of the month:
|
||||||
|
|
||||||
|
>>> from dateutil.rrule import rrule, MONTHLY
|
||||||
|
>>> from datetime import datetime
|
||||||
|
>>> start_date = datetime(2014, 12, 31)
|
||||||
|
>>> list(rrule(freq=MONTHLY, count=4, dtstart=start_date))
|
||||||
|
... # doctest: +NORMALIZE_WHITESPACE
|
||||||
|
[datetime.datetime(2014, 12, 31, 0, 0),
|
||||||
|
datetime.datetime(2015, 1, 31, 0, 0),
|
||||||
|
datetime.datetime(2015, 3, 31, 0, 0),
|
||||||
|
datetime.datetime(2015, 5, 31, 0, 0)]
|
||||||
|
|
||||||
Additionally, it supports the following keyword arguments:
|
Additionally, it supports the following keyword arguments:
|
||||||
|
|
||||||
:param cache:
|
:param cache:
|
||||||
|
@ -323,11 +356,19 @@ class rrule(rrulebase):
|
||||||
calendar.setfirstweekday().
|
calendar.setfirstweekday().
|
||||||
:param count:
|
:param count:
|
||||||
How many occurrences will be generated.
|
How many occurrences will be generated.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
As of version 2.5.0, the use of the ``until`` keyword together
|
||||||
|
with the ``count`` keyword is deprecated per RFC-2445 Sec. 4.3.10.
|
||||||
:param until:
|
:param until:
|
||||||
If given, this must be a datetime instance, that will specify the
|
If given, this must be a datetime instance, that will specify the
|
||||||
limit of the recurrence. If a recurrence instance happens to be the
|
limit of the recurrence. The last recurrence in the rule is the greatest
|
||||||
same as the datetime instance given in the until keyword, this will
|
datetime that is less than or equal to the value specified in the
|
||||||
be the last occurrence.
|
``until`` parameter.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
As of version 2.5.0, the use of the ``until`` keyword together
|
||||||
|
with the ``count`` keyword is deprecated per RFC-2445 Sec. 4.3.10.
|
||||||
:param bysetpos:
|
:param bysetpos:
|
||||||
If given, it must be either an integer, or a sequence of integers,
|
If given, it must be either an integer, or a sequence of integers,
|
||||||
positive or negative. Each given integer will specify an occurrence
|
positive or negative. Each given integer will specify an occurrence
|
||||||
|
@ -405,6 +446,11 @@ class rrule(rrulebase):
|
||||||
until = datetime.datetime.fromordinal(until.toordinal())
|
until = datetime.datetime.fromordinal(until.toordinal())
|
||||||
self._until = until
|
self._until = until
|
||||||
|
|
||||||
|
if count is not None and until:
|
||||||
|
warn("Using both 'count' and 'until' is inconsistent with RFC 2445"
|
||||||
|
" and has been deprecated in dateutil. Future versions will "
|
||||||
|
"raise an error.", DeprecationWarning)
|
||||||
|
|
||||||
if wkst is None:
|
if wkst is None:
|
||||||
self._wkst = calendar.firstweekday()
|
self._wkst = calendar.firstweekday()
|
||||||
elif isinstance(wkst, integer_types):
|
elif isinstance(wkst, integer_types):
|
||||||
|
@ -489,8 +535,8 @@ class rrule(rrulebase):
|
||||||
|
|
||||||
bymonthday = set(bymonthday) # Ensure it's unique
|
bymonthday = set(bymonthday) # Ensure it's unique
|
||||||
|
|
||||||
self._bymonthday = tuple(sorted([x for x in bymonthday if x > 0]))
|
self._bymonthday = tuple(sorted(x for x in bymonthday if x > 0))
|
||||||
self._bynmonthday = tuple(sorted([x for x in bymonthday if x < 0]))
|
self._bynmonthday = tuple(sorted(x for x in bymonthday if x < 0))
|
||||||
|
|
||||||
# Storing positive numbers first, then negative numbers
|
# Storing positive numbers first, then negative numbers
|
||||||
if 'bymonthday' not in self._original_rule:
|
if 'bymonthday' not in self._original_rule:
|
||||||
|
@ -538,13 +584,13 @@ class rrule(rrulebase):
|
||||||
self._byweekday = tuple(sorted(self._byweekday))
|
self._byweekday = tuple(sorted(self._byweekday))
|
||||||
orig_byweekday = [weekday(x) for x in self._byweekday]
|
orig_byweekday = [weekday(x) for x in self._byweekday]
|
||||||
else:
|
else:
|
||||||
orig_byweekday = tuple()
|
orig_byweekday = ()
|
||||||
|
|
||||||
if self._bynweekday is not None:
|
if self._bynweekday is not None:
|
||||||
self._bynweekday = tuple(sorted(self._bynweekday))
|
self._bynweekday = tuple(sorted(self._bynweekday))
|
||||||
orig_bynweekday = [weekday(*x) for x in self._bynweekday]
|
orig_bynweekday = [weekday(*x) for x in self._bynweekday]
|
||||||
else:
|
else:
|
||||||
orig_bynweekday = tuple()
|
orig_bynweekday = ()
|
||||||
|
|
||||||
if 'byweekday' not in self._original_rule:
|
if 'byweekday' not in self._original_rule:
|
||||||
self._original_rule['byweekday'] = tuple(itertools.chain(
|
self._original_rule['byweekday'] = tuple(itertools.chain(
|
||||||
|
@ -553,7 +599,7 @@ class rrule(rrulebase):
|
||||||
# byhour
|
# byhour
|
||||||
if byhour is None:
|
if byhour is None:
|
||||||
if freq < HOURLY:
|
if freq < HOURLY:
|
||||||
self._byhour = set((dtstart.hour,))
|
self._byhour = {dtstart.hour}
|
||||||
else:
|
else:
|
||||||
self._byhour = None
|
self._byhour = None
|
||||||
else:
|
else:
|
||||||
|
@ -573,7 +619,7 @@ class rrule(rrulebase):
|
||||||
# byminute
|
# byminute
|
||||||
if byminute is None:
|
if byminute is None:
|
||||||
if freq < MINUTELY:
|
if freq < MINUTELY:
|
||||||
self._byminute = set((dtstart.minute,))
|
self._byminute = {dtstart.minute}
|
||||||
else:
|
else:
|
||||||
self._byminute = None
|
self._byminute = None
|
||||||
else:
|
else:
|
||||||
|
@ -643,11 +689,14 @@ class rrule(rrulebase):
|
||||||
parts.append('INTERVAL=' + str(self._interval))
|
parts.append('INTERVAL=' + str(self._interval))
|
||||||
|
|
||||||
if self._wkst:
|
if self._wkst:
|
||||||
parts.append('WKST=' + str(self._wkst))
|
parts.append('WKST=' + repr(weekday(self._wkst))[0:2])
|
||||||
|
|
||||||
if self._count:
|
if self._count is not None:
|
||||||
parts.append('COUNT=' + str(self._count))
|
parts.append('COUNT=' + str(self._count))
|
||||||
|
|
||||||
|
if self._until:
|
||||||
|
parts.append(self._until.strftime('UNTIL=%Y%m%dT%H%M%S'))
|
||||||
|
|
||||||
if self._original_rule.get('byweekday') is not None:
|
if self._original_rule.get('byweekday') is not None:
|
||||||
# The str() method on weekday objects doesn't generate
|
# The str() method on weekday objects doesn't generate
|
||||||
# RFC2445-compliant strings, so we should modify that.
|
# RFC2445-compliant strings, so we should modify that.
|
||||||
|
@ -681,9 +730,23 @@ class rrule(rrulebase):
|
||||||
parts.append(partfmt.format(name=name, vals=(','.join(str(v)
|
parts.append(partfmt.format(name=name, vals=(','.join(str(v)
|
||||||
for v in value))))
|
for v in value))))
|
||||||
|
|
||||||
output.append(';'.join(parts))
|
output.append('RRULE:' + ';'.join(parts))
|
||||||
return '\n'.join(output)
|
return '\n'.join(output)
|
||||||
|
|
||||||
|
def replace(self, **kwargs):
|
||||||
|
"""Return new rrule with same attributes except for those attributes given new
|
||||||
|
values by whichever keyword arguments are specified."""
|
||||||
|
new_kwargs = {"interval": self._interval,
|
||||||
|
"count": self._count,
|
||||||
|
"dtstart": self._dtstart,
|
||||||
|
"freq": self._freq,
|
||||||
|
"until": self._until,
|
||||||
|
"wkst": self._wkst,
|
||||||
|
"cache": False if self._cache is None else True }
|
||||||
|
new_kwargs.update(self._original_rule)
|
||||||
|
new_kwargs.update(kwargs)
|
||||||
|
return rrule(**new_kwargs)
|
||||||
|
|
||||||
def _iter(self):
|
def _iter(self):
|
||||||
year, month, day, hour, minute, second, weekday, yearday, _ = \
|
year, month, day, hour, minute, second, weekday, yearday, _ = \
|
||||||
self._dtstart.timetuple()
|
self._dtstart.timetuple()
|
||||||
|
@ -782,13 +845,13 @@ class rrule(rrulebase):
|
||||||
self._len = total
|
self._len = total
|
||||||
return
|
return
|
||||||
elif res >= self._dtstart:
|
elif res >= self._dtstart:
|
||||||
total += 1
|
if count is not None:
|
||||||
yield res
|
|
||||||
if count:
|
|
||||||
count -= 1
|
count -= 1
|
||||||
if not count:
|
if count < 0:
|
||||||
self._len = total
|
self._len = total
|
||||||
return
|
return
|
||||||
|
total += 1
|
||||||
|
yield res
|
||||||
else:
|
else:
|
||||||
for i in dayset[start:end]:
|
for i in dayset[start:end]:
|
||||||
if i is not None:
|
if i is not None:
|
||||||
|
@ -799,14 +862,15 @@ class rrule(rrulebase):
|
||||||
self._len = total
|
self._len = total
|
||||||
return
|
return
|
||||||
elif res >= self._dtstart:
|
elif res >= self._dtstart:
|
||||||
total += 1
|
if count is not None:
|
||||||
yield res
|
|
||||||
if count:
|
|
||||||
count -= 1
|
count -= 1
|
||||||
if not count:
|
if count < 0:
|
||||||
self._len = total
|
self._len = total
|
||||||
return
|
return
|
||||||
|
|
||||||
|
total += 1
|
||||||
|
yield res
|
||||||
|
|
||||||
# Handle frequency and interval
|
# Handle frequency and interval
|
||||||
fixday = False
|
fixday = False
|
||||||
if freq == YEARLY:
|
if freq == YEARLY:
|
||||||
|
@ -1236,7 +1300,11 @@ class rruleset(rrulebase):
|
||||||
try:
|
try:
|
||||||
self.dt = advance_iterator(self.gen)
|
self.dt = advance_iterator(self.gen)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
|
if self.genlist[0] is self:
|
||||||
|
heapq.heappop(self.genlist)
|
||||||
|
else:
|
||||||
self.genlist.remove(self)
|
self.genlist.remove(self)
|
||||||
|
heapq.heapify(self.genlist)
|
||||||
|
|
||||||
next = __next__
|
next = __next__
|
||||||
|
|
||||||
|
@ -1259,16 +1327,19 @@ class rruleset(rrulebase):
|
||||||
self._exrule = []
|
self._exrule = []
|
||||||
self._exdate = []
|
self._exdate = []
|
||||||
|
|
||||||
|
@_invalidates_cache
|
||||||
def rrule(self, rrule):
|
def rrule(self, rrule):
|
||||||
""" Include the given :py:class:`rrule` instance in the recurrence set
|
""" Include the given :py:class:`rrule` instance in the recurrence set
|
||||||
generation. """
|
generation. """
|
||||||
self._rrule.append(rrule)
|
self._rrule.append(rrule)
|
||||||
|
|
||||||
|
@_invalidates_cache
|
||||||
def rdate(self, rdate):
|
def rdate(self, rdate):
|
||||||
""" Include the given :py:class:`datetime` instance in the recurrence
|
""" Include the given :py:class:`datetime` instance in the recurrence
|
||||||
set generation. """
|
set generation. """
|
||||||
self._rdate.append(rdate)
|
self._rdate.append(rdate)
|
||||||
|
|
||||||
|
@_invalidates_cache
|
||||||
def exrule(self, exrule):
|
def exrule(self, exrule):
|
||||||
""" Include the given rrule instance in the recurrence set exclusion
|
""" Include the given rrule instance in the recurrence set exclusion
|
||||||
list. Dates which are part of the given recurrence rules will not
|
list. Dates which are part of the given recurrence rules will not
|
||||||
|
@ -1276,6 +1347,7 @@ class rruleset(rrulebase):
|
||||||
"""
|
"""
|
||||||
self._exrule.append(exrule)
|
self._exrule.append(exrule)
|
||||||
|
|
||||||
|
@_invalidates_cache
|
||||||
def exdate(self, exdate):
|
def exdate(self, exdate):
|
||||||
""" Include the given datetime instance in the recurrence set
|
""" Include the given datetime instance in the recurrence set
|
||||||
exclusion list. Dates included that way will not be generated,
|
exclusion list. Dates included that way will not be generated,
|
||||||
|
@ -1288,27 +1360,30 @@ class rruleset(rrulebase):
|
||||||
self._genitem(rlist, iter(self._rdate))
|
self._genitem(rlist, iter(self._rdate))
|
||||||
for gen in [iter(x) for x in self._rrule]:
|
for gen in [iter(x) for x in self._rrule]:
|
||||||
self._genitem(rlist, gen)
|
self._genitem(rlist, gen)
|
||||||
rlist.sort()
|
|
||||||
exlist = []
|
exlist = []
|
||||||
self._exdate.sort()
|
self._exdate.sort()
|
||||||
self._genitem(exlist, iter(self._exdate))
|
self._genitem(exlist, iter(self._exdate))
|
||||||
for gen in [iter(x) for x in self._exrule]:
|
for gen in [iter(x) for x in self._exrule]:
|
||||||
self._genitem(exlist, gen)
|
self._genitem(exlist, gen)
|
||||||
exlist.sort()
|
|
||||||
lastdt = None
|
lastdt = None
|
||||||
total = 0
|
total = 0
|
||||||
|
heapq.heapify(rlist)
|
||||||
|
heapq.heapify(exlist)
|
||||||
while rlist:
|
while rlist:
|
||||||
ritem = rlist[0]
|
ritem = rlist[0]
|
||||||
if not lastdt or lastdt != ritem.dt:
|
if not lastdt or lastdt != ritem.dt:
|
||||||
while exlist and exlist[0] < ritem:
|
while exlist and exlist[0] < ritem:
|
||||||
advance_iterator(exlist[0])
|
exitem = exlist[0]
|
||||||
exlist.sort()
|
advance_iterator(exitem)
|
||||||
|
if exlist and exlist[0] is exitem:
|
||||||
|
heapq.heapreplace(exlist, exitem)
|
||||||
if not exlist or ritem != exlist[0]:
|
if not exlist or ritem != exlist[0]:
|
||||||
total += 1
|
total += 1
|
||||||
yield ritem.dt
|
yield ritem.dt
|
||||||
lastdt = ritem.dt
|
lastdt = ritem.dt
|
||||||
advance_iterator(ritem)
|
advance_iterator(ritem)
|
||||||
rlist.sort()
|
if rlist and rlist[0] is ritem:
|
||||||
|
heapq.heapreplace(rlist, ritem)
|
||||||
self._len = total
|
self._len = total
|
||||||
|
|
||||||
|
|
||||||
|
@ -1371,7 +1446,7 @@ class _rrulestr(object):
|
||||||
splt = wday.split('(')
|
splt = wday.split('(')
|
||||||
w = splt[0]
|
w = splt[0]
|
||||||
n = int(splt[1][:-1])
|
n = int(splt[1][:-1])
|
||||||
else:
|
elif len(wday):
|
||||||
# If it's of the form +1MO
|
# If it's of the form +1MO
|
||||||
for i in range(len(wday)):
|
for i in range(len(wday)):
|
||||||
if wday[i] not in '+-0123456789':
|
if wday[i] not in '+-0123456789':
|
||||||
|
@ -1380,6 +1455,9 @@ class _rrulestr(object):
|
||||||
w = wday[i:]
|
w = wday[i:]
|
||||||
if n:
|
if n:
|
||||||
n = int(n)
|
n = int(n)
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid (empty) BYDAY specification.")
|
||||||
|
|
||||||
l.append(weekdays[self._weekday_map[w]](n))
|
l.append(weekdays[self._weekday_map[w]](n))
|
||||||
rrkwargs["byweekday"] = l
|
rrkwargs["byweekday"] = l
|
||||||
|
|
||||||
|
@ -1479,11 +1557,22 @@ class _rrulestr(object):
|
||||||
elif name == "EXDATE":
|
elif name == "EXDATE":
|
||||||
for parm in parms:
|
for parm in parms:
|
||||||
if parm != "VALUE=DATE-TIME":
|
if parm != "VALUE=DATE-TIME":
|
||||||
raise ValueError("unsupported RDATE parm: "+parm)
|
raise ValueError("unsupported EXDATE parm: "+parm)
|
||||||
exdatevals.append(value)
|
exdatevals.append(value)
|
||||||
elif name == "DTSTART":
|
elif name == "DTSTART":
|
||||||
|
# RFC 5445 3.8.2.4: The VALUE parameter is optional, but
|
||||||
|
# may be found only once.
|
||||||
|
value_found = False
|
||||||
|
valid_values = {"VALUE=DATE-TIME", "VALUE=DATE"}
|
||||||
for parm in parms:
|
for parm in parms:
|
||||||
|
if parm not in valid_values:
|
||||||
raise ValueError("unsupported DTSTART parm: "+parm)
|
raise ValueError("unsupported DTSTART parm: "+parm)
|
||||||
|
else:
|
||||||
|
if value_found:
|
||||||
|
msg = ("Duplicate value parameter found in " +
|
||||||
|
"DTSTART: " + parm)
|
||||||
|
raise ValueError(msg)
|
||||||
|
value_found = True
|
||||||
if not parser:
|
if not parser:
|
||||||
from dateutil import parser
|
from dateutil import parser
|
||||||
dtstart = parser.parse(value, ignoretz=ignoretz,
|
dtstart = parser.parse(value, ignoretz=ignoretz,
|
||||||
|
@ -1526,6 +1615,7 @@ class _rrulestr(object):
|
||||||
def __call__(self, s, **kwargs):
|
def __call__(self, s, **kwargs):
|
||||||
return self._parse_rfc(s, **kwargs)
|
return self._parse_rfc(s, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
rrulestr = _rrulestr()
|
rrulestr = _rrulestr()
|
||||||
|
|
||||||
# vim:ts=4:sw=4:et
|
# vim:ts=4:sw=4:et
|
||||||
|
|
|
@ -1,20 +1,5 @@
|
||||||
from .tz import *
|
from .tz import *
|
||||||
from six import PY3
|
|
||||||
|
|
||||||
__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
|
__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
|
||||||
"tzstr", "tzical", "tzwin", "tzwinlocal", "gettz"]
|
"tzstr", "tzical", "tzwin", "tzwinlocal", "gettz",
|
||||||
|
"enfold", "datetime_ambiguous", "datetime_exists"]
|
||||||
def tzname_in_python2(namefunc):
|
|
||||||
"""Change unicode output into bytestrings in Python 2
|
|
||||||
|
|
||||||
tzname() API changed in Python 3. It used to return bytes, but was changed
|
|
||||||
to unicode strings
|
|
||||||
"""
|
|
||||||
def adjust_encoding(*args, **kwargs):
|
|
||||||
name = namefunc(*args, **kwargs)
|
|
||||||
if name is not None and not PY3:
|
|
||||||
name = name.encode()
|
|
||||||
|
|
||||||
return name
|
|
||||||
|
|
||||||
return adjust_encoding
|
|
||||||
|
|
415
lib/dateutil/tz/_common.py
Normal file
415
lib/dateutil/tz/_common.py
Normal file
|
@ -0,0 +1,415 @@
|
||||||
|
from six import PY3
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta, tzinfo
|
||||||
|
|
||||||
|
|
||||||
|
ZERO = timedelta(0)
|
||||||
|
|
||||||
|
__all__ = ['tzname_in_python2', 'enfold']
|
||||||
|
|
||||||
|
|
||||||
|
def tzname_in_python2(namefunc):
|
||||||
|
"""Change unicode output into bytestrings in Python 2
|
||||||
|
|
||||||
|
tzname() API changed in Python 3. It used to return bytes, but was changed
|
||||||
|
to unicode strings
|
||||||
|
"""
|
||||||
|
def adjust_encoding(*args, **kwargs):
|
||||||
|
name = namefunc(*args, **kwargs)
|
||||||
|
if name is not None and not PY3:
|
||||||
|
name = name.encode()
|
||||||
|
|
||||||
|
return name
|
||||||
|
|
||||||
|
return adjust_encoding
|
||||||
|
|
||||||
|
|
||||||
|
# The following is adapted from Alexander Belopolsky's tz library
|
||||||
|
# https://github.com/abalkin/tz
|
||||||
|
if hasattr(datetime, 'fold'):
|
||||||
|
# This is the pre-python 3.6 fold situation
|
||||||
|
def enfold(dt, fold=1):
|
||||||
|
"""
|
||||||
|
Provides a unified interface for assigning the ``fold`` attribute to
|
||||||
|
datetimes both before and after the implementation of PEP-495.
|
||||||
|
|
||||||
|
:param fold:
|
||||||
|
The value for the ``fold`` attribute in the returned datetime. This
|
||||||
|
should be either 0 or 1.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns an object for which ``getattr(dt, 'fold', 0)`` returns
|
||||||
|
``fold`` for all versions of Python. In versions prior to
|
||||||
|
Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
|
||||||
|
subclass of :py:class:`datetime.datetime` with the ``fold``
|
||||||
|
attribute added, if ``fold`` is 1.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6.0
|
||||||
|
"""
|
||||||
|
return dt.replace(fold=fold)
|
||||||
|
|
||||||
|
else:
|
||||||
|
class _DatetimeWithFold(datetime):
|
||||||
|
"""
|
||||||
|
This is a class designed to provide a PEP 495-compliant interface for
|
||||||
|
Python versions before 3.6. It is used only for dates in a fold, so
|
||||||
|
the ``fold`` attribute is fixed at ``1``.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6.0
|
||||||
|
"""
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def replace(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Return a datetime with the same attributes, except for those
|
||||||
|
attributes given new values by whichever keyword arguments are
|
||||||
|
specified. Note that tzinfo=None can be specified to create a naive
|
||||||
|
datetime from an aware datetime with no conversion of date and time
|
||||||
|
data.
|
||||||
|
|
||||||
|
This is reimplemented in ``_DatetimeWithFold`` because pypy3 will
|
||||||
|
return a ``datetime.datetime`` even if ``fold`` is unchanged.
|
||||||
|
"""
|
||||||
|
argnames = (
|
||||||
|
'year', 'month', 'day', 'hour', 'minute', 'second',
|
||||||
|
'microsecond', 'tzinfo'
|
||||||
|
)
|
||||||
|
|
||||||
|
for arg, argname in zip(args, argnames):
|
||||||
|
if argname in kwargs:
|
||||||
|
raise TypeError('Duplicate argument: {}'.format(argname))
|
||||||
|
|
||||||
|
kwargs[argname] = arg
|
||||||
|
|
||||||
|
for argname in argnames:
|
||||||
|
if argname not in kwargs:
|
||||||
|
kwargs[argname] = getattr(self, argname)
|
||||||
|
|
||||||
|
dt_class = self.__class__ if kwargs.get('fold', 1) else datetime
|
||||||
|
|
||||||
|
return dt_class(**kwargs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fold(self):
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def enfold(dt, fold=1):
|
||||||
|
"""
|
||||||
|
Provides a unified interface for assigning the ``fold`` attribute to
|
||||||
|
datetimes both before and after the implementation of PEP-495.
|
||||||
|
|
||||||
|
:param fold:
|
||||||
|
The value for the ``fold`` attribute in the returned datetime. This
|
||||||
|
should be either 0 or 1.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns an object for which ``getattr(dt, 'fold', 0)`` returns
|
||||||
|
``fold`` for all versions of Python. In versions prior to
|
||||||
|
Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
|
||||||
|
subclass of :py:class:`datetime.datetime` with the ``fold``
|
||||||
|
attribute added, if ``fold`` is 1.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6.0
|
||||||
|
"""
|
||||||
|
if getattr(dt, 'fold', 0) == fold:
|
||||||
|
return dt
|
||||||
|
|
||||||
|
args = dt.timetuple()[:6]
|
||||||
|
args += (dt.microsecond, dt.tzinfo)
|
||||||
|
|
||||||
|
if fold:
|
||||||
|
return _DatetimeWithFold(*args)
|
||||||
|
else:
|
||||||
|
return datetime(*args)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_fromutc_inputs(f):
|
||||||
|
"""
|
||||||
|
The CPython version of ``fromutc`` checks that the input is a ``datetime``
|
||||||
|
object and that ``self`` is attached as its ``tzinfo``.
|
||||||
|
"""
|
||||||
|
@wraps(f)
|
||||||
|
def fromutc(self, dt):
|
||||||
|
if not isinstance(dt, datetime):
|
||||||
|
raise TypeError("fromutc() requires a datetime argument")
|
||||||
|
if dt.tzinfo is not self:
|
||||||
|
raise ValueError("dt.tzinfo is not self")
|
||||||
|
|
||||||
|
return f(self, dt)
|
||||||
|
|
||||||
|
return fromutc
|
||||||
|
|
||||||
|
|
||||||
|
class _tzinfo(tzinfo):
|
||||||
|
"""
|
||||||
|
Base class for all ``dateutil`` ``tzinfo`` objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def is_ambiguous(self, dt):
|
||||||
|
"""
|
||||||
|
Whether or not the "wall time" of a given datetime is ambiguous in this
|
||||||
|
zone.
|
||||||
|
|
||||||
|
:param dt:
|
||||||
|
A :py:class:`datetime.datetime`, naive or time zone aware.
|
||||||
|
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns ``True`` if ambiguous, ``False`` otherwise.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
dt = dt.replace(tzinfo=self)
|
||||||
|
|
||||||
|
wall_0 = enfold(dt, fold=0)
|
||||||
|
wall_1 = enfold(dt, fold=1)
|
||||||
|
|
||||||
|
same_offset = wall_0.utcoffset() == wall_1.utcoffset()
|
||||||
|
same_dt = wall_0.replace(tzinfo=None) == wall_1.replace(tzinfo=None)
|
||||||
|
|
||||||
|
return same_dt and not same_offset
|
||||||
|
|
||||||
|
def _fold_status(self, dt_utc, dt_wall):
|
||||||
|
"""
|
||||||
|
Determine the fold status of a "wall" datetime, given a representation
|
||||||
|
of the same datetime as a (naive) UTC datetime. This is calculated based
|
||||||
|
on the assumption that ``dt.utcoffset() - dt.dst()`` is constant for all
|
||||||
|
datetimes, and that this offset is the actual number of hours separating
|
||||||
|
``dt_utc`` and ``dt_wall``.
|
||||||
|
|
||||||
|
:param dt_utc:
|
||||||
|
Representation of the datetime as UTC
|
||||||
|
|
||||||
|
:param dt_wall:
|
||||||
|
Representation of the datetime as "wall time". This parameter must
|
||||||
|
either have a `fold` attribute or have a fold-naive
|
||||||
|
:class:`datetime.tzinfo` attached, otherwise the calculation may
|
||||||
|
fail.
|
||||||
|
"""
|
||||||
|
if self.is_ambiguous(dt_wall):
|
||||||
|
delta_wall = dt_wall - dt_utc
|
||||||
|
_fold = int(delta_wall == (dt_utc.utcoffset() - dt_utc.dst()))
|
||||||
|
else:
|
||||||
|
_fold = 0
|
||||||
|
|
||||||
|
return _fold
|
||||||
|
|
||||||
|
def _fold(self, dt):
|
||||||
|
return getattr(dt, 'fold', 0)
|
||||||
|
|
||||||
|
def _fromutc(self, dt):
|
||||||
|
"""
|
||||||
|
Given a timezone-aware datetime in a given timezone, calculates a
|
||||||
|
timezone-aware datetime in a new timezone.
|
||||||
|
|
||||||
|
Since this is the one time that we *know* we have an unambiguous
|
||||||
|
datetime object, we take this opportunity to determine whether the
|
||||||
|
datetime is ambiguous and in a "fold" state (e.g. if it's the first
|
||||||
|
occurence, chronologically, of the ambiguous datetime).
|
||||||
|
|
||||||
|
:param dt:
|
||||||
|
A timezone-aware :class:`datetime.datetime` object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Re-implement the algorithm from Python's datetime.py
|
||||||
|
dtoff = dt.utcoffset()
|
||||||
|
if dtoff is None:
|
||||||
|
raise ValueError("fromutc() requires a non-None utcoffset() "
|
||||||
|
"result")
|
||||||
|
|
||||||
|
# The original datetime.py code assumes that `dst()` defaults to
|
||||||
|
# zero during ambiguous times. PEP 495 inverts this presumption, so
|
||||||
|
# for pre-PEP 495 versions of python, we need to tweak the algorithm.
|
||||||
|
dtdst = dt.dst()
|
||||||
|
if dtdst is None:
|
||||||
|
raise ValueError("fromutc() requires a non-None dst() result")
|
||||||
|
delta = dtoff - dtdst
|
||||||
|
|
||||||
|
dt += delta
|
||||||
|
# Set fold=1 so we can default to being in the fold for
|
||||||
|
# ambiguous dates.
|
||||||
|
dtdst = enfold(dt, fold=1).dst()
|
||||||
|
if dtdst is None:
|
||||||
|
raise ValueError("fromutc(): dt.dst gave inconsistent "
|
||||||
|
"results; cannot convert")
|
||||||
|
return dt + dtdst
|
||||||
|
|
||||||
|
@_validate_fromutc_inputs
|
||||||
|
def fromutc(self, dt):
|
||||||
|
"""
|
||||||
|
Given a timezone-aware datetime in a given timezone, calculates a
|
||||||
|
timezone-aware datetime in a new timezone.
|
||||||
|
|
||||||
|
Since this is the one time that we *know* we have an unambiguous
|
||||||
|
datetime object, we take this opportunity to determine whether the
|
||||||
|
datetime is ambiguous and in a "fold" state (e.g. if it's the first
|
||||||
|
occurance, chronologically, of the ambiguous datetime).
|
||||||
|
|
||||||
|
:param dt:
|
||||||
|
A timezone-aware :class:`datetime.datetime` object.
|
||||||
|
"""
|
||||||
|
dt_wall = self._fromutc(dt)
|
||||||
|
|
||||||
|
# Calculate the fold status given the two datetimes.
|
||||||
|
_fold = self._fold_status(dt, dt_wall)
|
||||||
|
|
||||||
|
# Set the default fold value for ambiguous dates
|
||||||
|
return enfold(dt_wall, fold=_fold)
|
||||||
|
|
||||||
|
|
||||||
|
class tzrangebase(_tzinfo):
|
||||||
|
"""
|
||||||
|
This is an abstract base class for time zones represented by an annual
|
||||||
|
transition into and out of DST. Child classes should implement the following
|
||||||
|
methods:
|
||||||
|
|
||||||
|
* ``__init__(self, *args, **kwargs)``
|
||||||
|
* ``transitions(self, year)`` - this is expected to return a tuple of
|
||||||
|
datetimes representing the DST on and off transitions in standard
|
||||||
|
time.
|
||||||
|
|
||||||
|
A fully initialized ``tzrangebase`` subclass should also provide the
|
||||||
|
following attributes:
|
||||||
|
* ``hasdst``: Boolean whether or not the zone uses DST.
|
||||||
|
* ``_dst_offset`` / ``_std_offset``: :class:`datetime.timedelta` objects
|
||||||
|
representing the respective UTC offsets.
|
||||||
|
* ``_dst_abbr`` / ``_std_abbr``: Strings representing the timezone short
|
||||||
|
abbreviations in DST and STD, respectively.
|
||||||
|
* ``_hasdst``: Whether or not the zone has DST.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6.0
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
raise NotImplementedError('tzrangebase is an abstract base class')
|
||||||
|
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
isdst = self._isdst(dt)
|
||||||
|
|
||||||
|
if isdst is None:
|
||||||
|
return None
|
||||||
|
elif isdst:
|
||||||
|
return self._dst_offset
|
||||||
|
else:
|
||||||
|
return self._std_offset
|
||||||
|
|
||||||
|
def dst(self, dt):
|
||||||
|
isdst = self._isdst(dt)
|
||||||
|
|
||||||
|
if isdst is None:
|
||||||
|
return None
|
||||||
|
elif isdst:
|
||||||
|
return self._dst_base_offset
|
||||||
|
else:
|
||||||
|
return ZERO
|
||||||
|
|
||||||
|
@tzname_in_python2
|
||||||
|
def tzname(self, dt):
|
||||||
|
if self._isdst(dt):
|
||||||
|
return self._dst_abbr
|
||||||
|
else:
|
||||||
|
return self._std_abbr
|
||||||
|
|
||||||
|
def fromutc(self, dt):
|
||||||
|
""" Given a datetime in UTC, return local time """
|
||||||
|
if not isinstance(dt, datetime):
|
||||||
|
raise TypeError("fromutc() requires a datetime argument")
|
||||||
|
|
||||||
|
if dt.tzinfo is not self:
|
||||||
|
raise ValueError("dt.tzinfo is not self")
|
||||||
|
|
||||||
|
# Get transitions - if there are none, fixed offset
|
||||||
|
transitions = self.transitions(dt.year)
|
||||||
|
if transitions is None:
|
||||||
|
return dt + self.utcoffset(dt)
|
||||||
|
|
||||||
|
# Get the transition times in UTC
|
||||||
|
dston, dstoff = transitions
|
||||||
|
|
||||||
|
dston -= self._std_offset
|
||||||
|
dstoff -= self._std_offset
|
||||||
|
|
||||||
|
utc_transitions = (dston, dstoff)
|
||||||
|
dt_utc = dt.replace(tzinfo=None)
|
||||||
|
|
||||||
|
isdst = self._naive_isdst(dt_utc, utc_transitions)
|
||||||
|
|
||||||
|
if isdst:
|
||||||
|
dt_wall = dt + self._dst_offset
|
||||||
|
else:
|
||||||
|
dt_wall = dt + self._std_offset
|
||||||
|
|
||||||
|
_fold = int(not isdst and self.is_ambiguous(dt_wall))
|
||||||
|
|
||||||
|
return enfold(dt_wall, fold=_fold)
|
||||||
|
|
||||||
|
def is_ambiguous(self, dt):
|
||||||
|
"""
|
||||||
|
Whether or not the "wall time" of a given datetime is ambiguous in this
|
||||||
|
zone.
|
||||||
|
|
||||||
|
:param dt:
|
||||||
|
A :py:class:`datetime.datetime`, naive or time zone aware.
|
||||||
|
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns ``True`` if ambiguous, ``False`` otherwise.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6.0
|
||||||
|
"""
|
||||||
|
if not self.hasdst:
|
||||||
|
return False
|
||||||
|
|
||||||
|
start, end = self.transitions(dt.year)
|
||||||
|
|
||||||
|
dt = dt.replace(tzinfo=None)
|
||||||
|
return (end <= dt < end + self._dst_base_offset)
|
||||||
|
|
||||||
|
def _isdst(self, dt):
|
||||||
|
if not self.hasdst:
|
||||||
|
return False
|
||||||
|
elif dt is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
transitions = self.transitions(dt.year)
|
||||||
|
|
||||||
|
if transitions is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
dt = dt.replace(tzinfo=None)
|
||||||
|
|
||||||
|
isdst = self._naive_isdst(dt, transitions)
|
||||||
|
|
||||||
|
# Handle ambiguous dates
|
||||||
|
if not isdst and self.is_ambiguous(dt):
|
||||||
|
return not self._fold(dt)
|
||||||
|
else:
|
||||||
|
return isdst
|
||||||
|
|
||||||
|
def _naive_isdst(self, dt, transitions):
|
||||||
|
dston, dstoff = transitions
|
||||||
|
|
||||||
|
dt = dt.replace(tzinfo=None)
|
||||||
|
|
||||||
|
if dston < dstoff:
|
||||||
|
isdst = dston <= dt < dstoff
|
||||||
|
else:
|
||||||
|
isdst = not dstoff <= dt < dston
|
||||||
|
|
||||||
|
return isdst
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _dst_base_offset(self):
|
||||||
|
return self._dst_offset - self._std_offset
|
||||||
|
|
||||||
|
__hash__ = None
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not (self == other)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s(...)" % self.__class__.__name__
|
||||||
|
|
||||||
|
__reduce__ = object.__reduce__
|
File diff suppressed because it is too large
Load diff
|
@ -1,187 +0,0 @@
|
||||||
# This code was originally contributed by Jeffrey Harris.
|
|
||||||
import datetime
|
|
||||||
import struct
|
|
||||||
|
|
||||||
from six.moves import winreg
|
|
||||||
|
|
||||||
from .__init__ import tzname_in_python2
|
|
||||||
|
|
||||||
__all__ = ["tzwin", "tzwinlocal"]
|
|
||||||
|
|
||||||
ONEWEEK = datetime.timedelta(7)
|
|
||||||
|
|
||||||
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():
|
|
||||||
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
|
|
||||||
try:
|
|
||||||
winreg.OpenKey(handle, TZKEYNAMENT).Close()
|
|
||||||
TZKEYNAME = TZKEYNAMENT
|
|
||||||
except WindowsError:
|
|
||||||
TZKEYNAME = TZKEYNAME9X
|
|
||||||
handle.Close()
|
|
||||||
return TZKEYNAME
|
|
||||||
|
|
||||||
TZKEYNAME = _settzkeyname()
|
|
||||||
|
|
||||||
|
|
||||||
class tzwinbase(datetime.tzinfo):
|
|
||||||
"""tzinfo class based on win32's timezones available in the registry."""
|
|
||||||
|
|
||||||
def utcoffset(self, dt):
|
|
||||||
if self._isdst(dt):
|
|
||||||
return datetime.timedelta(minutes=self._dstoffset)
|
|
||||||
else:
|
|
||||||
return datetime.timedelta(minutes=self._stdoffset)
|
|
||||||
|
|
||||||
def dst(self, dt):
|
|
||||||
if self._isdst(dt):
|
|
||||||
minutes = self._dstoffset - self._stdoffset
|
|
||||||
return datetime.timedelta(minutes=minutes)
|
|
||||||
else:
|
|
||||||
return datetime.timedelta(0)
|
|
||||||
|
|
||||||
@tzname_in_python2
|
|
||||||
def tzname(self, dt):
|
|
||||||
if self._isdst(dt):
|
|
||||||
return self._dstname
|
|
||||||
else:
|
|
||||||
return self._stdname
|
|
||||||
|
|
||||||
def list():
|
|
||||||
"""Return a list of all time zones known to the system."""
|
|
||||||
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
|
|
||||||
tzkey = winreg.OpenKey(handle, TZKEYNAME)
|
|
||||||
result = [winreg.EnumKey(tzkey, i)
|
|
||||||
for i in range(winreg.QueryInfoKey(tzkey)[0])]
|
|
||||||
tzkey.Close()
|
|
||||||
handle.Close()
|
|
||||||
return result
|
|
||||||
list = staticmethod(list)
|
|
||||||
|
|
||||||
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)
|
|
||||||
dstoff = picknthweekday(dt.year, self._stdmonth, self._stddayofweek,
|
|
||||||
self._stdhour, self._stdminute,
|
|
||||||
self._stdweeknumber)
|
|
||||||
if dston < dstoff:
|
|
||||||
return dston <= dt.replace(tzinfo=None) < dstoff
|
|
||||||
else:
|
|
||||||
return not dstoff <= dt.replace(tzinfo=None) < dston
|
|
||||||
|
|
||||||
|
|
||||||
class tzwin(tzwinbase):
|
|
||||||
|
|
||||||
def __init__(self, name):
|
|
||||||
self._name = name
|
|
||||||
|
|
||||||
# multiple contexts only possible in 2.7 and 3.1, we still support 2.6
|
|
||||||
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
|
||||||
with winreg.OpenKey(handle,
|
|
||||||
"%s\%s" % (TZKEYNAME, name)) as tzkey:
|
|
||||||
keydict = valuestodict(tzkey)
|
|
||||||
|
|
||||||
self._stdname = keydict["Std"]
|
|
||||||
self._dstname = keydict["Dlt"]
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
# 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._stdhour,
|
|
||||||
self._stdminute) = tup[4:9]
|
|
||||||
|
|
||||||
(self._dstmonth,
|
|
||||||
self._dstdayofweek, # Sunday = 0
|
|
||||||
self._dstweeknumber, # Last = 5
|
|
||||||
self._dsthour,
|
|
||||||
self._dstminute) = tup[12:17]
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "tzwin(%s)" % repr(self._name)
|
|
||||||
|
|
||||||
def __reduce__(self):
|
|
||||||
return (self.__class__, (self._name,))
|
|
||||||
|
|
||||||
|
|
||||||
class tzwinlocal(tzwinbase):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
|
|
||||||
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
|
||||||
|
|
||||||
with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey:
|
|
||||||
keydict = valuestodict(tzlocalkey)
|
|
||||||
|
|
||||||
self._stdname = keydict["StandardName"]
|
|
||||||
self._dstname = keydict["DaylightName"]
|
|
||||||
|
|
||||||
try:
|
|
||||||
with winreg.OpenKey(
|
|
||||||
handle, "%s\%s" % (TZKEYNAME, self._stdname)) as tzkey:
|
|
||||||
_keydict = valuestodict(tzkey)
|
|
||||||
self._display = _keydict["Display"]
|
|
||||||
except OSError:
|
|
||||||
self._display = None
|
|
||||||
|
|
||||||
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._stdhour,
|
|
||||||
self._stdminute) = tup[1:6]
|
|
||||||
|
|
||||||
tup = struct.unpack("=8h", keydict["DaylightStart"])
|
|
||||||
|
|
||||||
(self._dstmonth,
|
|
||||||
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))
|
|
||||||
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 = {}
|
|
||||||
size = winreg.QueryInfoKey(key)[1]
|
|
||||||
for i in range(size):
|
|
||||||
data = winreg.EnumValue(key, i)
|
|
||||||
dict[data[0]] = data[1]
|
|
||||||
return dict
|
|
|
@ -3,10 +3,18 @@ import datetime
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
from six.moves import winreg
|
from six.moves import winreg
|
||||||
|
from six import text_type
|
||||||
|
|
||||||
from .__init__ import tzname_in_python2
|
try:
|
||||||
|
import ctypes
|
||||||
|
from ctypes import wintypes
|
||||||
|
except ValueError:
|
||||||
|
# ValueError is raised on non-Windows systems for some horrible reason.
|
||||||
|
raise ImportError("Running tzwin on non-Windows system")
|
||||||
|
|
||||||
__all__ = ["tzwin", "tzwinlocal"]
|
from ._common import tzrangebase
|
||||||
|
|
||||||
|
__all__ = ["tzwin", "tzwinlocal", "tzres"]
|
||||||
|
|
||||||
ONEWEEK = datetime.timedelta(7)
|
ONEWEEK = datetime.timedelta(7)
|
||||||
|
|
||||||
|
@ -25,60 +33,158 @@ def _settzkeyname():
|
||||||
handle.Close()
|
handle.Close()
|
||||||
return TZKEYNAME
|
return TZKEYNAME
|
||||||
|
|
||||||
|
|
||||||
TZKEYNAME = _settzkeyname()
|
TZKEYNAME = _settzkeyname()
|
||||||
|
|
||||||
|
|
||||||
class tzwinbase(datetime.tzinfo):
|
class tzres(object):
|
||||||
|
"""
|
||||||
|
Class for accessing `tzres.dll`, which contains timezone name related
|
||||||
|
resources.
|
||||||
|
|
||||||
|
.. versionadded:: 2.5.0
|
||||||
|
"""
|
||||||
|
p_wchar = ctypes.POINTER(wintypes.WCHAR) # Pointer to a wide char
|
||||||
|
|
||||||
|
def __init__(self, tzres_loc='tzres.dll'):
|
||||||
|
# Load the user32 DLL so we can load strings from tzres
|
||||||
|
user32 = ctypes.WinDLL('user32')
|
||||||
|
|
||||||
|
# Specify the LoadStringW function
|
||||||
|
user32.LoadStringW.argtypes = (wintypes.HINSTANCE,
|
||||||
|
wintypes.UINT,
|
||||||
|
wintypes.LPWSTR,
|
||||||
|
ctypes.c_int)
|
||||||
|
|
||||||
|
self.LoadStringW = user32.LoadStringW
|
||||||
|
self._tzres = ctypes.WinDLL(tzres_loc)
|
||||||
|
self.tzres_loc = tzres_loc
|
||||||
|
|
||||||
|
def load_name(self, offset):
|
||||||
|
"""
|
||||||
|
Load a timezone name from a DLL offset (integer).
|
||||||
|
|
||||||
|
>>> from dateutil.tzwin import tzres
|
||||||
|
>>> tzr = tzres()
|
||||||
|
>>> print(tzr.load_name(112))
|
||||||
|
'Eastern Standard Time'
|
||||||
|
|
||||||
|
:param offset:
|
||||||
|
A positive integer value referring to a string from the tzres dll.
|
||||||
|
|
||||||
|
..note:
|
||||||
|
Offsets found in the registry are generally of the form
|
||||||
|
`@tzres.dll,-114`. The offset in this case if 114, not -114.
|
||||||
|
|
||||||
|
"""
|
||||||
|
resource = self.p_wchar()
|
||||||
|
lpBuffer = ctypes.cast(ctypes.byref(resource), wintypes.LPWSTR)
|
||||||
|
nchar = self.LoadStringW(self._tzres._handle, offset, lpBuffer, 0)
|
||||||
|
return resource[:nchar]
|
||||||
|
|
||||||
|
def name_from_string(self, tzname_str):
|
||||||
|
"""
|
||||||
|
Parse strings as returned from the Windows registry into the time zone
|
||||||
|
name as defined in the registry.
|
||||||
|
|
||||||
|
>>> from dateutil.tzwin import tzres
|
||||||
|
>>> tzr = tzres()
|
||||||
|
>>> print(tzr.name_from_string('@tzres.dll,-251'))
|
||||||
|
'Dateline Daylight Time'
|
||||||
|
>>> print(tzr.name_from_string('Eastern Standard Time'))
|
||||||
|
'Eastern Standard Time'
|
||||||
|
|
||||||
|
:param tzname_str:
|
||||||
|
A timezone name string as returned from a Windows registry key.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns the localized timezone string from tzres.dll if the string
|
||||||
|
is of the form `@tzres.dll,-offset`, else returns the input string.
|
||||||
|
"""
|
||||||
|
if not tzname_str.startswith('@'):
|
||||||
|
return tzname_str
|
||||||
|
|
||||||
|
name_splt = tzname_str.split(',-')
|
||||||
|
try:
|
||||||
|
offset = int(name_splt[1])
|
||||||
|
except:
|
||||||
|
raise ValueError("Malformed timezone string.")
|
||||||
|
|
||||||
|
return self.load_name(offset)
|
||||||
|
|
||||||
|
|
||||||
|
class tzwinbase(tzrangebase):
|
||||||
"""tzinfo class based on win32's timezones available in the registry."""
|
"""tzinfo class based on win32's timezones available in the registry."""
|
||||||
|
def __init__(self):
|
||||||
|
raise NotImplementedError('tzwinbase is an abstract base class')
|
||||||
|
|
||||||
def utcoffset(self, dt):
|
def __eq__(self, other):
|
||||||
if self._isdst(dt):
|
# Compare on all relevant dimensions, including name.
|
||||||
return datetime.timedelta(minutes=self._dstoffset)
|
if not isinstance(other, tzwinbase):
|
||||||
else:
|
return NotImplemented
|
||||||
return datetime.timedelta(minutes=self._stdoffset)
|
|
||||||
|
|
||||||
def dst(self, dt):
|
return (self._std_offset == other._std_offset and
|
||||||
if self._isdst(dt):
|
self._dst_offset == other._dst_offset and
|
||||||
minutes = self._dstoffset - self._stdoffset
|
self._stddayofweek == other._stddayofweek and
|
||||||
return datetime.timedelta(minutes=minutes)
|
self._dstdayofweek == other._dstdayofweek and
|
||||||
else:
|
self._stdweeknumber == other._stdweeknumber and
|
||||||
return datetime.timedelta(0)
|
self._dstweeknumber == other._dstweeknumber and
|
||||||
|
self._stdhour == other._stdhour and
|
||||||
@tzname_in_python2
|
self._dsthour == other._dsthour and
|
||||||
def tzname(self, dt):
|
self._stdminute == other._stdminute and
|
||||||
if self._isdst(dt):
|
self._dstminute == other._dstminute and
|
||||||
return self._dstname
|
self._std_abbr == other._std_abbr and
|
||||||
else:
|
self._dst_abbr == other._dst_abbr)
|
||||||
return self._stdname
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def list():
|
def list():
|
||||||
"""Return a list of all time zones known to the system."""
|
"""Return a list of all time zones known to the system."""
|
||||||
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
|
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
||||||
tzkey = winreg.OpenKey(handle, TZKEYNAME)
|
with winreg.OpenKey(handle, TZKEYNAME) as tzkey:
|
||||||
result = [winreg.EnumKey(tzkey, i)
|
result = [winreg.EnumKey(tzkey, i)
|
||||||
for i in range(winreg.QueryInfoKey(tzkey)[0])]
|
for i in range(winreg.QueryInfoKey(tzkey)[0])]
|
||||||
tzkey.Close()
|
|
||||||
handle.Close()
|
|
||||||
return result
|
return result
|
||||||
list = staticmethod(list)
|
|
||||||
|
|
||||||
def display(self):
|
def display(self):
|
||||||
return self._display
|
return self._display
|
||||||
|
|
||||||
def _isdst(self, dt):
|
def transitions(self, year):
|
||||||
if not self._dstmonth:
|
"""
|
||||||
# dstmonth == 0 signals the zone has no daylight saving time
|
For a given year, get the DST on and off transition times, expressed
|
||||||
return False
|
always on the standard time side. For zones with no transitions, this
|
||||||
dston = picknthweekday(dt.year, self._dstmonth, self._dstdayofweek,
|
function returns ``None``.
|
||||||
|
|
||||||
|
:param year:
|
||||||
|
The year whose transitions you would like to query.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns a :class:`tuple` of :class:`datetime.datetime` objects,
|
||||||
|
``(dston, dstoff)`` for zones with an annual DST transition, or
|
||||||
|
``None`` for fixed offset zones.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self.hasdst:
|
||||||
|
return None
|
||||||
|
|
||||||
|
dston = picknthweekday(year, self._dstmonth, self._dstdayofweek,
|
||||||
self._dsthour, self._dstminute,
|
self._dsthour, self._dstminute,
|
||||||
self._dstweeknumber)
|
self._dstweeknumber)
|
||||||
dstoff = picknthweekday(dt.year, self._stdmonth, self._stddayofweek,
|
|
||||||
|
dstoff = picknthweekday(year, self._stdmonth, self._stddayofweek,
|
||||||
self._stdhour, self._stdminute,
|
self._stdhour, self._stdminute,
|
||||||
self._stdweeknumber)
|
self._stdweeknumber)
|
||||||
if dston < dstoff:
|
|
||||||
return dston <= dt.replace(tzinfo=None) < dstoff
|
# Ambiguous dates default to the STD side
|
||||||
else:
|
dstoff -= self._dst_base_offset
|
||||||
return not dstoff <= dt.replace(tzinfo=None) < dston
|
|
||||||
|
return dston, dstoff
|
||||||
|
|
||||||
|
def _get_hasdst(self):
|
||||||
|
return self._dstmonth != 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _dst_base_offset(self):
|
||||||
|
return self._dst_base_offset_
|
||||||
|
|
||||||
|
|
||||||
class tzwin(tzwinbase):
|
class tzwin(tzwinbase):
|
||||||
|
@ -86,21 +192,22 @@ class tzwin(tzwinbase):
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self._name = name
|
self._name = name
|
||||||
|
|
||||||
# multiple contexts only possible in 2.7 and 3.1, we still support 2.6
|
|
||||||
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
||||||
with winreg.OpenKey(handle,
|
tzkeyname = text_type("{kn}\\{name}").format(kn=TZKEYNAME, name=name)
|
||||||
"%s\%s" % (TZKEYNAME, name)) as tzkey:
|
with winreg.OpenKey(handle, tzkeyname) as tzkey:
|
||||||
keydict = valuestodict(tzkey)
|
keydict = valuestodict(tzkey)
|
||||||
|
|
||||||
self._stdname = keydict["Std"]
|
self._std_abbr = keydict["Std"]
|
||||||
self._dstname = keydict["Dlt"]
|
self._dst_abbr = keydict["Dlt"]
|
||||||
|
|
||||||
self._display = keydict["Display"]
|
self._display = keydict["Display"]
|
||||||
|
|
||||||
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
|
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
|
||||||
tup = struct.unpack("=3l16h", keydict["TZI"])
|
tup = struct.unpack("=3l16h", keydict["TZI"])
|
||||||
self._stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1
|
stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1
|
||||||
self._dstoffset = self._stdoffset-tup[2] # + DaylightBias * -1
|
dstoffset = stdoffset-tup[2] # + DaylightBias * -1
|
||||||
|
self._std_offset = datetime.timedelta(minutes=stdoffset)
|
||||||
|
self._dst_offset = datetime.timedelta(minutes=dstoffset)
|
||||||
|
|
||||||
# for the meaning see the win32 TIME_ZONE_INFORMATION structure docs
|
# 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
|
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx
|
||||||
|
@ -116,6 +223,9 @@ class tzwin(tzwinbase):
|
||||||
self._dsthour,
|
self._dsthour,
|
||||||
self._dstminute) = tup[12:17]
|
self._dstminute) = tup[12:17]
|
||||||
|
|
||||||
|
self._dst_base_offset_ = self._dst_offset - self._std_offset
|
||||||
|
self.hasdst = self._get_hasdst()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "tzwin(%s)" % repr(self._name)
|
return "tzwin(%s)" % repr(self._name)
|
||||||
|
|
||||||
|
@ -124,44 +234,58 @@ class tzwin(tzwinbase):
|
||||||
|
|
||||||
|
|
||||||
class tzwinlocal(tzwinbase):
|
class tzwinlocal(tzwinbase):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
||||||
|
|
||||||
with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey:
|
with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey:
|
||||||
keydict = valuestodict(tzlocalkey)
|
keydict = valuestodict(tzlocalkey)
|
||||||
|
|
||||||
self._stdname = keydict["StandardName"]
|
self._std_abbr = keydict["StandardName"]
|
||||||
self._dstname = keydict["DaylightName"]
|
self._dst_abbr = keydict["DaylightName"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with winreg.OpenKey(
|
tzkeyname = text_type('{kn}\\{sn}').format(kn=TZKEYNAME,
|
||||||
handle, "%s\%s" % (TZKEYNAME, self._stdname)) as tzkey:
|
sn=self._std_abbr)
|
||||||
|
with winreg.OpenKey(handle, tzkeyname) as tzkey:
|
||||||
_keydict = valuestodict(tzkey)
|
_keydict = valuestodict(tzkey)
|
||||||
self._display = _keydict["Display"]
|
self._display = _keydict["Display"]
|
||||||
except OSError:
|
except OSError:
|
||||||
self._display = None
|
self._display = None
|
||||||
|
|
||||||
self._stdoffset = -keydict["Bias"]-keydict["StandardBias"]
|
stdoffset = -keydict["Bias"]-keydict["StandardBias"]
|
||||||
self._dstoffset = self._stdoffset-keydict["DaylightBias"]
|
dstoffset = stdoffset-keydict["DaylightBias"]
|
||||||
|
|
||||||
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
|
self._std_offset = datetime.timedelta(minutes=stdoffset)
|
||||||
|
self._dst_offset = datetime.timedelta(minutes=dstoffset)
|
||||||
|
|
||||||
|
# For reasons unclear, in this particular key, the day of week has been
|
||||||
|
# moved to the END of the SYSTEMTIME structure.
|
||||||
tup = struct.unpack("=8h", keydict["StandardStart"])
|
tup = struct.unpack("=8h", keydict["StandardStart"])
|
||||||
|
|
||||||
(self._stdmonth,
|
(self._stdmonth,
|
||||||
self._stddayofweek, # Sunday = 0
|
|
||||||
self._stdweeknumber, # Last = 5
|
self._stdweeknumber, # Last = 5
|
||||||
self._stdhour,
|
self._stdhour,
|
||||||
self._stdminute) = tup[1:6]
|
self._stdminute) = tup[1:5]
|
||||||
|
|
||||||
|
self._stddayofweek = tup[7]
|
||||||
|
|
||||||
tup = struct.unpack("=8h", keydict["DaylightStart"])
|
tup = struct.unpack("=8h", keydict["DaylightStart"])
|
||||||
|
|
||||||
(self._dstmonth,
|
(self._dstmonth,
|
||||||
self._dstdayofweek, # Sunday = 0
|
|
||||||
self._dstweeknumber, # Last = 5
|
self._dstweeknumber, # Last = 5
|
||||||
self._dsthour,
|
self._dsthour,
|
||||||
self._dstminute) = tup[1:6]
|
self._dstminute) = tup[1:5]
|
||||||
|
|
||||||
|
self._dstdayofweek = tup[7]
|
||||||
|
|
||||||
|
self._dst_base_offset_ = self._dst_offset - self._std_offset
|
||||||
|
self.hasdst = self._get_hasdst()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "tzwinlocal()"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
# str will return the standard name, not the daylight name.
|
||||||
|
return "tzwinlocal(%s)" % repr(self._std_abbr)
|
||||||
|
|
||||||
def __reduce__(self):
|
def __reduce__(self):
|
||||||
return (self.__class__, ())
|
return (self.__class__, ())
|
||||||
|
@ -170,18 +294,38 @@ class tzwinlocal(tzwinbase):
|
||||||
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))
|
|
||||||
for n in range(whichweek):
|
# This will work if dayofweek is ISO weekday (1-7) or Microsoft-style (0-6),
|
||||||
dt = weekdayone+(whichweek-n)*ONEWEEK
|
# Because 7 % 7 = 0
|
||||||
if dt.month == month:
|
weekdayone = first.replace(day=((dayofweek - first.isoweekday()) % 7) + 1)
|
||||||
return dt
|
wd = weekdayone + ((whichweek - 1) * ONEWEEK)
|
||||||
|
if (wd.month != month):
|
||||||
|
wd -= ONEWEEK
|
||||||
|
|
||||||
|
return wd
|
||||||
|
|
||||||
|
|
||||||
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 = {}
|
dout = {}
|
||||||
size = winreg.QueryInfoKey(key)[1]
|
size = winreg.QueryInfoKey(key)[1]
|
||||||
|
tz_res = None
|
||||||
|
|
||||||
for i in range(size):
|
for i in range(size):
|
||||||
data = winreg.EnumValue(key, i)
|
key_name, value, dtype = winreg.EnumValue(key, i)
|
||||||
dict[data[0]] = data[1]
|
if dtype == winreg.REG_DWORD or dtype == winreg.REG_DWORD_LITTLE_ENDIAN:
|
||||||
return dict
|
# If it's a DWORD (32-bit integer), it's stored as unsigned - convert
|
||||||
|
# that to a proper signed integer
|
||||||
|
if value & (1 << 31):
|
||||||
|
value = value - (1 << 32)
|
||||||
|
elif dtype == winreg.REG_SZ:
|
||||||
|
# If it's a reference to the tzres DLL, load the actual string
|
||||||
|
if value.startswith('@tzres'):
|
||||||
|
tz_res = tz_res or tzres()
|
||||||
|
value = tz_res.name_from_string(value)
|
||||||
|
|
||||||
|
value = value.rstrip('\x00') # Remove trailing nulls
|
||||||
|
|
||||||
|
dout[key_name] = value
|
||||||
|
|
||||||
|
return dout
|
||||||
|
|
12
lib/dateutil/utils.py
Normal file
12
lib/dateutil/utils.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
|
||||||
|
def within_delta(dt1, dt2, delta):
|
||||||
|
"""
|
||||||
|
Useful for comparing two datetimes that may a negilible difference
|
||||||
|
to be considered equal.
|
||||||
|
"""
|
||||||
|
delta = abs(delta)
|
||||||
|
difference = dt1 - dt2
|
||||||
|
return -delta <= difference <= delta
|
|
@ -1,12 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import warnings
|
import warnings
|
||||||
import tempfile
|
|
||||||
import shutil
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from subprocess import check_call
|
|
||||||
from tarfile import TarFile
|
from tarfile import TarFile
|
||||||
from pkgutil import get_data
|
from pkgutil import get_data
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
@ -14,21 +9,11 @@ from contextlib import closing
|
||||||
|
|
||||||
from dateutil.tz import tzfile
|
from dateutil.tz import tzfile
|
||||||
|
|
||||||
from sickbeard import encodingKludge as ek
|
__all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata", "rebuild"]
|
||||||
import sickbeard
|
|
||||||
|
|
||||||
__all__ = ["gettz", "gettz_db_metadata", "rebuild"]
|
|
||||||
|
|
||||||
ZONEFILENAME = "dateutil-zoneinfo.tar.gz"
|
ZONEFILENAME = "dateutil-zoneinfo.tar.gz"
|
||||||
METADATA_FN = 'METADATA'
|
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))
|
|
||||||
|
|
||||||
|
|
||||||
class tzfile(tzfile):
|
class tzfile(tzfile):
|
||||||
def __reduce__(self):
|
def __reduce__(self):
|
||||||
|
@ -37,9 +22,7 @@ class tzfile(tzfile):
|
||||||
|
|
||||||
def getzoneinfofile_stream():
|
def getzoneinfofile_stream():
|
||||||
try:
|
try:
|
||||||
# return BytesIO(get_data(__name__, ZONEFILENAME))
|
return BytesIO(get_data(__name__, ZONEFILENAME))
|
||||||
with open(ek.ek(os.path.join, sickbeard.ZONEINFO_DIR, ZONEFILENAME), 'rb') as f:
|
|
||||||
return BytesIO(f.read())
|
|
||||||
except IOError as e: # TODO switch to FileNotFoundError?
|
except IOError as e: # TODO switch to FileNotFoundError?
|
||||||
warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror))
|
warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror))
|
||||||
return None
|
return None
|
||||||
|
@ -48,23 +31,15 @@ def getzoneinfofile_stream():
|
||||||
class ZoneInfoFile(object):
|
class ZoneInfoFile(object):
|
||||||
def __init__(self, zonefile_stream=None):
|
def __init__(self, zonefile_stream=None):
|
||||||
if zonefile_stream is not None:
|
if zonefile_stream is not None:
|
||||||
with tar_open(fileobj=zonefile_stream, mode='r') as tf:
|
with TarFile.open(fileobj=zonefile_stream) as tf:
|
||||||
# dict comprehension does not work on python2.6
|
self.zones = {zf.name: tzfile(tf.extractfile(zf), filename=zf.name)
|
||||||
# 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()
|
for zf in tf.getmembers()
|
||||||
if zf.isfile() and zf.name != METADATA_FN)
|
if zf.isfile() and zf.name != METADATA_FN}
|
||||||
# deal with links: They'll point to their parent object. Less
|
# deal with links: They'll point to their parent object. Less
|
||||||
# waste of memory
|
# waste of memory
|
||||||
# links = {zl.name: self.zones[zl.linkname]
|
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
|
for zl in tf.getmembers() if
|
||||||
zl.islnk() or zl.issym())
|
zl.islnk() or zl.issym()}
|
||||||
self.zones.update(links)
|
self.zones.update(links)
|
||||||
try:
|
try:
|
||||||
metadata_json = tf.extractfile(tf.getmember(METADATA_FN))
|
metadata_json = tf.extractfile(tf.getmember(METADATA_FN))
|
||||||
|
@ -74,20 +49,97 @@ class ZoneInfoFile(object):
|
||||||
# no metadata in tar file
|
# no metadata in tar file
|
||||||
self.metadata = None
|
self.metadata = None
|
||||||
else:
|
else:
|
||||||
self.zones = dict()
|
self.zones = {}
|
||||||
self.metadata = None
|
self.metadata = None
|
||||||
|
|
||||||
|
def get(self, name, default=None):
|
||||||
|
"""
|
||||||
|
Wrapper for :func:`ZoneInfoFile.zones.get`. This is a convenience method
|
||||||
|
for retrieving zones from the zone dictionary.
|
||||||
|
|
||||||
|
:param name:
|
||||||
|
The name of the zone to retrieve. (Generally IANA zone names)
|
||||||
|
|
||||||
|
:param default:
|
||||||
|
The value to return in the event of a missing key.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6.0
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.zones.get(name, default)
|
||||||
|
|
||||||
|
|
||||||
# The current API has gettz as a module function, although in fact it taps into
|
# 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
|
# 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
|
# will create a new "global" class instance the first time a user requests a
|
||||||
# timezone. Ugly, but adheres to the api.
|
# timezone. Ugly, but adheres to the api.
|
||||||
#
|
#
|
||||||
# TODO: deprecate this.
|
# TODO: Remove after deprecation period.
|
||||||
_CLASS_ZONE_INSTANCE = list()
|
_CLASS_ZONE_INSTANCE = []
|
||||||
|
|
||||||
|
|
||||||
|
def get_zonefile_instance(new_instance=False):
|
||||||
|
"""
|
||||||
|
This is a convenience function which provides a :class:`ZoneInfoFile`
|
||||||
|
instance using the data provided by the ``dateutil`` package. By default, it
|
||||||
|
caches a single instance of the ZoneInfoFile object and returns that.
|
||||||
|
|
||||||
|
:param new_instance:
|
||||||
|
If ``True``, a new instance of :class:`ZoneInfoFile` is instantiated and
|
||||||
|
used as the cached instance for the next call. Otherwise, new instances
|
||||||
|
are created only as necessary.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns a :class:`ZoneInfoFile` object.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6
|
||||||
|
"""
|
||||||
|
if new_instance:
|
||||||
|
zif = None
|
||||||
|
else:
|
||||||
|
zif = getattr(get_zonefile_instance, '_cached_instance', None)
|
||||||
|
|
||||||
|
if zif is None:
|
||||||
|
zif = ZoneInfoFile(getzoneinfofile_stream())
|
||||||
|
|
||||||
|
get_zonefile_instance._cached_instance = zif
|
||||||
|
|
||||||
|
return zif
|
||||||
|
|
||||||
|
|
||||||
def gettz(name):
|
def gettz(name):
|
||||||
|
"""
|
||||||
|
This retrieves a time zone from the local zoneinfo tarball that is packaged
|
||||||
|
with dateutil.
|
||||||
|
|
||||||
|
:param name:
|
||||||
|
An IANA-style time zone name, as found in the zoneinfo file.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
Returns a :class:`dateutil.tz.tzfile` time zone object.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
It is generally inadvisable to use this function, and it is only
|
||||||
|
provided for API compatibility with earlier versions. This is *not*
|
||||||
|
equivalent to ``dateutil.tz.gettz()``, which selects an appropriate
|
||||||
|
time zone based on the inputs, favoring system zoneinfo. This is ONLY
|
||||||
|
for accessing the dateutil-specific zoneinfo (which may be out of
|
||||||
|
date compared to the system zoneinfo).
|
||||||
|
|
||||||
|
.. deprecated:: 2.6
|
||||||
|
If you need to use a specific zoneinfofile over the system zoneinfo,
|
||||||
|
instantiate a :class:`dateutil.zoneinfo.ZoneInfoFile` object and call
|
||||||
|
:func:`dateutil.zoneinfo.ZoneInfoFile.get(name)` instead.
|
||||||
|
|
||||||
|
Use :func:`get_zonefile_instance` to retrieve an instance of the
|
||||||
|
dateutil-provided zoneinfo.
|
||||||
|
"""
|
||||||
|
warnings.warn("zoneinfo.gettz() will be removed in future versions, "
|
||||||
|
"to use the dateutil-provided zoneinfo files, instantiate a "
|
||||||
|
"ZoneInfoFile object and use ZoneInfoFile.zones.get() "
|
||||||
|
"instead. See the documentation for details.",
|
||||||
|
DeprecationWarning)
|
||||||
|
|
||||||
if len(_CLASS_ZONE_INSTANCE) == 0:
|
if len(_CLASS_ZONE_INSTANCE) == 0:
|
||||||
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
|
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
|
||||||
return _CLASS_ZONE_INSTANCE[0].zones.get(name)
|
return _CLASS_ZONE_INSTANCE[0].zones.get(name)
|
||||||
|
@ -98,10 +150,19 @@ def gettz_db_metadata():
|
||||||
|
|
||||||
See `zonefile_metadata`_
|
See `zonefile_metadata`_
|
||||||
|
|
||||||
:returns: A dictionary with the database metadata
|
:returns:
|
||||||
|
A dictionary with the database metadata
|
||||||
|
|
||||||
|
.. deprecated:: 2.6
|
||||||
|
See deprecation warning in :func:`zoneinfo.gettz`. To get metadata,
|
||||||
|
query the attribute ``zoneinfo.ZoneInfoFile.metadata``.
|
||||||
"""
|
"""
|
||||||
|
warnings.warn("zoneinfo.gettz_db_metadata() will be removed in future "
|
||||||
|
"versions, to use the dateutil-provided zoneinfo files, "
|
||||||
|
"ZoneInfoFile object and query the 'metadata' attribute "
|
||||||
|
"instead. See the documentation for details.",
|
||||||
|
DeprecationWarning)
|
||||||
|
|
||||||
if len(_CLASS_ZONE_INSTANCE) == 0:
|
if len(_CLASS_ZONE_INSTANCE) == 0:
|
||||||
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
|
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
|
||||||
return _CLASS_ZONE_INSTANCE[0].metadata
|
return _CLASS_ZONE_INSTANCE[0].metadata
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,9 @@ import tempfile
|
||||||
import shutil
|
import shutil
|
||||||
import json
|
import json
|
||||||
from subprocess import check_call
|
from subprocess import check_call
|
||||||
|
from tarfile import TarFile
|
||||||
|
|
||||||
from dateutil.zoneinfo import tar_open, METADATA_FN, ZONEFILENAME
|
from dateutil.zoneinfo import METADATA_FN, ZONEFILENAME
|
||||||
|
|
||||||
|
|
||||||
def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None):
|
def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None):
|
||||||
|
@ -18,7 +19,7 @@ def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None):
|
||||||
zonedir = os.path.join(tmpdir, "zoneinfo")
|
zonedir = os.path.join(tmpdir, "zoneinfo")
|
||||||
moduledir = os.path.dirname(__file__)
|
moduledir = os.path.dirname(__file__)
|
||||||
try:
|
try:
|
||||||
with tar_open(filename) as tf:
|
with TarFile.open(filename) as tf:
|
||||||
for name in zonegroups:
|
for name in zonegroups:
|
||||||
tf.extract(name, tmpdir)
|
tf.extract(name, tmpdir)
|
||||||
filepaths = [os.path.join(tmpdir, n) for n in zonegroups]
|
filepaths = [os.path.join(tmpdir, n) for n in zonegroups]
|
||||||
|
@ -31,13 +32,14 @@ def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None):
|
||||||
with open(os.path.join(zonedir, METADATA_FN), 'w') as f:
|
with open(os.path.join(zonedir, METADATA_FN), 'w') as f:
|
||||||
json.dump(metadata, f, indent=4, sort_keys=True)
|
json.dump(metadata, f, indent=4, sort_keys=True)
|
||||||
target = os.path.join(moduledir, ZONEFILENAME)
|
target = os.path.join(moduledir, ZONEFILENAME)
|
||||||
with tar_open(target, "w:%s" % format) as tf:
|
with TarFile.open(target, "w:%s" % format) as tf:
|
||||||
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)
|
||||||
finally:
|
finally:
|
||||||
shutil.rmtree(tmpdir)
|
shutil.rmtree(tmpdir)
|
||||||
|
|
||||||
|
|
||||||
def _print_on_nosuchfile(e):
|
def _print_on_nosuchfile(e):
|
||||||
"""Print helpful troubleshooting message
|
"""Print helpful troubleshooting message
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue