summaryrefslogtreecommitdiffstats
path: root/venv/lib/python3.9/site-packages/pytz_deprecation_shim
diff options
context:
space:
mode:
Diffstat (limited to 'venv/lib/python3.9/site-packages/pytz_deprecation_shim')
-rw-r--r--venv/lib/python3.9/site-packages/pytz_deprecation_shim/__init__.py34
-rw-r--r--venv/lib/python3.9/site-packages/pytz_deprecation_shim/_common.py13
-rw-r--r--venv/lib/python3.9/site-packages/pytz_deprecation_shim/_compat.py15
-rw-r--r--venv/lib/python3.9/site-packages/pytz_deprecation_shim/_compat_py2.py43
-rw-r--r--venv/lib/python3.9/site-packages/pytz_deprecation_shim/_compat_py3.py58
-rw-r--r--venv/lib/python3.9/site-packages/pytz_deprecation_shim/_exceptions.py75
-rw-r--r--venv/lib/python3.9/site-packages/pytz_deprecation_shim/_impl.py296
-rw-r--r--venv/lib/python3.9/site-packages/pytz_deprecation_shim/helpers.py90
8 files changed, 624 insertions, 0 deletions
diff --git a/venv/lib/python3.9/site-packages/pytz_deprecation_shim/__init__.py b/venv/lib/python3.9/site-packages/pytz_deprecation_shim/__init__.py
new file mode 100644
index 00000000..8b451620
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pytz_deprecation_shim/__init__.py
@@ -0,0 +1,34 @@
+__all__ = [
+ "AmbiguousTimeError",
+ "NonExistentTimeError",
+ "InvalidTimeError",
+ "UnknownTimeZoneError",
+ "PytzUsageWarning",
+ "FixedOffset",
+ "UTC",
+ "utc",
+ "build_tzinfo",
+ "timezone",
+ "fixed_offset_timezone",
+ "wrap_zone",
+]
+
+from . import helpers
+from ._exceptions import (
+ AmbiguousTimeError,
+ InvalidTimeError,
+ NonExistentTimeError,
+ PytzUsageWarning,
+ UnknownTimeZoneError,
+)
+from ._impl import (
+ UTC,
+ build_tzinfo,
+ fixed_offset_timezone,
+ timezone,
+ wrap_zone,
+)
+
+# Compatibility aliases
+utc = UTC
+FixedOffset = fixed_offset_timezone
diff --git a/venv/lib/python3.9/site-packages/pytz_deprecation_shim/_common.py b/venv/lib/python3.9/site-packages/pytz_deprecation_shim/_common.py
new file mode 100644
index 00000000..ace322e9
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pytz_deprecation_shim/_common.py
@@ -0,0 +1,13 @@
+import sys
+
+_PYTZ_IMPORTED = False
+
+
+def pytz_imported():
+ """Detects whether or not pytz has been imported without importing pytz."""
+ global _PYTZ_IMPORTED
+
+ if not _PYTZ_IMPORTED and "pytz" in sys.modules:
+ _PYTZ_IMPORTED = True
+
+ return _PYTZ_IMPORTED
diff --git a/venv/lib/python3.9/site-packages/pytz_deprecation_shim/_compat.py b/venv/lib/python3.9/site-packages/pytz_deprecation_shim/_compat.py
new file mode 100644
index 00000000..5b734592
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pytz_deprecation_shim/_compat.py
@@ -0,0 +1,15 @@
+import sys
+
+if sys.version_info[0] == 2:
+ from . import _compat_py2 as _compat_impl
+else:
+ from . import _compat_py3 as _compat_impl
+
+UTC = _compat_impl.UTC
+get_timezone = _compat_impl.get_timezone
+get_timezone_file = _compat_impl.get_timezone_file
+get_fixed_offset_zone = _compat_impl.get_fixed_offset_zone
+is_ambiguous = _compat_impl.is_ambiguous
+is_imaginary = _compat_impl.is_imaginary
+enfold = _compat_impl.enfold
+get_fold = _compat_impl.get_fold
diff --git a/venv/lib/python3.9/site-packages/pytz_deprecation_shim/_compat_py2.py b/venv/lib/python3.9/site-packages/pytz_deprecation_shim/_compat_py2.py
new file mode 100644
index 00000000..f473d267
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pytz_deprecation_shim/_compat_py2.py
@@ -0,0 +1,43 @@
+from datetime import timedelta
+
+from dateutil import tz
+
+UTC = tz.UTC
+
+
+def get_timezone(key):
+ if not key:
+ raise KeyError("Unknown time zone: %s" % key)
+
+ try:
+ rv = tz.gettz(key)
+ except Exception:
+ rv = None
+
+ if rv is None or not isinstance(rv, (tz.tzutc, tz.tzfile)):
+ raise KeyError("Unknown time zone: %s" % key)
+
+ return rv
+
+
+def get_timezone_file(f, key=None):
+ return tz.tzfile(f)
+
+
+def get_fixed_offset_zone(offset):
+ return tz.tzoffset(None, timedelta(minutes=offset))
+
+
+def is_ambiguous(dt):
+ return tz.datetime_ambiguous(dt)
+
+
+def is_imaginary(dt):
+ return not tz.datetime_exists(dt)
+
+
+enfold = tz.enfold
+
+
+def get_fold(dt):
+ return getattr(dt, "fold", 0)
diff --git a/venv/lib/python3.9/site-packages/pytz_deprecation_shim/_compat_py3.py b/venv/lib/python3.9/site-packages/pytz_deprecation_shim/_compat_py3.py
new file mode 100644
index 00000000..8881abac
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pytz_deprecation_shim/_compat_py3.py
@@ -0,0 +1,58 @@
+# Note: This file could use Python 3-only syntax, but at the moment this breaks
+# the coverage job on Python 2. Until we make it so that coverage can ignore
+# this file only on Python 2, we'll have to stick to 2/3-compatible syntax.
+try:
+ import zoneinfo
+except ImportError:
+ from backports import zoneinfo
+
+import datetime
+
+UTC = datetime.timezone.utc
+
+
+def get_timezone(key):
+ try:
+ return zoneinfo.ZoneInfo(key)
+ except (ValueError, OSError):
+ # TODO: Use `from e` when this file can use Python 3 syntax
+ raise KeyError(key)
+
+
+def get_timezone_file(f, key=None):
+ return zoneinfo.ZoneInfo.from_file(f, key=key)
+
+
+def get_fixed_offset_zone(offset):
+ return datetime.timezone(datetime.timedelta(minutes=offset))
+
+
+def is_imaginary(dt):
+ dt_rt = dt.astimezone(UTC).astimezone(dt.tzinfo)
+
+ return not (dt == dt_rt)
+
+
+def is_ambiguous(dt):
+ if is_imaginary(dt):
+ return False
+
+ wall_0 = dt
+ wall_1 = dt.replace(fold=not dt.fold)
+
+ # Ambiguous datetimes can only exist if the offset changes, so we don't
+ # actually have to check whether dst() or tzname() are different.
+ same_offset = wall_0.utcoffset() == wall_1.utcoffset()
+
+ return not same_offset
+
+
+def enfold(dt, fold=1):
+ if dt.fold != fold:
+ return dt.replace(fold=fold)
+ else:
+ return dt
+
+
+def get_fold(dt):
+ return dt.fold
diff --git a/venv/lib/python3.9/site-packages/pytz_deprecation_shim/_exceptions.py b/venv/lib/python3.9/site-packages/pytz_deprecation_shim/_exceptions.py
new file mode 100644
index 00000000..58d7af0a
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pytz_deprecation_shim/_exceptions.py
@@ -0,0 +1,75 @@
+from ._common import pytz_imported
+
+
+class PytzUsageWarning(RuntimeWarning):
+ """Warning raised when accessing features specific to ``pytz``'s interface.
+
+ This warning is used to direct users of ``pytz``-specific features like the
+ ``localize`` and ``normalize`` methods towards using the standard
+ ``tzinfo`` interface, so that these shims can be replaced with one of the
+ underlying libraries they are wrapping.
+ """
+
+
+class UnknownTimeZoneError(KeyError):
+ """Raised when no time zone is found for a specified key."""
+
+
+class InvalidTimeError(Exception):
+ """The base class for exceptions related to folds and gaps."""
+
+
+class AmbiguousTimeError(InvalidTimeError):
+ """Exception raised when ``is_dst=None`` for an ambiguous time (fold)."""
+
+
+class NonExistentTimeError(InvalidTimeError):
+ """Exception raised when ``is_dst=None`` for a non-existent time (gap)."""
+
+
+PYTZ_BASE_ERROR_MAPPING = {}
+
+
+def _make_pytz_derived_errors(
+ InvalidTimeError_=InvalidTimeError,
+ AmbiguousTimeError_=AmbiguousTimeError,
+ NonExistentTimeError_=NonExistentTimeError,
+ UnknownTimeZoneError_=UnknownTimeZoneError,
+):
+ if PYTZ_BASE_ERROR_MAPPING or not pytz_imported():
+ return
+
+ import pytz
+
+ class InvalidTimeError(InvalidTimeError_, pytz.InvalidTimeError):
+ pass
+
+ class AmbiguousTimeError(AmbiguousTimeError_, pytz.AmbiguousTimeError):
+ pass
+
+ class NonExistentTimeError(
+ NonExistentTimeError_, pytz.NonExistentTimeError
+ ):
+ pass
+
+ class UnknownTimeZoneError(
+ UnknownTimeZoneError_, pytz.UnknownTimeZoneError
+ ):
+ pass
+
+ PYTZ_BASE_ERROR_MAPPING.update(
+ {
+ InvalidTimeError_: InvalidTimeError,
+ AmbiguousTimeError_: AmbiguousTimeError,
+ NonExistentTimeError_: NonExistentTimeError,
+ UnknownTimeZoneError_: UnknownTimeZoneError,
+ }
+ )
+
+
+def get_exception(exc_type, msg):
+ _make_pytz_derived_errors()
+
+ out_exc_type = PYTZ_BASE_ERROR_MAPPING.get(exc_type, exc_type)
+
+ return out_exc_type(msg)
diff --git a/venv/lib/python3.9/site-packages/pytz_deprecation_shim/_impl.py b/venv/lib/python3.9/site-packages/pytz_deprecation_shim/_impl.py
new file mode 100644
index 00000000..54430479
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pytz_deprecation_shim/_impl.py
@@ -0,0 +1,296 @@
+# -*- coding: utf-8 -*-
+import warnings
+from datetime import tzinfo
+
+from . import _compat
+from ._exceptions import (
+ AmbiguousTimeError,
+ NonExistentTimeError,
+ PytzUsageWarning,
+ UnknownTimeZoneError,
+ get_exception,
+)
+
+IS_DST_SENTINEL = object()
+KEY_SENTINEL = object()
+
+
+def timezone(key, _cache={}):
+ """Builds an IANA database time zone shim.
+
+ This is the equivalent of ``pytz.timezone``.
+
+ :param key:
+ A valid key from the IANA time zone database.
+
+ :raises UnknownTimeZoneError:
+ If an unknown value is passed, this will raise an exception that can be
+ caught by :exc:`pytz_deprecation_shim.UnknownTimeZoneError` or
+ ``pytz.UnknownTimeZoneError``. Like
+ :exc:`zoneinfo.ZoneInfoNotFoundError`, both of those are subclasses of
+ :exc:`KeyError`.
+ """
+ instance = _cache.get(key, None)
+ if instance is None:
+ if len(key) == 3 and key.lower() == "utc":
+ instance = _cache.setdefault(key, UTC)
+ else:
+ try:
+ zone = _compat.get_timezone(key)
+ except KeyError:
+ raise get_exception(UnknownTimeZoneError, key)
+ instance = _cache.setdefault(key, wrap_zone(zone, key=key))
+
+ return instance
+
+
+def fixed_offset_timezone(offset, _cache={}):
+ """Builds a fixed offset time zone shim.
+
+ This is the equivalent of ``pytz.FixedOffset``. An alias is available as
+ ``pytz_deprecation_shim.FixedOffset`` as well.
+
+ :param offset:
+ A fixed offset from UTC, in minutes. This must be in the range ``-1439
+ <= offset <= 1439``.
+
+ :raises ValueError:
+ For offsets whose absolute value is greater than or equal to 24 hours.
+
+ :return:
+ A shim time zone.
+ """
+ if not (-1440 < offset < 1440):
+ raise ValueError("absolute offset is too large", offset)
+
+ instance = _cache.get(offset, None)
+ if instance is None:
+ if offset == 0:
+ instance = _cache.setdefault(offset, UTC)
+ else:
+ zone = _compat.get_fixed_offset_zone(offset)
+ instance = _cache.setdefault(offset, wrap_zone(zone, key=None))
+
+ return instance
+
+
+def build_tzinfo(zone, fp):
+ """Builds a shim object from a TZif file.
+
+ This is a shim for ``pytz.build_tzinfo``. Given a value to use as the zone
+ IANA key and a file-like object containing a valid TZif file (i.e.
+ conforming to :rfc:`8536`), this builds a time zone object and wraps it in
+ a shim class.
+
+ The argument names are chosen to match those in ``pytz.build_tzinfo``.
+
+ :param zone:
+ A string to be used as the time zone object's IANA key.
+
+ :param fp:
+ A readable file-like object emitting bytes, pointing to a valid TZif
+ file.
+
+ :return:
+ A shim time zone.
+ """
+ zone_file = _compat.get_timezone_file(fp)
+
+ return wrap_zone(zone_file, key=zone)
+
+
+def wrap_zone(tz, key=KEY_SENTINEL, _cache={}):
+ """Wrap an existing time zone object in a shim class.
+
+ This is likely to be useful if you would like to work internally with
+ non-``pytz`` zones, but you expose an interface to callers relying on
+ ``pytz``'s interface. It may also be useful for passing non-``pytz`` zones
+ to libraries expecting to use ``pytz``'s interface.
+
+ :param tz:
+ A :pep:`495`-compatible time zone, such as those provided by
+ :mod:`dateutil.tz` or :mod:`zoneinfo`.
+
+ :param key:
+ The value for the IANA time zone key. This is optional for ``zoneinfo``
+ zones, but required for ``dateutil.tz`` zones.
+
+ :return:
+ A shim time zone.
+ """
+ if key is KEY_SENTINEL:
+ key = getattr(tz, "key", KEY_SENTINEL)
+
+ if key is KEY_SENTINEL:
+ raise TypeError(
+ "The `key` argument is required when wrapping zones that do not "
+ + "have a `key` attribute."
+ )
+
+ instance = _cache.get((id(tz), key), None)
+ if instance is None:
+ instance = _cache.setdefault((id(tz), key), _PytzShimTimezone(tz, key))
+
+ return instance
+
+
+class _PytzShimTimezone(tzinfo):
+ # Add instance variables for _zone and _key because this will make error
+ # reporting with partially-initialized _BasePytzShimTimezone objects
+ # work better.
+ _zone = None
+ _key = None
+
+ def __init__(self, zone, key):
+ self._key = key
+ self._zone = zone
+
+ def utcoffset(self, dt):
+ return self._zone.utcoffset(dt)
+
+ def dst(self, dt):
+ return self._zone.dst(dt)
+
+ def tzname(self, dt):
+ return self._zone.tzname(dt)
+
+ def fromutc(self, dt):
+ # The default fromutc implementation only works if tzinfo is "self"
+ dt_base = dt.replace(tzinfo=self._zone)
+ dt_out = self._zone.fromutc(dt_base)
+
+ return dt_out.replace(tzinfo=self)
+
+ def __str__(self):
+ if self._key is not None:
+ return str(self._key)
+ else:
+ return repr(self)
+
+ def __repr__(self):
+ return "%s(%s, %s)" % (
+ self.__class__.__name__,
+ repr(self._zone),
+ repr(self._key),
+ )
+
+ def unwrap_shim(self):
+ """Returns the underlying class that the shim is a wrapper for.
+
+ This is a shim-specific method equivalent to
+ :func:`pytz_deprecation_shim.helpers.upgrade_tzinfo`. It is provided as
+ a method to allow end-users to upgrade shim timezones without requiring
+ an explicit dependency on ``pytz_deprecation_shim``, e.g.:
+
+ .. code-block:: python
+
+ if getattr(tz, "unwrap_shim", None) is None:
+ tz = tz.unwrap_shim()
+ """
+ return self._zone
+
+ @property
+ def zone(self):
+ warnings.warn(
+ "The zone attribute is specific to pytz's interface; "
+ + "please migrate to a new time zone provider. "
+ + "For more details on how to do so, see %s"
+ % PYTZ_MIGRATION_GUIDE_URL,
+ PytzUsageWarning,
+ stacklevel=2,
+ )
+
+ return self._key
+
+ def localize(self, dt, is_dst=IS_DST_SENTINEL):
+ warnings.warn(
+ "The localize method is no longer necessary, as this "
+ + "time zone supports the fold attribute (PEP 495). "
+ + "For more details on migrating to a PEP 495-compliant "
+ + "implementation, see %s" % PYTZ_MIGRATION_GUIDE_URL,
+ PytzUsageWarning,
+ stacklevel=2,
+ )
+
+ if dt.tzinfo is not None:
+ raise ValueError("Not naive datetime (tzinfo is already set)")
+
+ dt_out = dt.replace(tzinfo=self)
+
+ if is_dst is IS_DST_SENTINEL:
+ return dt_out
+
+ dt_ambiguous = _compat.is_ambiguous(dt_out)
+ dt_imaginary = (
+ _compat.is_imaginary(dt_out) if not dt_ambiguous else False
+ )
+
+ if is_dst is None:
+ if dt_imaginary:
+ raise get_exception(
+ NonExistentTimeError, dt.replace(tzinfo=None)
+ )
+
+ if dt_ambiguous:
+ raise get_exception(AmbiguousTimeError, dt.replace(tzinfo=None))
+
+ elif dt_ambiguous or dt_imaginary:
+ # Start by normalizing the folds; dt_out may have fold=0 or fold=1,
+ # but we need to know the DST offset on both sides anyway, so we
+ # will get one datetime representing each side of the fold, then
+ # decide which one we're going to return.
+ if _compat.get_fold(dt_out):
+ dt_enfolded = dt_out
+ dt_out = _compat.enfold(dt_out, fold=0)
+ else:
+ dt_enfolded = _compat.enfold(dt_out, fold=1)
+
+ # Now we want to decide whether the fold=0 or fold=1 represents
+ # what pytz would return for `is_dst=True`
+ enfolded_dst = bool(dt_enfolded.dst())
+ if bool(dt_out.dst()) == enfolded_dst:
+ # If this is not a transition between standard time and
+ # daylight saving time, pytz will consider the larger offset
+ # the DST offset.
+ enfolded_dst = dt_enfolded.utcoffset() > dt_out.utcoffset()
+
+ # The default we've established is that dt_out is fold=0; swap it
+ # for the fold=1 datetime if is_dst == True and the enfolded side
+ # is DST or if is_dst == False and the enfolded side is *not* DST.
+ if is_dst == enfolded_dst:
+ dt_out = dt_enfolded
+
+ return dt_out
+
+ def normalize(self, dt):
+ warnings.warn(
+ "The normalize method is no longer necessary, as this "
+ + "time zone supports the fold attribute (PEP 495). "
+ + "For more details on migrating to a PEP 495-compliant "
+ + "implementation, see %s" % PYTZ_MIGRATION_GUIDE_URL,
+ PytzUsageWarning,
+ stacklevel=2,
+ )
+
+ if dt.tzinfo is None:
+ raise ValueError("Naive time - no tzinfo set")
+
+ if dt.tzinfo is self:
+ return dt
+
+ return dt.astimezone(self)
+
+ def __copy__(self):
+ return self
+
+ def __deepcopy__(self, memo=None):
+ return self
+
+ def __reduce__(self):
+ return wrap_zone, (self._zone, self._key)
+
+
+UTC = wrap_zone(_compat.UTC, "UTC")
+PYTZ_MIGRATION_GUIDE_URL = (
+ "https://pytz-deprecation-shim.readthedocs.io/en/latest/migration.html"
+)
diff --git a/venv/lib/python3.9/site-packages/pytz_deprecation_shim/helpers.py b/venv/lib/python3.9/site-packages/pytz_deprecation_shim/helpers.py
new file mode 100644
index 00000000..6b05b130
--- /dev/null
+++ b/venv/lib/python3.9/site-packages/pytz_deprecation_shim/helpers.py
@@ -0,0 +1,90 @@
+"""
+This module contains helper functions to ease the transition from ``pytz`` to
+another :pep:`495`-compatible library.
+"""
+from . import _common, _compat
+from ._impl import _PytzShimTimezone
+
+_PYTZ_BASE_CLASSES = None
+
+
+def is_pytz_zone(tz):
+ """Check if a time zone is a ``pytz`` time zone.
+
+ This will only import ``pytz`` if it has already been imported, and does
+ not rely on the existence of the ``localize`` or ``normalize`` methods
+ (since the shim classes also have these methods, but are not ``pytz``
+ zones).
+ """
+
+ # If pytz is not in sys.modules, then we will assume the time zone is not a
+ # pytz zone. It is possible that someone has manipulated sys.modules to
+ # remove pytz, but that's the kind of thing that causes all kinds of other
+ # problems anyway, so we'll call that an unsupported configuration.
+ if not _common.pytz_imported():
+ return False
+
+ if _PYTZ_BASE_CLASSES is None:
+ _populate_pytz_base_classes()
+
+ return isinstance(tz, _PYTZ_BASE_CLASSES)
+
+
+def upgrade_tzinfo(tz):
+ """Convert a ``pytz`` or shim timezone into its modern equivalent.
+
+ The shim classes are thin wrappers around :mod:`zoneinfo` or
+ :mod:`dateutil.tz` implementations of the :class:`datetime.tzinfo` base
+ class. This function removes the shim and returns the underlying "upgraded"
+ time zone.
+
+ When passed a ``pytz`` zone (not a shim), this returns the non-``pytz``
+ equivalent. This may fail if ``pytz`` is using a data source incompatible
+ with the upgraded provider's data source, or if the ``pytz`` zone was built
+ from a file rather than an IANA key.
+
+ When passed an object that is not a shim or a ``pytz`` zone, this returns
+ the original object.
+
+ :param tz:
+ A :class:`datetime.tzinfo` object.
+
+ :raises KeyError:
+ If a ``pytz`` zone is passed to the function with no equivalent in the
+ :pep:`495`-compatible library's version of the Olson database.
+
+ :return:
+ A :pep:`495`-compatible equivalent of any ``pytz`` or shim
+ class, or the original object.
+ """
+ if isinstance(tz, _PytzShimTimezone):
+ return tz._zone
+
+ if is_pytz_zone(tz):
+ if tz.zone is None:
+ # This is a fixed offset zone
+ offset = tz.utcoffset(None)
+ offset_minutes = offset.total_seconds() / 60
+
+ return _compat.get_fixed_offset_zone(offset_minutes)
+
+ if tz.zone == "UTC":
+ return _compat.UTC
+
+ return _compat.get_timezone(tz.zone)
+
+ return tz
+
+
+def _populate_pytz_base_classes():
+ import pytz
+ from pytz.tzinfo import BaseTzInfo
+
+ base_classes = (BaseTzInfo, pytz._FixedOffset)
+
+ # In releases prior to 2018.4, pytz.UTC was not a subclass of BaseTzInfo
+ if not isinstance(pytz.UTC, BaseTzInfo): # pragma: nocover
+ base_classes = base_classes + (type(pytz.UTC),)
+
+ global _PYTZ_BASE_CLASSES
+ _PYTZ_BASE_CLASSES = base_classes