| from six import PY2 |
|
|
| 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 |
| """ |
| if PY2: |
| @wraps(namefunc) |
| def adjust_encoding(*args, **kwargs): |
| name = namefunc(*args, **kwargs) |
| if name is not None: |
| name = name.encode() |
|
|
| return name |
|
|
| return adjust_encoding |
| else: |
| return namefunc |
|
|
|
|
| |
| |
| if hasattr(datetime, 'fold'): |
| |
| 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 |
| occurrence, chronologically, of the ambiguous datetime). |
| |
| :param dt: |
| A timezone-aware :class:`datetime.datetime` object. |
| """ |
|
|
| |
| dtoff = dt.utcoffset() |
| if dtoff is None: |
| raise ValueError("fromutc() requires a non-None utcoffset() " |
| "result") |
|
|
| |
| |
| |
| dtdst = dt.dst() |
| if dtdst is None: |
| raise ValueError("fromutc() requires a non-None dst() result") |
| delta = dtoff - dtdst |
|
|
| dt += delta |
| |
| |
| 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 |
| occurrence, chronologically, of the ambiguous datetime). |
| |
| :param dt: |
| A timezone-aware :class:`datetime.datetime` object. |
| """ |
| dt_wall = self._fromutc(dt) |
|
|
| |
| _fold = self._fold_status(dt, dt_wall) |
|
|
| |
| 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") |
|
|
| |
| transitions = self.transitions(dt.year) |
| if transitions is None: |
| return dt + self.utcoffset(dt) |
|
|
| |
| 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) |
|
|
| |
| 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__ |
|
|