Spaces:
Runtime error
Runtime error
| 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: | |
| 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 | |
| # 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) | |
| 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``. | |
| """ | |
| 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. | |
| """ | |
| # 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 | |
| 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) | |
| # 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 | |
| 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 | |
| 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__ | |