Revert "Update apscheduler==3.8.1"

This reverts commit 973cad264f.
This commit is contained in:
JonnyWong16 2021-11-28 13:47:49 -08:00
commit c0fb1dbbdc
No known key found for this signature in database
GPG key ID: B1F1F9807184697A
20 changed files with 333 additions and 1122 deletions

View file

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

View file

@ -1,75 +1,97 @@
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 _get_localzone_name(_root="/"):
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='/'):
"""Tries to find the local timezone configuration.
This method finds the timezone name, if it can, or it returns None.
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.
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_name_from_env()
tzenv = _try_tz_from_env()
if tzenv:
return tzenv
# Are we under Termux on Android?
if os.path.exists(os.path.join(_root, "system/bin/getprop")):
if os.path.exists('/system/bin/getprop'):
import subprocess
androidtz = (
subprocess.check_output(["getprop", "persist.sys.timezone"])
.strip()
.decode()
)
return androidtz
androidtz = subprocess.check_output(['getprop', 'persist.sys.timezone']).strip().decode()
return pytz.timezone(androidtz)
# Now look for distribution specific configuration files
# that contain the timezone name.
# Stick all of them in a dict, to compare later.
found_configs = {}
for configfile in ("etc/timezone", "var/db/zoneinfo"):
for configfile in ('etc/timezone', 'var/db/zoneinfo'):
tzpath = os.path.join(_root, configfile)
try:
with open(tzpath, "rt") as tzfile:
with open(tzpath, 'rb') as tzfile:
data = tzfile.read()
etctz = data.strip('/ \t\r\n')
# 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()
if not etctz:
# Empty file, skip
continue
for etctz in etctz.splitlines():
for etctz in data.decode().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
found_configs[tzpath] = etctz.replace(" ", "_")
except (IOError, UnicodeDecodeError):
# File doesn't exist or is a directory, or it's a binary file.
except IOError:
# File doesn't exist or is a directory
continue
# CentOS has a ZONE setting in /etc/sysconfig/clock,
@ -77,14 +99,14 @@ def _get_localzone_name(_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:
@ -96,108 +118,48 @@ def _get_localzone_name(_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
found_configs[tzpath] = etctz.replace(" ", "_")
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, UnicodeDecodeError):
# UnicodeDecode handles when clock is symlink to /etc/localtime
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")
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
tzpath = os.path.realpath(tzpath)
start = tzpath.find("/")+1
while start != 0:
etctz = etctz[start:]
tzpath = tzpath[start:]
try:
pds.timezone(etctz)
tzinfo = f"{tzpath} is a symlink to"
found_configs[tzinfo] = etctz.replace(" ", "_")
except pds.UnknownTimeZoneError:
return pytz.timezone(tzpath)
except pytz.UnknownTimeZoneError:
pass
start = etctz.find("/") + 1
start = tzpath.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))
# No explicit setting existed. Use localtime
for filename in ('etc/localtime', 'usr/local/etc/localtime'):
tzpath = os.path.join(_root, filename)
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
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()
@ -207,9 +169,6 @@ 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,24 +1,7 @@
# -*- 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():
@ -38,9 +21,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 # pragma: no cover
return -time.altzone
else:
return -time.timezone # pragma: no cover
return -time.timezone
def get_tz_offset(tz):
@ -56,70 +39,8 @@ 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: {} != {}. "
"Please, check your config files."
).format(tz_offset, system_offset)
msg = ('Timezone offset does not match system offset: {0} != {1}. '
'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 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
parts = 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,53 +1,32 @@
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."""
result = {}
dict = {}
size = winreg.QueryInfoKey(key)[1]
for i in range(size):
data = winreg.EnumValue(key, i)
result[data[0]] = data[1]
return result
dict[data[0]] = data[1]
return dict
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():
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"
@ -55,16 +34,44 @@ def _get_localzone_name():
keyvalues = valuestodict(localtz)
localtz.Close()
if "TimeZoneKeyName" in keyvalues:
# Windows 7 and later
if 'TimeZoneKeyName' in keyvalues:
# Windows 7 (and Vista?)
# 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:
# Don't support XP any longer
raise LookupError("Can not find Windows timezone configuration")
# 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')
timezone = win_tz.get(tzkeyname)
if timezone is None:
@ -74,64 +81,24 @@ def _get_localzone_name():
# Return what we have.
if timezone is None:
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}"
raise pytz.UnknownTimeZoneError('Can not find timezone ' + tzkeyname)
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 = 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)
_cache_tz = pytz.timezone(get_localzone_name())
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
global _cache_tz_name
_cache_tz_name = _get_localzone_name()
_cache_tz = pds.timezone(_cache_tz_name)
_cache_tz = pytz.timezone(get_localzone_name())
utils.assert_tz_offset(_cache_tz)
return _cache_tz

View file

@ -104,7 +104,6 @@ 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',
@ -119,7 +118,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/UTC',
'UTC': 'Etc/GMT',
'UTC+12': 'Etc/GMT-12',
'UTC+13': 'Etc/GMT-13',
'UTC-02': 'Etc/GMT+2',
@ -137,8 +136,7 @@ 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',
'Yukon Standard Time': 'America/Whitehorse'}
'Yakutsk Standard Time': 'Asia/Yakutsk'}
# Old name for the win_tz variable:
tz_names = win_tz
@ -168,7 +166,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': 'South Sudan Standard Time',
'Africa/Juba': 'E. Africa Standard Time',
'Africa/Kampala': 'E. Africa Standard Time',
'Africa/Khartoum': 'Sudan Standard Time',
'Africa/Kigali': 'South Africa Standard Time',
@ -236,8 +234,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': 'Greenwich Standard Time',
'America/Dawson': 'Yukon Standard Time',
'America/Danmarkshavn': 'UTC',
'America/Dawson': 'Pacific Standard Time',
'America/Dawson_Creek': 'US Mountain Standard Time',
'America/Denver': 'Mountain Standard Time',
'America/Detroit': 'Eastern Standard Time',
@ -347,14 +345,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': 'Yukon Standard Time',
'America/Whitehorse': 'Pacific Standard Time',
'America/Winnipeg': 'Central Standard Time',
'America/Yakutat': 'Alaskan Standard Time',
'America/Yellowknife': 'Mountain Standard Time',
'Antarctica/Casey': 'Central Pacific Standard Time',
'Antarctica/Casey': 'Singapore Standard Time',
'Antarctica/Davis': 'SE Asia Standard Time',
'Antarctica/DumontDUrville': 'West Pacific Standard Time',
'Antarctica/Macquarie': 'Tasmania Standard Time',
'Antarctica/Macquarie': 'Central Pacific Standard Time',
'Antarctica/Mawson': 'West Asia Standard Time',
'Antarctica/McMurdo': 'New Zealand Standard Time',
'Antarctica/Palmer': 'SA Eastern Standard Time',
@ -503,7 +501,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': 'Yukon Standard Time',
'Canada/Yukon': 'Pacific Standard Time',
'Chile/Continental': 'Pacific SA Standard Time',
'Chile/EasterIsland': 'Easter Island Standard Time',
'Cuba': 'Cuba Standard Time',