Bump apscheduler from 3.8.0 to 3.9.1 (#1675)

* Bump apscheduler from 3.8.0 to 3.9.1

Bumps [apscheduler](https://github.com/agronholm/apscheduler) from 3.8.0 to 3.9.1.
- [Release notes](https://github.com/agronholm/apscheduler/releases)
- [Changelog](https://github.com/agronholm/apscheduler/blob/3.9.1/docs/versionhistory.rst)
- [Commits](https://github.com/agronholm/apscheduler/compare/3.8.0...3.9.1)

---
updated-dependencies:
- dependency-name: apscheduler
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update apscheduler==3.9.1

* Update pytz==2022.1

* Add pytz-deprecation-shim==0.1.0.post0

* Update tzdata==2022.1

* Update tzlocal==4.2

* Update requirements.txt

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com>

[skip ci]
This commit is contained in:
dependabot[bot] 2022-05-16 20:32:37 -07:00 committed by GitHub
parent 942e09e59e
commit 54c9214b03
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 1029 additions and 223 deletions

View file

@ -106,7 +106,7 @@ class MongoDBJobStore(BaseJobStore):
raise JobLookupError(job_id)
def remove_all_jobs(self):
self.collection.remove()
self.collection.delete_many({})
def shutdown(self):
self.client.close()
@ -133,7 +133,7 @@ class MongoDBJobStore(BaseJobStore):
# Remove all the jobs we failed to restore
if failed_job_ids:
self.collection.remove({'_id': {'$in': failed_job_ids}})
self.collection.delete_many({'_id': {'$in': failed_job_ids}})
return jobs

View file

@ -191,12 +191,11 @@ class BaseScheduler(six.with_metaclass(ABCMeta)):
self.state = STATE_STOPPED
# Shut down all executors
with self._executors_lock:
with self._executors_lock, self._jobstores_lock:
for executor in six.itervalues(self._executors):
executor.shutdown(wait)
# Shut down all job stores
with self._jobstores_lock:
for jobstore in six.itervalues(self._jobstores):
jobstore.shutdown()

View file

@ -7,6 +7,9 @@ try:
except (ImportError, RuntimeError): # pragma: nocover
try:
from PyQt4.QtCore import QObject, QTimer
except ImportError:
try:
from PySide6.QtCore import QObject, QTimer # noqa
except ImportError:
try:
from PySide2.QtCore import QObject, QTimer # noqa
@ -14,7 +17,7 @@ except (ImportError, RuntimeError): # pragma: nocover
try:
from PySide.QtCore import QObject, QTimer # noqa
except ImportError:
raise ImportError('QtScheduler requires either PyQt5, PyQt4, PySide2 '
raise ImportError('QtScheduler requires either PyQt5, PyQt4, PySide6, PySide2 '
'or PySide installed')

View file

@ -6,7 +6,8 @@ import six
from apscheduler.triggers.base import BaseTrigger
from apscheduler.triggers.cron.fields import (
BaseField, MonthField, WeekField, DayOfMonthField, DayOfWeekField, DEFAULT_VALUES)
from apscheduler.util import datetime_ceil, convert_to_datetime, datetime_repr, astimezone
from apscheduler.util import (
datetime_ceil, convert_to_datetime, datetime_repr, astimezone, localize, normalize)
class CronTrigger(BaseTrigger):
@ -143,7 +144,7 @@ class CronTrigger(BaseTrigger):
i += 1
difference = datetime(**values) - dateval.replace(tzinfo=None)
return self.timezone.normalize(dateval + difference), fieldnum
return normalize(dateval + difference), fieldnum
def _set_field_value(self, dateval, fieldnum, new_value):
values = {}
@ -156,7 +157,7 @@ class CronTrigger(BaseTrigger):
else:
values[field.name] = new_value
return self.timezone.localize(datetime(**values))
return localize(datetime(**values), self.timezone)
def get_next_fire_time(self, previous_fire_time, now):
if previous_fire_time:

View file

@ -4,7 +4,9 @@ from math import ceil
from tzlocal import get_localzone
from apscheduler.triggers.base import BaseTrigger
from apscheduler.util import convert_to_datetime, timedelta_seconds, datetime_repr, astimezone
from apscheduler.util import (
convert_to_datetime, normalize, timedelta_seconds, datetime_repr,
astimezone)
class IntervalTrigger(BaseTrigger):
@ -63,7 +65,7 @@ class IntervalTrigger(BaseTrigger):
next_fire_time = self._apply_jitter(next_fire_time, self.jitter, now)
if not self.end_date or next_fire_time <= self.end_date:
return self.timezone.normalize(next_fire_time)
return normalize(next_fire_time)
def __getstate__(self):
return {

View file

@ -34,7 +34,7 @@ except ImportError:
__all__ = ('asint', 'asbool', 'astimezone', 'convert_to_datetime', 'datetime_to_utc_timestamp',
'utc_timestamp_to_datetime', 'timedelta_seconds', 'datetime_ceil', 'get_callable_name',
'obj_to_ref', 'ref_to_obj', 'maybe_ref', 'repr_escape', 'check_callable_args',
'TIMEOUT_MAX')
'normalize', 'localize', 'TIMEOUT_MAX')
class _Undefined(object):
@ -90,9 +90,7 @@ def astimezone(obj):
if isinstance(obj, six.string_types):
return timezone(obj)
if isinstance(obj, tzinfo):
if not hasattr(obj, 'localize') or not hasattr(obj, 'normalize'):
raise TypeError('Only timezones from the pytz library are supported')
if obj.zone == 'local':
if obj.tzname(None) == 'local':
raise ValueError(
'Unable to determine the name of the local timezone -- you must explicitly '
'specify the name of the local timezone. Please refrain from using timezones like '
@ -162,11 +160,7 @@ def convert_to_datetime(input, tz, arg_name):
if isinstance(tz, six.string_types):
tz = timezone(tz)
try:
return tz.localize(datetime_, is_dst=None)
except AttributeError:
raise TypeError(
'Only pytz timezones are supported (need the localize() and normalize() methods)')
return localize(datetime_, tz)
def datetime_to_utc_timestamp(timeval):
@ -431,3 +425,14 @@ def iscoroutinefunction_partial(f):
# The asyncio version of iscoroutinefunction includes testing for @coroutine
# decorations vs. the inspect version which does not.
return iscoroutinefunction(f)
def normalize(dt):
return datetime.fromtimestamp(dt.timestamp(), dt.tzinfo)
def localize(dt, tzinfo):
if hasattr(tzinfo, 'localize'):
return tzinfo.localize(dt)
return normalize(dt.replace(tzinfo=tzinfo))

View file

@ -22,8 +22,8 @@ from pytz.tzfile import build_tzinfo
# The IANA (nee Olson) database is updated several times a year.
OLSON_VERSION = '2021c'
VERSION = '2021.3' # pip compatible version number.
OLSON_VERSION = '2022a'
VERSION = '2022.1' # pip compatible version number.
__version__ = VERSION
OLSEN_VERSION = OLSON_VERSION # Old releases had this misspelling

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -72,11 +72,11 @@ Leap 2016 Dec 31 23:59:60 + S
# Any additional leap seconds will come after this.
# This Expires line is commented out for now,
# so that pre-2020a zic implementations do not reject this file.
#Expires 2022 Jun 28 00:00:00
#Expires 2022 Dec 28 00:00:00
# POSIX timestamps for the data in this file:
#updated 1467936000 (2016-07-08 00:00:00 UTC)
#expires 1656374400 (2022-06-28 00:00:00 UTC)
#expires 1672185600 (2022-12-28 00:00:00 UTC)
# Updated through IERS Bulletin C62
# File expires on: 28 June 2022
# Updated through IERS Bulletin C63
# File expires on: 28 December 2022

View file

@ -1111,8 +1111,10 @@ R P 2016 2018 - Mar Sa>=24 1 1 S
R P 2016 2018 - O Sa>=24 1 0 -
R P 2019 o - Mar 29 0 1 S
R P 2019 o - O Sa>=24 0 0 -
R P 2020 ma - Mar Sa>=24 0 1 S
R P 2020 ma - O Sa>=24 1 0 -
R P 2020 2021 - Mar Sa>=24 0 1 S
R P 2020 o - O 24 1 0 -
R P 2021 ma - O F>=23 1 0 -
R P 2022 ma - Mar Su>=25 0 1 S
Z Asia/Gaza 2:17:52 - LMT 1900 O
2 Z EET/EEST 1948 May 15
2 K EE%sT 1967 Jun 5
@ -1418,10 +1420,11 @@ R FJ 2011 o - Mar Su>=1 3 0 -
R FJ 2012 2013 - Ja Su>=18 3 0 -
R FJ 2014 o - Ja Su>=18 2 0 -
R FJ 2014 2018 - N Su>=1 2 1 -
R FJ 2015 ma - Ja Su>=12 3 0 -
R FJ 2015 2021 - Ja Su>=12 3 0 -
R FJ 2019 o - N Su>=8 2 1 -
R FJ 2020 o - D 20 2 1 -
R FJ 2021 ma - N Su>=8 2 1 -
R FJ 2022 ma - N Su>=8 2 1 -
R FJ 2023 ma - Ja Su>=12 3 0 -
Z Pacific/Fiji 11:55:44 - LMT 1915 O 26
12 FJ +12/+13
Z Pacific/Gambier -8:59:48 - LMT 1912 O
@ -2429,8 +2432,8 @@ Z Europe/Simferopol 2:16:24 - LMT 1880
1 c CE%sT 1944 Ap 13
3 R MSK/MSD 1990
3 - MSK 1990 Jul 1 2
2 - EET 1992
2 e EE%sT 1994 May
2 - EET 1992 Mar 20
2 c EE%sT 1994 May
3 e MSK/MSD 1996 Mar 31 0s
3 1 MSD 1996 O 27 3s
3 R MSK/MSD 1997
@ -2785,7 +2788,7 @@ Z Europe/Kiev 2:2:4 - LMT 1880
1 c CE%sT 1943 N 6
3 R MSK/MSD 1990 Jul 1 2
2 1 EEST 1991 S 29 3
2 e EE%sT 1995
2 c EE%sT 1996 May 13
2 E EE%sT
Z Europe/Uzhgorod 1:29:12 - LMT 1890 O
1 - CET 1940
@ -2795,8 +2798,8 @@ Z Europe/Uzhgorod 1:29:12 - LMT 1890 O
3 R MSK/MSD 1990
3 - MSK 1990 Jul 1 2
1 - CET 1991 Mar 31 3
2 - EET 1992
2 e EE%sT 1995
2 - EET 1992 Mar 20
2 c EE%sT 1996 May 13
2 E EE%sT
Z Europe/Zaporozhye 2:20:40 - LMT 1880
2:20 - +0220 1924 May 2
@ -2804,7 +2807,8 @@ Z Europe/Zaporozhye 2:20:40 - LMT 1880
3 - MSK 1941 Au 25
1 c CE%sT 1943 O 25
3 R MSK/MSD 1991 Mar 31 2
2 e EE%sT 1995
2 e EE%sT 1992 Mar 20
2 c EE%sT 1996 May 13
2 E EE%sT
R u 1918 1919 - Mar lastSu 2 1 D
R u 1918 1919 - O lastSu 2 0 S
@ -4086,12 +4090,12 @@ R x 2016 2018 - May Su>=9 3u 0 -
R x 2016 2018 - Au Su>=9 4u 1 -
R x 2019 ma - Ap Su>=2 3u 0 -
R x 2019 ma - S Su>=2 4u 1 -
Z America/Santiago -4:42:46 - LMT 1890
-4:42:46 - SMT 1910 Ja 10
Z America/Santiago -4:42:45 - LMT 1890
-4:42:45 - SMT 1910 Ja 10
-5 - -05 1916 Jul
-4:42:46 - SMT 1918 S 10
-4:42:45 - SMT 1918 S 10
-4 - -04 1919 Jul
-4:42:46 - SMT 1927 S
-4:42:45 - SMT 1927 S
-5 x -05/-04 1932 S
-4 - -04 1942 Jun
-5 - -05 1942 Au
@ -4101,11 +4105,11 @@ Z America/Santiago -4:42:46 - LMT 1890
-5 - -05 1947 May 21 23
-4 x -04/-03
Z America/Punta_Arenas -4:43:40 - LMT 1890
-4:42:46 - SMT 1910 Ja 10
-4:42:45 - SMT 1910 Ja 10
-5 - -05 1916 Jul
-4:42:46 - SMT 1918 S 10
-4:42:45 - SMT 1918 S 10
-4 - -04 1919 Jul
-4:42:46 - SMT 1927 S
-4:42:45 - SMT 1927 S
-5 x -05/-04 1932 S
-4 - -04 1942 Jun
-5 - -05 1942 Au

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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"
)

View file

@ -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

View file

@ -1,6 +1,6 @@
# IANA versions like 2020a are not valid PEP 440 identifiers; the recommended
# way to translate the version is to use YYYY.n where `n` is a 0-based index.
__version__ = "2021.5"
__version__ = "2022.1"
# This exposes the original IANA version number.
IANA_VERSION = "2021e"
IANA_VERSION = "2022a"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -72,11 +72,11 @@ Leap 2016 Dec 31 23:59:60 + S
# Any additional leap seconds will come after this.
# This Expires line is commented out for now,
# so that pre-2020a zic implementations do not reject this file.
#Expires 2022 Jun 28 00:00:00
#Expires 2022 Dec 28 00:00:00
# POSIX timestamps for the data in this file:
#updated 1467936000 (2016-07-08 00:00:00 UTC)
#expires 1656374400 (2022-06-28 00:00:00 UTC)
#expires 1672185600 (2022-12-28 00:00:00 UTC)
# Updated through IERS Bulletin C62
# File expires on: 28 June 2022
# Updated through IERS Bulletin C63
# File expires on: 28 December 2022

View file

@ -1,4 +1,4 @@
# version 2021e
# version 2022a
# This zic input file is in the public domain.
R d 1916 o - Jun 14 23s 1 S
R d 1916 1919 - O Su>=1 23s 0 -
@ -1111,9 +1111,10 @@ R P 2016 2018 - Mar Sa>=24 1 1 S
R P 2016 2018 - O Sa>=24 1 0 -
R P 2019 o - Mar 29 0 1 S
R P 2019 o - O Sa>=24 0 0 -
R P 2020 ma - Mar Sa>=24 0 1 S
R P 2020 2021 - Mar Sa>=24 0 1 S
R P 2020 o - O 24 1 0 -
R P 2021 ma - O lastF 1 0 -
R P 2021 ma - O F>=23 1 0 -
R P 2022 ma - Mar Su>=25 0 1 S
Z Asia/Gaza 2:17:52 - LMT 1900 O
2 Z EET/EEST 1948 May 15
2 K EE%sT 1967 Jun 5
@ -2431,8 +2432,8 @@ Z Europe/Simferopol 2:16:24 - LMT 1880
1 c CE%sT 1944 Ap 13
3 R MSK/MSD 1990
3 - MSK 1990 Jul 1 2
2 - EET 1992
2 e EE%sT 1994 May
2 - EET 1992 Mar 20
2 c EE%sT 1994 May
3 e MSK/MSD 1996 Mar 31 0s
3 1 MSD 1996 O 27 3s
3 R MSK/MSD 1997
@ -2787,7 +2788,7 @@ Z Europe/Kiev 2:2:4 - LMT 1880
1 c CE%sT 1943 N 6
3 R MSK/MSD 1990 Jul 1 2
2 1 EEST 1991 S 29 3
2 e EE%sT 1995
2 c EE%sT 1996 May 13
2 E EE%sT
Z Europe/Uzhgorod 1:29:12 - LMT 1890 O
1 - CET 1940
@ -2797,8 +2798,8 @@ Z Europe/Uzhgorod 1:29:12 - LMT 1890 O
3 R MSK/MSD 1990
3 - MSK 1990 Jul 1 2
1 - CET 1991 Mar 31 3
2 - EET 1992
2 e EE%sT 1995
2 - EET 1992 Mar 20
2 c EE%sT 1996 May 13
2 E EE%sT
Z Europe/Zaporozhye 2:20:40 - LMT 1880
2:20 - +0220 1924 May 2
@ -2806,7 +2807,8 @@ Z Europe/Zaporozhye 2:20:40 - LMT 1880
3 - MSK 1941 Au 25
1 c CE%sT 1943 O 25
3 R MSK/MSD 1991 Mar 31 2
2 e EE%sT 1995
2 e EE%sT 1992 Mar 20
2 c EE%sT 1996 May 13
2 E EE%sT
R u 1918 1919 - Mar lastSu 2 1 D
R u 1918 1919 - O lastSu 2 0 S
@ -4088,12 +4090,12 @@ R x 2016 2018 - May Su>=9 3u 0 -
R x 2016 2018 - Au Su>=9 4u 1 -
R x 2019 ma - Ap Su>=2 3u 0 -
R x 2019 ma - S Su>=2 4u 1 -
Z America/Santiago -4:42:46 - LMT 1890
-4:42:46 - SMT 1910 Ja 10
Z America/Santiago -4:42:45 - LMT 1890
-4:42:45 - SMT 1910 Ja 10
-5 - -05 1916 Jul
-4:42:46 - SMT 1918 S 10
-4:42:45 - SMT 1918 S 10
-4 - -04 1919 Jul
-4:42:46 - SMT 1927 S
-4:42:45 - SMT 1927 S
-5 x -05/-04 1932 S
-4 - -04 1942 Jun
-5 - -05 1942 Au
@ -4103,11 +4105,11 @@ Z America/Santiago -4:42:46 - LMT 1890
-5 - -05 1947 May 21 23
-4 x -04/-03
Z America/Punta_Arenas -4:43:40 - LMT 1890
-4:42:46 - SMT 1910 Ja 10
-4:42:45 - SMT 1910 Ja 10
-5 - -05 1916 Jul
-4:42:46 - SMT 1918 S 10
-4:42:45 - SMT 1918 S 10
-4 - -04 1919 Jul
-4:42:46 - SMT 1927 S
-4:42:45 - SMT 1927 S
-5 x -05/-04 1932 S
-4 - -04 1942 Jun
-5 - -05 1942 Au

View file

@ -1,5 +1,13 @@
import sys
if sys.platform == 'win32':
from tzlocal.win32 import get_localzone, reload_localzone
if sys.platform == "win32":
from tzlocal.win32 import (
get_localzone,
get_localzone_name,
reload_localzone,
) # pragma: no cover
else:
from tzlocal.unix import get_localzone, reload_localzone
from tzlocal.unix import get_localzone, get_localzone_name, reload_localzone
__all__ = ["get_localzone", "get_localzone_name", "reload_localzone"]

View file

@ -1,97 +1,75 @@
import os
import pytz
import re
import sys
import warnings
from datetime import timezone
import pytz_deprecation_shim as pds
from tzlocal import utils
if sys.version_info >= (3, 9):
from zoneinfo import ZoneInfo # pragma: no cover
else:
from backports.zoneinfo import ZoneInfo # pragma: no cover
_cache_tz = None
_cache_tz_name = None
def _tz_from_env(tzenv):
if tzenv[0] == ':':
tzenv = tzenv[1:]
# TZ specifies a file
if os.path.isabs(tzenv) and os.path.exists(tzenv):
with open(tzenv, 'rb') as tzfile:
return pytz.tzfile.build_tzinfo('local', tzfile)
# TZ specifies a zoneinfo zone.
try:
tz = pytz.timezone(tzenv)
# That worked, so we return this:
return tz
except pytz.UnknownTimeZoneError:
raise pytz.UnknownTimeZoneError(
"tzlocal() does not support non-zoneinfo timezones like %s. \n"
"Please use a timezone in the form of Continent/City")
def _try_tz_from_env():
tzenv = os.environ.get('TZ')
if tzenv:
try:
return _tz_from_env(tzenv)
except pytz.UnknownTimeZoneError:
pass
def _get_localzone(_root='/'):
def _get_localzone_name(_root="/"):
"""Tries to find the local timezone configuration.
This method prefers finding the timezone name and passing that to pytz,
over passing in the localtime file, as in the later case the zoneinfo
name is unknown.
This method finds the timezone name, if it can, or it returns None.
The parameter _root makes the function look for files like /etc/localtime
beneath the _root directory. This is primarily used by the tests.
In normal usage you call the function without parameters."""
tzenv = _try_tz_from_env()
# First try the ENV setting.
tzenv = utils._tz_name_from_env()
if tzenv:
return tzenv
# Are we under Termux on Android?
if os.path.exists('/system/bin/getprop'):
if os.path.exists(os.path.join(_root, "system/bin/getprop")):
import subprocess
androidtz = subprocess.check_output(['getprop', 'persist.sys.timezone']).strip().decode()
return pytz.timezone(androidtz)
androidtz = (
subprocess.check_output(["getprop", "persist.sys.timezone"])
.strip()
.decode()
)
return androidtz
# Now look for distribution specific configuration files
# that contain the timezone name.
for configfile in ('etc/timezone', 'var/db/zoneinfo'):
# Stick all of them in a dict, to compare later.
found_configs = {}
for configfile in ("etc/timezone", "var/db/zoneinfo"):
tzpath = os.path.join(_root, configfile)
try:
with open(tzpath, 'rb') as tzfile:
with open(tzpath, "rt") as tzfile:
data = tzfile.read()
# Issue #3 was that /etc/timezone was a zoneinfo file.
# That's a misconfiguration, but we need to handle it gracefully:
if data[:5] == b'TZif2':
continue
etctz = data.strip().decode()
etctz = data.strip('/ \t\r\n')
if not etctz:
# Empty file, skip
continue
for etctz in data.decode().splitlines():
for etctz in etctz.splitlines():
# Get rid of host definitions and comments:
if ' ' in etctz:
etctz, dummy = etctz.split(' ', 1)
if '#' in etctz:
etctz, dummy = etctz.split('#', 1)
if " " in etctz:
etctz, dummy = etctz.split(" ", 1)
if "#" in etctz:
etctz, dummy = etctz.split("#", 1)
if not etctz:
continue
tz = pytz.timezone(etctz.replace(' ', '_'))
if _root == '/':
# We are using a file in etc to name the timezone.
# Verify that the timezone specified there is actually used:
utils.assert_tz_offset(tz)
return tz
except IOError:
# File doesn't exist or is a directory
found_configs[tzpath] = etctz.replace(" ", "_")
except (IOError, UnicodeDecodeError):
# File doesn't exist or is a directory, or it's a binary file.
continue
# CentOS has a ZONE setting in /etc/sysconfig/clock,
@ -99,14 +77,14 @@ def _get_localzone(_root='/'):
# Gentoo has a TIMEZONE setting in /etc/conf.d/clock
# We look through these files for a timezone:
zone_re = re.compile(r'\s*ZONE\s*=\s*\"')
timezone_re = re.compile(r'\s*TIMEZONE\s*=\s*\"')
end_re = re.compile('\"')
zone_re = re.compile(r"\s*ZONE\s*=\s*\"")
timezone_re = re.compile(r"\s*TIMEZONE\s*=\s*\"")
end_re = re.compile('"')
for filename in ('etc/sysconfig/clock', 'etc/conf.d/clock'):
for filename in ("etc/sysconfig/clock", "etc/conf.d/clock"):
tzpath = os.path.join(_root, filename)
try:
with open(tzpath, 'rt') as tzfile:
with open(tzpath, "rt") as tzfile:
data = tzfile.readlines()
for line in data:
@ -118,48 +96,108 @@ def _get_localzone(_root='/'):
if match is not None:
# Some setting existed
line = line[match.end():]
etctz = line[:end_re.search(line).start()]
etctz = line[: end_re.search(line).start()]
# We found a timezone
tz = pytz.timezone(etctz.replace(' ', '_'))
if _root == '/':
found_configs[tzpath] = etctz.replace(" ", "_")
except (IOError, UnicodeDecodeError):
# UnicodeDecode handles when clock is symlink to /etc/localtime
continue
# systemd distributions use symlinks that include the zone name,
# see manpage of localtime(5) and timedatectl(1)
tzpath = os.path.join(_root, "etc/localtime")
if os.path.exists(tzpath) and os.path.islink(tzpath):
etctz = realtzpath = os.path.realpath(tzpath)
start = etctz.find("/") + 1
while start != 0:
etctz = etctz[start:]
try:
pds.timezone(etctz)
tzinfo = f"{tzpath} is a symlink to"
found_configs[tzinfo] = etctz.replace(" ", "_")
except pds.UnknownTimeZoneError:
pass
start = etctz.find("/") + 1
if len(found_configs) > 0:
# We found some explicit config of some sort!
if len(found_configs) > 1:
# Uh-oh, multiple configs. See if they match:
unique_tzs = set()
zoneinfo = os.path.join(_root, "usr", "share", "zoneinfo")
directory_depth = len(zoneinfo.split(os.path.sep))
for tzname in found_configs.values():
# Look them up in /usr/share/zoneinfo, and find what they
# really point to:
path = os.path.realpath(os.path.join(zoneinfo, *tzname.split("/")))
real_zone_name = "/".join(path.split(os.path.sep)[directory_depth:])
unique_tzs.add(real_zone_name)
if len(unique_tzs) != 1:
message = "Multiple conflicting time zone configurations found:\n"
for key, value in found_configs.items():
message += f"{key}: {value}\n"
message += "Fix the configuration, or set the time zone in a TZ environment variable.\n"
raise utils.ZoneInfoNotFoundError(message)
# We found exactly one config! Use it.
return list(found_configs.values())[0]
def _get_localzone(_root="/"):
"""Creates a timezone object from the timezone name.
If there is no timezone config, it will try to create a file from the
localtime timezone, and if there isn't one, it will default to UTC.
The parameter _root makes the function look for files like /etc/localtime
beneath the _root directory. This is primarily used by the tests.
In normal usage you call the function without parameters."""
# First try the ENV setting.
tzenv = utils._tz_from_env()
if tzenv:
return tzenv
tzname = _get_localzone_name(_root)
if tzname is None:
# No explicit setting existed. Use localtime
for filename in ("etc/localtime", "usr/local/etc/localtime"):
tzpath = os.path.join(_root, filename)
if not os.path.exists(tzpath):
continue
with open(tzpath, "rb") as tzfile:
tz = pds.wrap_zone(ZoneInfo.from_file(tzfile, key="local"))
break
else:
warnings.warn("Can not find any timezone configuration, defaulting to UTC.")
tz = timezone.utc
else:
tz = pds.timezone(tzname)
if _root == "/":
# We are using a file in etc to name the timezone.
# Verify that the timezone specified there is actually used:
utils.assert_tz_offset(tz)
return tz
except IOError:
# File doesn't exist or is a directory
continue
# systemd distributions use symlinks that include the zone name,
# see manpage of localtime(5) and timedatectl(1)
tzpath = os.path.join(_root, 'etc/localtime')
if os.path.exists(tzpath) and os.path.islink(tzpath):
tzpath = os.path.realpath(tzpath)
start = tzpath.find("/")+1
while start != 0:
tzpath = tzpath[start:]
try:
return pytz.timezone(tzpath)
except pytz.UnknownTimeZoneError:
pass
start = tzpath.find("/")+1
def get_localzone_name():
"""Get the computers configured local timezone name, if any."""
global _cache_tz_name
if _cache_tz_name is None:
_cache_tz_name = _get_localzone_name()
# No explicit setting existed. Use localtime
for filename in ('etc/localtime', 'usr/local/etc/localtime'):
tzpath = os.path.join(_root, filename)
return _cache_tz_name
if not os.path.exists(tzpath):
continue
with open(tzpath, 'rb') as tzfile:
return pytz.tzfile.build_tzinfo('local', tzfile)
warnings.warn('Can not find any timezone configuration, defaulting to UTC.')
return pytz.utc
def get_localzone():
"""Get the computers configured local timezone, if any."""
global _cache_tz
if _cache_tz is None:
_cache_tz = _get_localzone()
@ -169,6 +207,9 @@ def get_localzone():
def reload_localzone():
"""Reload the cached localzone. You need to call this if the timezone has changed."""
global _cache_tz_name
global _cache_tz
_cache_tz_name = _get_localzone_name()
_cache_tz = _get_localzone()
return _cache_tz

View file

@ -1,7 +1,24 @@
# -*- coding: utf-8 -*-
import os
import time
import datetime
import calendar
import pytz_deprecation_shim as pds
try:
import zoneinfo # pragma: no cover
except ImportError:
from backports import zoneinfo # pragma: no cover
from tzlocal import windows_tz
class ZoneInfoNotFoundError(pds.UnknownTimeZoneError, zoneinfo.ZoneInfoNotFoundError):
"""An exception derived from both pytz and zoneinfo
This exception will be trappable both by pytz expecting clients and
zoneinfo expecting clients.
"""
def get_system_offset():
@ -21,9 +38,9 @@ def get_system_offset():
# so we check that the difference is less than one minute, because nobody
# has that small DST differences.
if abs(offset - time.altzone) < 60:
return -time.altzone
return -time.altzone # pragma: no cover
else:
return -time.timezone
return -time.timezone # pragma: no cover
def get_tz_offset(tz):
@ -39,8 +56,73 @@ def assert_tz_offset(tz):
tz_offset = get_tz_offset(tz)
system_offset = get_system_offset()
if tz_offset != system_offset:
msg = ('Timezone offset does not match system offset: {0} != {1}. '
'Please, check your config files.').format(
tz_offset, system_offset
)
msg = (
"Timezone offset does not match system offset: {} != {}. "
"Please, check your config files."
).format(tz_offset, system_offset)
raise ValueError(msg)
def _tz_name_from_env(tzenv=None):
if tzenv is None:
tzenv = os.environ.get("TZ")
if not tzenv:
return None
if tzenv[0] == ":":
tzenv = tzenv[1:]
if tzenv in windows_tz.tz_win:
# Yup, it's a timezone
return tzenv
if os.path.isabs(tzenv) and os.path.exists(tzenv):
# It's a file specification, expand it, if possible
parts = os.path.realpath(tzenv).split(os.sep)
# Is it a zone info zone?
possible_tz = "/".join(parts[-2:])
if possible_tz in windows_tz.tz_win:
# Yup, it is
return possible_tz
# Maybe it's a short one, like UTC?
if parts[-1] in windows_tz.tz_win:
# Indeed
return parts[-1]
def _tz_from_env(tzenv=None):
if tzenv is None:
tzenv = os.environ.get("TZ")
if not tzenv:
return None
# Some weird format that exists:
if tzenv[0] == ":":
tzenv = tzenv[1:]
# TZ specifies a file
if os.path.isabs(tzenv) and os.path.exists(tzenv):
# Try to see if we can figure out the name
tzname = _tz_name_from_env(tzenv)
if not tzname:
# Nope, not a standard timezone name, just take the filename
tzname = tzenv.split(os.sep)[-1]
with open(tzenv, "rb") as tzfile:
zone = zoneinfo.ZoneInfo.from_file(tzfile, key=tzname)
return pds.wrap_zone(zone)
# TZ must specify a zoneinfo zone.
try:
tz = pds.timezone(tzenv)
# That worked, so we return this:
return tz
except pds.UnknownTimeZoneError:
# Nope, it's something like "PST4DST" etc, we can't handle that.
raise ZoneInfoNotFoundError(
"tzlocal() does not support non-zoneinfo timezones like %s. \n"
"Please use a timezone in the form of Continent/City"
) from None

View file

@ -1,32 +1,53 @@
from datetime import datetime
import pytz_deprecation_shim as pds
try:
import _winreg as winreg
except ImportError:
import winreg
import pytz
from tzlocal.windows_tz import win_tz
from tzlocal import utils
_cache_tz = None
_cache_tz_name = None
def valuestodict(key):
"""Convert a registry key's values to a dictionary."""
dict = {}
result = {}
size = winreg.QueryInfoKey(key)[1]
for i in range(size):
data = winreg.EnumValue(key, i)
dict[data[0]] = data[1]
return dict
result[data[0]] = data[1]
return result
def get_localzone_name():
def _get_dst_info(tz):
# Find the offset for when it doesn't have DST:
dst_offset = std_offset = None
has_dst = False
year = datetime.now().year
for dt in (datetime(year, 1, 1), datetime(year, 6, 1)):
if tz.dst(dt).total_seconds() == 0.0:
# OK, no DST during winter, get this offset
std_offset = tz.utcoffset(dt).total_seconds()
else:
has_dst = True
return has_dst, std_offset, dst_offset
def _get_localzone_name():
# Windows is special. It has unique time zone names (in several
# meanings of the word) available, but unfortunately, they can be
# translated to the language of the operating system, so we need to
# do a backwards lookup, by going through all time zones and see which
# one matches.
tzenv = utils._tz_name_from_env()
if tzenv:
return tzenv
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
@ -34,44 +55,16 @@ def get_localzone_name():
keyvalues = valuestodict(localtz)
localtz.Close()
if 'TimeZoneKeyName' in keyvalues:
# Windows 7 (and Vista?)
if "TimeZoneKeyName" in keyvalues:
# Windows 7 and later
# For some reason this returns a string with loads of NUL bytes at
# least on some systems. I don't know if this is a bug somewhere, I
# just work around it.
tzkeyname = keyvalues['TimeZoneKeyName'].split('\x00', 1)[0]
tzkeyname = keyvalues["TimeZoneKeyName"].split("\x00", 1)[0]
else:
# Windows 2000 or XP
# This is the localized name:
tzwin = keyvalues['StandardName']
# Open the list of timezones to look up the real name:
TZKEYNAME = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
tzkey = winreg.OpenKey(handle, TZKEYNAME)
# Now, match this value to Time Zone information
tzkeyname = None
for i in range(winreg.QueryInfoKey(tzkey)[0]):
subkey = winreg.EnumKey(tzkey, i)
sub = winreg.OpenKey(tzkey, subkey)
data = valuestodict(sub)
sub.Close()
try:
if data['Std'] == tzwin:
tzkeyname = subkey
break
except KeyError:
# This timezone didn't have proper configuration.
# Ignore it.
pass
tzkey.Close()
handle.Close()
if tzkeyname is None:
raise LookupError('Can not find Windows timezone configuration')
# Don't support XP any longer
raise LookupError("Can not find Windows timezone configuration")
timezone = win_tz.get(tzkeyname)
if timezone is None:
@ -81,24 +74,64 @@ def get_localzone_name():
# Return what we have.
if timezone is None:
raise pytz.UnknownTimeZoneError('Can not find timezone ' + tzkeyname)
raise utils.ZoneInfoNotFoundError(tzkeyname)
if keyvalues.get("DynamicDaylightTimeDisabled", 0) == 1:
# DST is disabled, so don't return the timezone name,
# instead return Etc/GMT+offset
tz = pds.timezone(timezone)
has_dst, std_offset, dst_offset = _get_dst_info(tz)
if not has_dst:
# The DST is turned off in the windows configuration,
# but this timezone doesn't have DST so it doesn't matter
return timezone
if std_offset is None:
raise utils.ZoneInfoNotFoundError(
f"{tzkeyname} claims to not have a non-DST time!?")
if std_offset % 3600:
# I can't convert this to an hourly offset
raise utils.ZoneInfoNotFoundError(
f"tzlocal can't support disabling DST in the {timezone} zone.")
# This has whole hours as offset, return it as Etc/GMT
return f"Etc/GMT{-std_offset//3600:+.0f}"
return timezone
def get_localzone_name():
"""Get the zoneinfo timezone name that matches the Windows-configured timezone."""
global _cache_tz_name
if _cache_tz_name is None:
_cache_tz_name = _get_localzone_name()
return _cache_tz_name
def get_localzone():
"""Returns the zoneinfo-based tzinfo object that matches the Windows-configured timezone."""
global _cache_tz
if _cache_tz is None:
_cache_tz = pytz.timezone(get_localzone_name())
_cache_tz = pds.timezone(get_localzone_name())
if not utils._tz_name_from_env():
# If the timezone does NOT come from a TZ environment variable,
# verify that it's correct. If it's from the environment,
# we accept it, this is so you can run tests with different timezones.
utils.assert_tz_offset(_cache_tz)
return _cache_tz
def reload_localzone():
"""Reload the cached localzone. You need to call this if the timezone has changed."""
global _cache_tz
_cache_tz = pytz.timezone(get_localzone_name())
global _cache_tz_name
_cache_tz_name = _get_localzone_name()
_cache_tz = pds.timezone(_cache_tz_name)
utils.assert_tz_offset(_cache_tz)
return _cache_tz

View file

@ -104,6 +104,7 @@ win_tz = {'AUS Central Standard Time': 'Australia/Darwin',
'Saratov Standard Time': 'Europe/Saratov',
'Singapore Standard Time': 'Asia/Singapore',
'South Africa Standard Time': 'Africa/Johannesburg',
'South Sudan Standard Time': 'Africa/Juba',
'Sri Lanka Standard Time': 'Asia/Colombo',
'Sudan Standard Time': 'Africa/Khartoum',
'Syria Standard Time': 'Asia/Damascus',
@ -118,7 +119,7 @@ win_tz = {'AUS Central Standard Time': 'Australia/Darwin',
'Turks And Caicos Standard Time': 'America/Grand_Turk',
'US Eastern Standard Time': 'America/Indianapolis',
'US Mountain Standard Time': 'America/Phoenix',
'UTC': 'Etc/GMT',
'UTC': 'Etc/UTC',
'UTC+12': 'Etc/GMT-12',
'UTC+13': 'Etc/GMT-13',
'UTC-02': 'Etc/GMT+2',
@ -136,7 +137,8 @@ win_tz = {'AUS Central Standard Time': 'Australia/Darwin',
'West Asia Standard Time': 'Asia/Tashkent',
'West Bank Standard Time': 'Asia/Hebron',
'West Pacific Standard Time': 'Pacific/Port_Moresby',
'Yakutsk Standard Time': 'Asia/Yakutsk'}
'Yakutsk Standard Time': 'Asia/Yakutsk',
'Yukon Standard Time': 'America/Whitehorse'}
# Old name for the win_tz variable:
tz_names = win_tz
@ -166,7 +168,7 @@ tz_win = {'Africa/Abidjan': 'Greenwich Standard Time',
'Africa/Gaborone': 'South Africa Standard Time',
'Africa/Harare': 'South Africa Standard Time',
'Africa/Johannesburg': 'South Africa Standard Time',
'Africa/Juba': 'E. Africa Standard Time',
'Africa/Juba': 'South Sudan Standard Time',
'Africa/Kampala': 'E. Africa Standard Time',
'Africa/Khartoum': 'Sudan Standard Time',
'Africa/Kigali': 'South Africa Standard Time',
@ -234,8 +236,8 @@ tz_win = {'Africa/Abidjan': 'Greenwich Standard Time',
'America/Creston': 'US Mountain Standard Time',
'America/Cuiaba': 'Central Brazilian Standard Time',
'America/Curacao': 'SA Western Standard Time',
'America/Danmarkshavn': 'UTC',
'America/Dawson': 'Pacific Standard Time',
'America/Danmarkshavn': 'Greenwich Standard Time',
'America/Dawson': 'Yukon Standard Time',
'America/Dawson_Creek': 'US Mountain Standard Time',
'America/Denver': 'Mountain Standard Time',
'America/Detroit': 'Eastern Standard Time',
@ -345,14 +347,14 @@ tz_win = {'Africa/Abidjan': 'Greenwich Standard Time',
'America/Tortola': 'SA Western Standard Time',
'America/Vancouver': 'Pacific Standard Time',
'America/Virgin': 'SA Western Standard Time',
'America/Whitehorse': 'Pacific Standard Time',
'America/Whitehorse': 'Yukon Standard Time',
'America/Winnipeg': 'Central Standard Time',
'America/Yakutat': 'Alaskan Standard Time',
'America/Yellowknife': 'Mountain Standard Time',
'Antarctica/Casey': 'Singapore Standard Time',
'Antarctica/Casey': 'Central Pacific Standard Time',
'Antarctica/Davis': 'SE Asia Standard Time',
'Antarctica/DumontDUrville': 'West Pacific Standard Time',
'Antarctica/Macquarie': 'Central Pacific Standard Time',
'Antarctica/Macquarie': 'Tasmania Standard Time',
'Antarctica/Mawson': 'West Asia Standard Time',
'Antarctica/McMurdo': 'New Zealand Standard Time',
'Antarctica/Palmer': 'SA Eastern Standard Time',
@ -501,7 +503,7 @@ tz_win = {'Africa/Abidjan': 'Greenwich Standard Time',
'Canada/Newfoundland': 'Newfoundland Standard Time',
'Canada/Pacific': 'Pacific Standard Time',
'Canada/Saskatchewan': 'Canada Central Standard Time',
'Canada/Yukon': 'Pacific Standard Time',
'Canada/Yukon': 'Yukon Standard Time',
'Chile/Continental': 'Pacific SA Standard Time',
'Chile/EasterIsland': 'Easter Island Standard Time',
'Cuba': 'Cuba Standard Time',

View file

@ -1,4 +1,4 @@
apscheduler==3.8.0
apscheduler==3.9.1
importlib-resources==5.6.0
pyinstaller==4.9
pyopenssl==22.0.0

View file

@ -1,5 +1,5 @@
appdirs==1.4.4
apscheduler==3.8.0
apscheduler==3.9.1
arrow==1.2.2
backports.csv==1.0.7
backports.functools-lru-cache==1.6.4
@ -33,7 +33,7 @@ PyJWT==2.3.0
pyparsing==3.0.7
python-dateutil==2.8.2
python-twitter==3.5
pytz==2021.3
pytz==2022.1
requests==2.27.1
requests-oauthlib==1.3.1
rumps==0.3.0; platform_system == "Darwin"
@ -42,8 +42,8 @@ six==1.16.0
soupsieve==2.3.1
tempora==5.0.1
tokenize-rt==4.2.1
tzdata==2021.5
tzlocal==2.1 # apscheduler==3.8.0 requires tzlocal~=2.0
tzdata==2022.1
tzlocal==4.2
urllib3==1.26.8
webencodings==0.5.1
websocket-client==1.2.3