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

@ -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 == '/':
# 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
found_configs[tzpath] = etctz.replace(" ", "_")
except IOError:
# File doesn't exist or is a directory
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')
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
etctz = realtzpath = os.path.realpath(tzpath)
start = etctz.find("/") + 1
while start != 0:
tzpath = tzpath[start:]
etctz = etctz[start:]
try:
return pytz.timezone(tzpath)
except pytz.UnknownTimeZoneError:
pds.timezone(etctz)
tzinfo = f"{tzpath} is a symlink to"
found_configs[tzinfo] = etctz.replace(" ", "_")
except pds.UnknownTimeZoneError:
pass
start = tzpath.find("/")+1
start = etctz.find("/") + 1
# No explicit setting existed. Use localtime
for filename in ('etc/localtime', 'usr/local/etc/localtime'):
tzpath = os.path.join(_root, filename)
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))
if not os.path.exists(tzpath):
continue
with open(tzpath, 'rb') as tzfile:
return pytz.tzfile.build_tzinfo('local', tzfile)
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
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()
return _cache_tz_name
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)
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',