Update DateUtil 2.7.2 (ff03c0f) → 2.7.2 (49690ee)

This commit is contained in:
JackDandy 2018-09-04 21:58:18 +01:00
parent 74c524bc92
commit 86086b900f
10 changed files with 488 additions and 157 deletions

View file

@ -364,13 +364,23 @@ class parserinfo(object):
return self.TZOFFSET.get(name)
def convertyear(self, year, century_specified=False):
"""
Converts two-digit years to year within [-50, 49]
range of self._year (current local time)
"""
# Function contract is that the year is always positive
assert year >= 0
if year < 100 and not century_specified:
# assume current century to start
year += self._century
if abs(year - self._year) >= 50:
if year < self._year:
year += 100
else:
if year >= self._year + 50: # if too far in future
year -= 100
elif year < self._year - 50: # if too far in past
year += 100
return year
def validate(self, res):
@ -448,10 +458,37 @@ class _ymd(list):
raise ValueError('Year is already set')
self.ystridx = len(self) - 1
def _resolve_from_stridxs(self, strids):
"""
Try to resolve the identities of year/month/day elements using
ystridx, mstridx, and dstridx, if enough of these are specified.
"""
if len(self) == 3 and len(strids) == 2:
# we can back out the remaining stridx value
missing = [x for x in range(3) if x not in strids.values()]
key = [x for x in ['y', 'm', 'd'] if x not in strids]
assert len(missing) == len(key) == 1
key = key[0]
val = missing[0]
strids[key] = val
assert len(self) == len(strids) # otherwise this should not be called
out = {key: self[strids[key]] for key in strids}
return (out.get('y'), out.get('m'), out.get('d'))
def resolve_ymd(self, yearfirst, dayfirst):
len_ymd = len(self)
year, month, day = (None, None, None)
strids = (('y', self.ystridx),
('m', self.mstridx),
('d', self.dstridx))
strids = {key: val for key, val in strids if val is not None}
if (len(self) == len(strids) > 0 or
(len(self) == 3 and len(strids) == 2)):
return self._resolve_from_stridxs(strids)
mstridx = self.mstridx
if len_ymd > 3:
@ -460,13 +497,17 @@ class _ymd(list):
# One member, or two members with a month string
if mstridx is not None:
month = self[mstridx]
del self[mstridx]
# since mstridx is 0 or 1, self[mstridx-1] always
# looks up the other element
other = self[mstridx - 1]
else:
other = self[0]
if len_ymd > 1 or mstridx is None:
if self[0] > 31:
year = self[0]
if other > 31:
year = other
else:
day = self[0]
day = other
elif len_ymd == 2:
# Two members with numbers
@ -1115,16 +1156,14 @@ class parser(object):
tzdata = tzinfos(tzname, tzoffset)
else:
tzdata = tzinfos.get(tzname)
if isinstance(tzdata, datetime.tzinfo):
# handle case where tzinfo is paased an options that returns None
# eg tzinfos = {'BRST' : None}
if isinstance(tzdata, datetime.tzinfo) or tzdata is None:
tzinfo = tzdata
elif isinstance(tzdata, text_type):
tzinfo = tz.tzstr(tzdata)
elif isinstance(tzdata, integer_types):
tzinfo = tz.tzoffset(tzname, tzdata)
else:
raise ValueError("Offset must be tzinfo subclass, "
"tz string, or int offset.")
return tzinfo
def _build_tzaware(self, naive, res, tzinfos):
@ -1160,7 +1199,7 @@ class parser(object):
warnings.warn("tzname {tzname} identified but not understood. "
"Pass `tzinfos` argument in order to correctly "
"return a timezone-aware datetime. In a future "
"version, this raise an "
"version, this will raise an "
"exception.".format(tzname=res.tzname),
category=UnknownTimezoneWarning)
aware = naive
@ -1202,10 +1241,15 @@ class parser(object):
def _to_decimal(self, val):
try:
return Decimal(val)
decimal_value = Decimal(val)
# See GH 662, edge case, infinite value should not be converted via `_to_decimal`
if not decimal_value.is_finite():
raise ValueError("Converted decimal value is infinite or NaN")
except Exception as e:
msg = "Could not convert %s to decimal" % val
six.raise_from(ValueError(msg), e)
else:
return decimal_value
DEFAULTPARSER = parser()

View file

@ -4,6 +4,8 @@ This module offers a parser for ISO-8601 strings
It is intended to support all valid date, time and datetime formats per the
ISO-8601 specification.
..versionadded:: 2.7.0
"""
from datetime import datetime, timedelta, time, date
import calendar
@ -86,10 +88,12 @@ class isoparser(object):
- ``hh``
- ``hh:mm`` or ``hhmm``
- ``hh:mm:ss`` or ``hhmmss``
- ``hh:mm:ss.sss`` or ``hh:mm:ss.ssssss`` (3-6 sub-second digits)
- ``hh:mm:ss.ssssss`` (Up to 6 sub-second digits)
Midnight is a special case for `hh`, as the standard supports both
00:00 and 24:00 as a representation.
00:00 and 24:00 as a representation. The decimal separator can be
either a dot or a comma.
.. caution::
@ -124,6 +128,8 @@ class isoparser(object):
currently fail (e.g. ``2017-01-01T00:00+00:00:00``) are not
guaranteed to continue failing in future versions if they encode
a valid date.
.. versionadded:: 2.7.0
"""
components, pos = self._parse_isodate(dt_str)
@ -133,6 +139,10 @@ class isoparser(object):
else:
raise ValueError('String contains unknown ISO components')
if len(components) > 3 and components[3] == 24:
components[3] = 0
return datetime(*components) + timedelta(days=1)
return datetime(*components)
@_takes_ascii
@ -163,7 +173,10 @@ class isoparser(object):
:return:
Returns a :class:`datetime.time` object
"""
return time(*self._parse_isotime(timestr))
components = self._parse_isotime(timestr)
if components[0] == 24:
components[0] = 0
return time(*components)
@_takes_ascii
def parse_tzstr(self, tzstr, zero_as_utc=True):
@ -186,10 +199,9 @@ class isoparser(object):
return self._parse_tzstr(tzstr, zero_as_utc=zero_as_utc)
# Constants
_MICROSECOND_END_REGEX = re.compile(b'[-+Z]+')
_DATE_SEP = b'-'
_TIME_SEP = b':'
_MICRO_SEP = b'.'
_FRACTION_REGEX = re.compile(b'[\\.,]([0-9]+)')
def _parse_isodate(self, dt_str):
try:
@ -344,16 +356,14 @@ class isoparser(object):
pos += 1
if comp == 3:
# Microsecond
if timestr[pos:pos + 1] != self._MICRO_SEP:
# Fraction of a second
frac = self._FRACTION_REGEX.match(timestr[pos:])
if not frac:
continue
pos += 1
us_str = self._MICROSECOND_END_REGEX.split(timestr[pos:pos + 6],
1)[0]
us_str = frac.group(1)[:6] # Truncate to microseconds
components[comp] = int(us_str) * 10**(6 - len(us_str))
pos += len(us_str)
pos += len(frac.group())
if pos < len_str:
raise ValueError('Unused components in ISO string')
@ -362,7 +372,6 @@ class isoparser(object):
# Standard supports 00:00 and 24:00 as representations of midnight
if any(component != 0 for component in components[1:4]):
raise ValueError('Hour may only be 24 at 24:00:00.000')
components[0] = 0
return components

View file

@ -17,8 +17,12 @@ __all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
class relativedelta(object):
"""
The relativedelta type is based on the specification of the excellent
work done by M.-A. Lemburg in his
The relativedelta type is designed to be applied to an existing datetime and
can replace specific components of that datetime, or represents an interval
of time.
It is based on the specification of the excellent work done by M.-A. Lemburg
in his
`mx.DateTime <https://www.egenix.com/products/python/mxBase/mxDateTime/>`_ extension.
However, notice that this type does *NOT* implement the same algorithm as
his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.
@ -45,11 +49,15 @@ class relativedelta(object):
with the information in the relativedelta.
weekday:
One of the weekday instances (MO, TU, etc). These instances may
receive a parameter N, specifying the Nth weekday, which could
be positive or negative (like MO(+1) or MO(-2). Not specifying
it is the same as specifying +1. You can also use an integer,
where 0=MO.
One of the weekday instances (MO, TU, etc) available in the
relativedelta module. These instances may receive a parameter N,
specifying the Nth weekday, which could be positive or negative
(like MO(+1) or MO(-2)). Not specifying it is the same as specifying
+1. You can also use an integer, where 0=MO. This argument is always
relative e.g. if the calculated date is already Monday, using MO(1)
or MO(-1) won't change the day. To effectively make it absolute, use
it in combination with the day argument (e.g. day=1, MO(1) for first
Monday of the month).
leapdays:
Will add given days to the date found, if year is a leap
@ -59,33 +67,39 @@ class relativedelta(object):
Set the yearday or the non-leap year day (jump leap days).
These are converted to day/month/leapdays information.
Here is the behavior of operations with relativedelta:
There are relative and absolute forms of the keyword
arguments. The plural is relative, and the singular is
absolute. For each argument in the order below, the absolute form
is applied first (by setting each attribute to that value) and
then the relative form (by adding the value to the attribute).
1. Calculate the absolute year, using the 'year' argument, or the
original datetime year, if the argument is not present.
The order of attributes considered when this relativedelta is
added to a datetime is:
2. Add the relative 'years' argument to the absolute year.
1. Year
2. Month
3. Day
4. Hours
5. Minutes
6. Seconds
7. Microseconds
3. Do steps 1 and 2 for month/months.
Finally, weekday is applied, using the rule described above.
4. Calculate the absolute day, using the 'day' argument, or the
original datetime day, if the argument is not present. Then,
subtract from the day until it fits in the year and month
found after their operations.
For example
5. Add the relative 'days' argument to the absolute day. Notice
that the 'weeks' argument is multiplied by 7 and added to
'days'.
>>> from datetime import datetime
>>> from dateutil.relativedelta import relativedelta, MO
>>> dt = datetime(2018, 4, 9, 13, 37, 0)
>>> delta = relativedelta(hours=25, day=1, weekday=MO(1))
>>> dt + delta
datetime.datetime(2018, 4, 2, 14, 37)
6. Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds,
microsecond/microseconds.
First, the day is set to 1 (the first of the month), then 25 hours
are added, to get to the 2nd day and 14th hour, finally the
weekday is applied, but since the 2nd is already a Monday there is
no effect.
7. If the 'weekday' argument is present, calculate the weekday,
with the given (wday, nth) tuple. wday is the index of the
weekday (0-6, 0=Mon), and nth is the number of weeks to add
forward or backward, depending on its signal. Notice that if
the calculated date is already Monday, for example, using
(0, 1) or (0, -1) won't change the day.
"""
def __init__(self, dt1=None, dt2=None,
@ -271,7 +285,7 @@ class relativedelta(object):
values for the relative attributes.
>>> relativedelta(days=1.5, hours=2).normalized()
relativedelta(days=1, hours=14)
relativedelta(days=+1, hours=+14)
:return:
Returns a :class:`dateutil.relativedelta.relativedelta` object.

View file

@ -337,10 +337,6 @@ class rrule(rrulebase):
Additionally, it supports the following keyword arguments:
:param cache:
If given, it must be a boolean value specifying to enable or disable
caching of results. If you will use the same rrule instance multiple
times, enabling caching will improve the performance considerably.
:param dtstart:
The recurrence start. Besides being the base for the recurrence,
missing parameters in the final recurrence instances will also be
@ -357,20 +353,26 @@ class rrule(rrulebase):
from calendar.firstweekday(), and may be modified by
calendar.setfirstweekday().
:param count:
How many occurrences will be generated.
If given, this determines 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-5545 Sec. 3.3.10.
As of version 2.5.0, the use of the keyword ``until`` in conjunction
with ``count`` is deprecated, to make sure ``dateutil`` is fully
compliant with `RFC-5545 Sec. 3.3.10 <https://tools.ietf.org/
html/rfc5545#section-3.3.10>`_. Therefore, ``until`` and ``count``
**must not** occur in the same call to ``rrule``.
:param until:
If given, this must be a datetime instance, that will specify the
If given, this must be a datetime instance specifying the upper-bound
limit of the recurrence. The last recurrence in the rule is the greatest
datetime that is less than or equal to the value specified in the
``until`` parameter.
.. note::
As of version 2.5.0, the use of the ``until`` keyword together
with the ``count`` keyword is deprecated per RFC-5545 Sec. 3.3.10.
As of version 2.5.0, the use of the keyword ``until`` in conjunction
with ``count`` is deprecated, to make sure ``dateutil`` is fully
compliant with `RFC-5545 Sec. 3.3.10 <https://tools.ietf.org/
html/rfc5545#section-3.3.10>`_. Therefore, ``until`` and ``count``
**must not** occur in the same call to ``rrule``.
:param bysetpos:
If given, it must be either an integer, or a sequence of integers,
positive or negative. Each given integer will specify an occurrence
@ -387,6 +389,11 @@ class rrule(rrulebase):
:param byyearday:
If given, it must be either an integer, or a sequence of integers,
meaning the year days to apply the recurrence to.
:param byeaster:
If given, it must be either an integer, or a sequence of integers,
positive or negative. Each integer will define an offset from the
Easter Sunday. Passing the offset 0 to byeaster will yield the Easter
Sunday itself. This is an extension to the RFC specification.
:param byweekno:
If given, it must be either an integer, or a sequence of integers,
meaning the week numbers to apply the recurrence to. Week numbers
@ -412,11 +419,10 @@ class rrule(rrulebase):
:param bysecond:
If given, it must be either an integer, or a sequence of integers,
meaning the seconds to apply the recurrence to.
:param byeaster:
If given, it must be either an integer, or a sequence of integers,
positive or negative. Each integer will define an offset from the
Easter Sunday. Passing the offset 0 to byeaster will yield the Easter
Sunday itself. This is an extension to the RFC specification.
:param cache:
If given, it must be a boolean value specifying to enable or disable
caching of results. If you will use the same rrule instance multiple
times, enabling caching will improve the performance considerably.
"""
def __init__(self, freq, dtstart=None,
interval=1, wkst=None, count=None, until=None, bysetpos=None,
@ -427,6 +433,9 @@ class rrule(rrulebase):
super(rrule, self).__init__(cache)
global easter
if not dtstart:
if until and until.tzinfo:
dtstart = datetime.datetime.now(tz=until.tzinfo).replace(microsecond=0)
else:
dtstart = datetime.datetime.now().replace(microsecond=0)
elif not isinstance(dtstart, datetime.datetime):
dtstart = datetime.datetime.fromordinal(dtstart.toordinal())
@ -1404,6 +1413,49 @@ class rruleset(rrulebase):
class _rrulestr(object):
""" Parses a string representation of a recurrence rule or set of
recurrence rules.
:param s:
Required, a string defining one or more recurrence rules.
:param dtstart:
If given, used as the default recurrence start if not specified in the
rule string.
:param cache:
If set ``True`` caching of results will be enabled, improving
performance of multiple queries considerably.
:param unfold:
If set ``True`` indicates that a rule string is split over more
than one line and should be joined before processing.
:param forceset:
If set ``True`` forces a :class:`dateutil.rrule.rruleset` to
be returned.
:param compatible:
If set ``True`` forces ``unfold`` and ``forceset`` to be ``True``.
:param ignoretz:
If set ``True``, time zones in parsed strings are ignored and a naive
:class:`datetime.datetime` object is returned.
:param tzids:
If given, a callable or mapping used to retrieve a
:class:`datetime.tzinfo` from a string representation.
Defaults to :func:`dateutil.tz.gettz`.
:param tzinfos:
Additional time zone names / aliases which may be present in a string
representation. See :func:`dateutil.parser.parse` for more
information.
:return:
Returns a :class:`dateutil.rrule.rruleset` or
:class:`dateutil.rrule.rrule`
"""
_freq_map = {"YEARLY": YEARLY,
"MONTHLY": MONTHLY,

View file

@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
from .tz import *
from .tz import __doc__
#: Convenience constant providing a :class:`tzutc()` instance
#:

View file

@ -1,4 +1,4 @@
from six import PY3
from six import PY2
from functools import wraps
@ -16,14 +16,18 @@ def tzname_in_python2(namefunc):
tzname() API changed in Python 3. It used to return bytes, but was changed
to unicode strings
"""
if PY2:
@wraps(namefunc)
def adjust_encoding(*args, **kwargs):
name = namefunc(*args, **kwargs)
if name is not None and not PY3:
if name is not None:
name = name.encode()
return name
return adjust_encoding
else:
return namefunc
# The following is adapted from Alexander Belopolsky's tz library

View file

@ -1,5 +1,5 @@
from datetime import timedelta
import weakref
class _TzSingleton(type):
def __init__(cls, *args, **kwargs):
@ -19,7 +19,7 @@ class _TzFactory(type):
class _TzOffsetFactory(_TzFactory):
def __init__(cls, *args, **kwargs):
cls.__instances = {}
cls.__instances = weakref.WeakValueDictionary()
def __call__(cls, name, offset):
if isinstance(offset, timedelta):
@ -36,7 +36,7 @@ class _TzOffsetFactory(_TzFactory):
class _TzStrFactory(_TzFactory):
def __init__(cls, *args, **kwargs):
cls.__instances = {}
cls.__instances = weakref.WeakValueDictionary()
def __call__(cls, s, posix_offset=False):
key = (s, posix_offset)

View file

@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
"""
This module offers timezone implementations subclassing the abstract
:py:`datetime.tzinfo` type. There are classes to handle tzfile format files
(usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`, etc), TZ
environment string (in all known formats), given ranges (with help from
relative deltas), local machine timezone, fixed offset timezone, and UTC
:py:class:`datetime.tzinfo` type. There are classes to handle tzfile format
files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`,
etc), TZ environment string (in all known formats), given ranges (with help
from relative deltas), local machine timezone, fixed offset timezone, and UTC
timezone.
"""
import datetime
@ -13,6 +13,7 @@ import time
import sys
import os
import bisect
import weakref
import six
from six import string_types
@ -28,6 +29,9 @@ try:
except ImportError:
tzwin = tzwinlocal = None
# For warning about rounding tzinfo
from warnings import warn
ZERO = datetime.timedelta(0)
EPOCH = datetime.datetime.utcfromtimestamp(0)
EPOCHORDINAL = EPOCH.toordinal()
@ -137,7 +141,8 @@ class tzoffset(datetime.tzinfo):
offset = offset.total_seconds()
except (TypeError, AttributeError):
pass
self._offset = datetime.timedelta(seconds=offset)
self._offset = datetime.timedelta(seconds=_get_supported_offset(offset))
def utcoffset(self, dt):
return self._offset
@ -387,10 +392,60 @@ class tzfile(_tzinfo):
``fileobj``'s ``name`` attribute or to ``repr(fileobj)``.
See `Sources for Time Zone and Daylight Saving Time Data
<https://data.iana.org/time-zones/tz-link.html>`_ for more information. Time
zone files can be compiled from the `IANA Time Zone database files
<https://data.iana.org/time-zones/tz-link.html>`_ for more information.
Time zone files can be compiled from the `IANA Time Zone database files
<https://www.iana.org/time-zones>`_ with the `zic time zone compiler
<https://www.freebsd.org/cgi/man.cgi?query=zic&sektion=8>`_
.. note::
Only construct a ``tzfile`` directly if you have a specific timezone
file on disk that you want to read into a Python ``tzinfo`` object.
If you want to get a ``tzfile`` representing a specific IANA zone,
(e.g. ``'America/New_York'``), you should call
:func:`dateutil.tz.gettz` with the zone identifier.
**Examples:**
Using the US Eastern time zone as an example, we can see that a ``tzfile``
provides time zone information for the standard Daylight Saving offsets:
.. testsetup:: tzfile
from dateutil.tz import gettz
from datetime import datetime
.. doctest:: tzfile
>>> NYC = gettz('America/New_York')
>>> NYC
tzfile('/usr/share/zoneinfo/America/New_York')
>>> print(datetime(2016, 1, 3, tzinfo=NYC)) # EST
2016-01-03 00:00:00-05:00
>>> print(datetime(2016, 7, 7, tzinfo=NYC)) # EDT
2016-07-07 00:00:00-04:00
The ``tzfile`` structure contains a fully history of the time zone,
so historical dates will also have the right offsets. For example, before
the adoption of the UTC standards, New York used local solar mean time:
.. doctest:: tzfile
>>> print(datetime(1901, 4, 12, tzinfo=NYC)) # LMT
1901-04-12 00:00:00-04:56
And during World War II, New York was on "Eastern War Time", which was a
state of permanent daylight saving time:
.. doctest:: tzfile
>>> print(datetime(1944, 2, 7, tzinfo=NYC)) # EWT
1944-02-07 00:00:00-04:00
"""
def __init__(self, fileobj, filename=None):
@ -410,7 +465,7 @@ class tzfile(_tzinfo):
if fileobj is not None:
if not file_opened_here:
fileobj = _ContextWrapper(fileobj)
fileobj = _nullcontext(fileobj)
with fileobj as file_stream:
tzobj = self._read_tzfile(file_stream)
@ -550,10 +605,7 @@ class tzfile(_tzinfo):
out.ttinfo_list = []
for i in range(typecnt):
gmtoff, isdst, abbrind = ttinfo[i]
# Round to full-minutes if that's not the case. Python's
# datetime doesn't accept sub-minute timezones. Check
# http://python.org/sf/1447945 for some information.
gmtoff = 60 * ((gmtoff + 30) // 60)
gmtoff = _get_supported_offset(gmtoff)
tti = _ttinfo()
tti.offset = gmtoff
tti.dstoffset = datetime.timedelta(0)
@ -605,37 +657,44 @@ class tzfile(_tzinfo):
# isgmt are off, so it should be in wall time. OTOH, it's
# always in gmt time. Let me know if you have comments
# about this.
laststdoffset = None
lastdst = None
lastoffset = None
lastdstoffset = None
lastbaseoffset = None
out.trans_list = []
for i, tti in enumerate(out.trans_idx):
if not tti.isdst:
offset = tti.offset
laststdoffset = offset
else:
if laststdoffset is not None:
# Store the DST offset as well and update it in the list
tti.dstoffset = tti.offset - laststdoffset
out.trans_idx[i] = tti
dstoffset = 0
offset = laststdoffset or 0
out.trans_list.append(out.trans_list_utc[i] + offset)
# In case we missed any DST offsets on the way in for some reason, make
# a second pass over the list, looking for the /next/ DST offset.
laststdoffset = None
for i in reversed(range(len(out.trans_idx))):
tti = out.trans_idx[i]
if lastdst is not None:
if tti.isdst:
if not (tti.dstoffset or laststdoffset is None):
tti.dstoffset = tti.offset - laststdoffset
else:
laststdoffset = tti.offset
if not lastdst:
dstoffset = offset - lastoffset
if not isinstance(tti.dstoffset, datetime.timedelta):
tti.dstoffset = datetime.timedelta(seconds=tti.dstoffset)
if not dstoffset and lastdstoffset:
dstoffset = lastdstoffset
out.trans_idx[i] = tti
tti.dstoffset = datetime.timedelta(seconds=dstoffset)
lastdstoffset = dstoffset
# If a time zone changes its base offset during a DST transition,
# then you need to adjust by the previous base offset to get the
# transition time in local time. Otherwise you use the current
# base offset. Ideally, I would have some mathematical proof of
# why this is true, but I haven't really thought about it enough.
baseoffset = offset - dstoffset
adjustment = baseoffset
if (lastbaseoffset is not None and baseoffset != lastbaseoffset
and tti.isdst != lastdst):
# The base DST has changed
adjustment = lastbaseoffset
lastdst = tti.isdst
lastoffset = offset
lastbaseoffset = baseoffset
out.trans_list.append(out.trans_list_utc[i] + adjustment)
out.trans_idx = tuple(out.trans_idx)
out.trans_list = tuple(out.trans_list)
@ -840,8 +899,9 @@ class tzrange(tzrangebase):
:param start:
A :class:`relativedelta.relativedelta` object or equivalent specifying
the time and time of year that daylight savings time starts. To specify,
for example, that DST starts at 2AM on the 2nd Sunday in March, pass:
the time and time of year that daylight savings time starts. To
specify, for example, that DST starts at 2AM on the 2nd Sunday in
March, pass:
``relativedelta(hours=2, month=3, day=1, weekday=SU(+2))``
@ -849,12 +909,12 @@ class tzrange(tzrangebase):
value is 2 AM on the first Sunday in April.
:param end:
A :class:`relativedelta.relativedelta` object or equivalent representing
the time and time of year that daylight savings time ends, with the
same specification method as in ``start``. One note is that this should
point to the first time in the *standard* zone, so if a transition
occurs at 2AM in the DST zone and the clocks are set back 1 hour to 1AM,
set the `hours` parameter to +1.
A :class:`relativedelta.relativedelta` object or equivalent
representing the time and time of year that daylight savings time
ends, with the same specification method as in ``start``. One note is
that this should point to the first time in the *standard* zone, so if
a transition occurs at 2AM in the DST zone and the clocks are set back
1 hour to 1AM, set the ``hours`` parameter to +1.
**Examples:**
@ -985,8 +1045,9 @@ class tzstr(tzrange):
:param s:
A time zone string in ``TZ`` variable format. This can be a
:class:`bytes` (2.x: :class:`str`), :class:`str` (2.x: :class:`unicode`)
or a stream emitting unicode characters (e.g. :class:`StringIO`).
:class:`bytes` (2.x: :class:`str`), :class:`str` (2.x:
:class:`unicode`) or a stream emitting unicode characters
(e.g. :class:`StringIO`).
:param posix_offset:
Optional. If set to ``True``, interpret strings such as ``GMT+3`` or
@ -1203,7 +1264,7 @@ class tzical(object):
fileobj = open(fileobj, 'r')
else:
self._s = getattr(fileobj, 'name', repr(fileobj))
fileobj = _ContextWrapper(fileobj)
fileobj = _nullcontext(fileobj)
self._vtz = {}
@ -1398,15 +1459,85 @@ else:
TZFILES = []
TZPATHS = []
def __get_gettz(name, zoneinfo_priority=False):
tzlocal_classes = (tzlocal,)
if tzwinlocal is not None:
tzlocal_classes += (tzwinlocal,)
class GettzFunc(object):
"""
Retrieve a time zone object from a string representation
This function is intended to retrieve the :py:class:`tzinfo` subclass
that best represents the time zone that would be used if a POSIX
`TZ variable`_ were set to the same value.
If no argument or an empty string is passed to ``gettz``, local time
is returned:
.. code-block:: python3
>>> gettz()
tzfile('/etc/localtime')
This function is also the preferred way to map IANA tz database keys
to :class:`tzfile` objects:
.. code-block:: python3
>>> gettz('Pacific/Kiritimati')
tzfile('/usr/share/zoneinfo/Pacific/Kiritimati')
On Windows, the standard is extended to include the Windows-specific
zone names provided by the operating system:
.. code-block:: python3
>>> gettz('Egypt Standard Time')
tzwin('Egypt Standard Time')
Passing a GNU ``TZ`` style string time zone specification returns a
:class:`tzstr` object:
.. code-block:: python3
>>> gettz('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3')
tzstr('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3')
:param name:
A time zone name (IANA, or, on Windows, Windows keys), location of
a ``tzfile(5)`` zoneinfo file or ``TZ`` variable style time zone
specifier. An empty string, no argument or ``None`` is interpreted
as local time.
:return:
Returns an instance of one of ``dateutil``'s :py:class:`tzinfo`
subclasses.
.. versionchanged:: 2.7.0
After version 2.7.0, any two calls to ``gettz`` using the same
input strings will return the same object:
.. code-block:: python3
>>> tz.gettz('America/Chicago') is tz.gettz('America/Chicago')
True
In addition to improving performance, this ensures that
`"same zone" semantics`_ are used for datetimes in the same zone.
.. _`TZ variable`:
https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
.. _`"same zone" semantics`:
https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html
"""
def __init__(self, name, zoneinfo_priority=False):
self.__instances = {}
self.__instances = weakref.WeakValueDictionary()
self._cache_lock = _thread.allocate_lock()
def __call__(self, name=None, zoneinfo_priority=False):
@ -1415,17 +1546,22 @@ def __get_gettz(name, zoneinfo_priority=False):
if rv is None:
rv = self.nocache(name=name, zoneinfo_priority=zoneinfo_priority)
if not (name is None or isinstance(rv, tzlocal_classes)):
if not (name is None
or isinstance(rv, tzlocal_classes)
or rv is None):
# tzlocal is slightly more complicated than the other
# time zone providers because it depends on environment
# at construction time, so don't cache that.
#
# We also cannot store weak references to None, so we
# will also not store that.
self.__instances[name] = rv
return rv
def cache_clear(self):
with self._cache_lock:
self.__instances = {}
self.__instances = weakref.WeakValueDictionary()
@staticmethod
def nocache(name=None, zoneinfo_priority=False):
@ -1492,7 +1628,10 @@ def __get_gettz(name, zoneinfo_priority=False):
if not tz:
for c in name:
# name must have at least one offset to be a tzstr
# name is not a tzstr unless it has at least
# one offset. For short values of "name", an
# explicit for loop seems to be the fastest way
# To determine if a string contains a digit
if c in "0123456789":
try:
tz = tzstr(name)
@ -1508,9 +1647,11 @@ def __get_gettz(name, zoneinfo_priority=False):
return GettzFunc(name, zoneinfo_priority)
gettz = __get_gettz(name=None, zoneinfo_priority=False)
del __get_gettz
def datetime_exists(dt, tz=None):
"""
Given a datetime and a time zone, determine whether or not a given datetime
@ -1525,9 +1666,10 @@ def datetime_exists(dt, tz=None):
``None`` or not provided, the datetime's own time zone will be used.
:return:
Returns a boolean value whether or not the "wall time" exists in ``tz``.
Returns a boolean value whether or not the "wall time" exists in
``tz``.
..versionadded:: 2.7.0
.. versionadded:: 2.7.0
"""
if tz is None:
if dt.tzinfo is None:
@ -1575,7 +1717,7 @@ def datetime_ambiguous(dt, tz=None):
if is_ambiguous_fn is not None:
try:
return tz.is_ambiguous(dt)
except:
except Exception:
pass
# If it doesn't come out and tell us it's ambiguous, we'll just check if
@ -1598,7 +1740,8 @@ def resolve_imaginary(dt):
wall time would be in a zone had the offset transition not occurred, so
it will always fall forward by the transition's change in offset.
..doctest::
.. doctest::
>>> from dateutil import tz
>>> from datetime import datetime
>>> NYC = tz.gettz('America/New_York')
@ -1623,7 +1766,7 @@ def resolve_imaginary(dt):
imaginary, the datetime returned is guaranteed to be the same object
passed to the function.
..versionadded:: 2.7.0
.. versionadded:: 2.7.0
"""
if dt.tzinfo is not None and not datetime_exists(dt):
@ -1637,13 +1780,31 @@ def resolve_imaginary(dt):
def _datetime_to_timestamp(dt):
"""
Convert a :class:`datetime.datetime` object to an epoch timestamp in seconds
since January 1, 1970, ignoring the time zone.
Convert a :class:`datetime.datetime` object to an epoch timestamp in
seconds since January 1, 1970, ignoring the time zone.
"""
return (dt.replace(tzinfo=None) - EPOCH).total_seconds()
class _ContextWrapper(object):
if sys.version_info >= (3, 6):
def _get_supported_offset(second_offset):
return second_offset
else:
def _get_supported_offset(second_offset):
# For python pre-3.6, round to full-minutes if that's not the case.
# Python's datetime doesn't accept sub-minute timezones. Check
# http://python.org/sf/1447945 or https://bugs.python.org/issue5288
# for some information.
old_offset = second_offset
calculated_offset = 60 * ((second_offset + 30) // 60)
return calculated_offset
try:
# Python 3.7 feature
from contextmanager import nullcontext as _nullcontext
except ImportError:
class _nullcontext(object):
"""
Class for wrapping contexts so that they are passed through in a
with statement.

View file

@ -1,3 +1,11 @@
# -*- coding: utf-8 -*-
"""
This module provides an interface to the native time zone data on Windows,
including :py:class:`datetime.tzinfo` implementations.
Attempting to import this module on a non-Windows platform will raise an
:py:obj:`ImportError`.
"""
# This code was originally contributed by Jeffrey Harris.
import datetime
import struct
@ -39,7 +47,7 @@ TZKEYNAME = _settzkeyname()
class tzres(object):
"""
Class for accessing `tzres.dll`, which contains timezone name related
Class for accessing ``tzres.dll``, which contains timezone name related
resources.
.. versionadded:: 2.5.0
@ -72,9 +80,10 @@ class tzres(object):
:param offset:
A positive integer value referring to a string from the tzres dll.
..note:
.. note::
Offsets found in the registry are generally of the form
`@tzres.dll,-114`. The offset in this case if 114, not -114.
``@tzres.dll,-114``. The offset in this case is 114, not -114.
"""
resource = self.p_wchar()
@ -146,6 +155,9 @@ class tzwinbase(tzrangebase):
return result
def display(self):
"""
Return the display name of the time zone.
"""
return self._display
def transitions(self, year):
@ -188,6 +200,17 @@ class tzwinbase(tzrangebase):
class tzwin(tzwinbase):
"""
Time zone object created from the zone info in the Windows registry
These are similar to :py:class:`dateutil.tz.tzrange` objects in that
the time zone data is provided in the format of a single offset rule
for either 0 or 2 time zone transitions per year.
:param: name
The name of a Windows time zone key, e.g. "Eastern Standard Time".
The full list of keys can be retrieved with :func:`tzwin.list`.
"""
def __init__(self, name):
self._name = name
@ -234,6 +257,22 @@ class tzwin(tzwinbase):
class tzwinlocal(tzwinbase):
"""
Class representing the local time zone information in the Windows registry
While :class:`dateutil.tz.tzlocal` makes system calls (via the :mod:`time`
module) to retrieve time zone information, ``tzwinlocal`` retrieves the
rules directly from the Windows registry and creates an object like
:class:`dateutil.tz.tzwin`.
Because Windows does not have an equivalent of :func:`time.tzset`, on
Windows, :class:`dateutil.tz.tzlocal` instances will always reflect the
time zone settings *at the time that the process was started*, meaning
changes to the machine's time zone settings during the run of a program
on Windows will **not** be reflected by :class:`dateutil.tz.tzlocal`.
Because ``tzwinlocal`` reads the registry directly, it is unaffected by
this issue.
"""
def __init__(self):
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey:

View file

@ -1,4 +1,10 @@
# -*- coding: utf-8 -*-
"""
This module offers general convenience and utility functions for dealing with
datetimes.
.. versionadded:: 2.7.0
"""
from __future__ import unicode_literals
from datetime import datetime, time