Update tzlocal==5.0.1

This commit is contained in:
JonnyWong16 2023-08-25 11:33:56 -07:00
parent 80e6131a0d
commit 85bc9c39ae
No known key found for this signature in database
GPG key ID: B1F1F9807184697A
6 changed files with 807 additions and 772 deletions

View file

@ -9,5 +9,12 @@ if sys.platform == "win32":
else:
from tzlocal.unix import get_localzone, get_localzone_name, reload_localzone
from tzlocal.utils import assert_tz_offset
__all__ = ["get_localzone", "get_localzone_name", "reload_localzone"]
__all__ = [
"get_localzone",
"get_localzone_name",
"reload_localzone",
"assert_tz_offset",
]

View file

@ -1,20 +1,21 @@
import logging
import os
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
import zoneinfo # pragma: no cover
else:
from backports.zoneinfo import ZoneInfo # pragma: no cover
from backports import zoneinfo # pragma: no cover
_cache_tz = None
_cache_tz_name = None
log = logging.getLogger("tzlocal")
def _get_localzone_name(_root="/"):
"""Tries to find the local timezone configuration.
@ -32,14 +33,21 @@ def _get_localzone_name(_root="/"):
# Are we under Termux on Android?
if os.path.exists(os.path.join(_root, "system/bin/getprop")):
log.debug("This looks like Termux")
import subprocess
try:
androidtz = (
subprocess.check_output(["getprop", "persist.sys.timezone"])
.strip()
.decode()
)
return androidtz
except (OSError, subprocess.CalledProcessError):
# proot environment or failed to getprop
log.debug("It's not termux?")
pass
# Now look for distribution specific configuration files
# that contain the timezone name.
@ -50,10 +58,11 @@ def _get_localzone_name(_root="/"):
for configfile in ("etc/timezone", "var/db/zoneinfo"):
tzpath = os.path.join(_root, configfile)
try:
with open(tzpath, "rt") as tzfile:
with open(tzpath) as tzfile:
data = tzfile.read()
log.debug(f"{tzpath} found, contents:\n {data}")
etctz = data.strip('/ \t\r\n')
etctz = data.strip("/ \t\r\n")
if not etctz:
# Empty file, skip
continue
@ -68,7 +77,7 @@ def _get_localzone_name(_root="/"):
found_configs[tzpath] = etctz.replace(" ", "_")
except (IOError, UnicodeDecodeError):
except (OSError, UnicodeDecodeError):
# File doesn't exist or is a directory, or it's a binary file.
continue
@ -86,6 +95,7 @@ def _get_localzone_name(_root="/"):
try:
with open(tzpath, "rt") as tzfile:
data = tzfile.readlines()
log.debug(f"{tzpath} found, contents:\n {data}")
for line in data:
# Look for the ZONE= setting.
@ -101,7 +111,7 @@ def _get_localzone_name(_root="/"):
# We found a timezone
found_configs[tzpath] = etctz.replace(" ", "_")
except (IOError, UnicodeDecodeError):
except (OSError, UnicodeDecodeError):
# UnicodeDecode handles when clock is symlink to /etc/localtime
continue
@ -109,30 +119,34 @@ def _get_localzone_name(_root="/"):
# 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)
log.debug(f"{tzpath} found")
etctz = os.path.realpath(tzpath)
start = etctz.find("/") + 1
while start != 0:
etctz = etctz[start:]
try:
pds.timezone(etctz)
zoneinfo.ZoneInfo(etctz)
tzinfo = f"{tzpath} is a symlink to"
found_configs[tzinfo] = etctz.replace(" ", "_")
except pds.UnknownTimeZoneError:
# Only need first valid relative path in simlink.
break
except zoneinfo.ZoneInfoNotFoundError:
pass
start = etctz.find("/") + 1
if len(found_configs) > 0:
log.debug(f"{len(found_configs)} found:\n {found_configs}")
# 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))
zoneinfopath = os.path.join(_root, "usr", "share", "zoneinfo")
directory_depth = len(zoneinfopath.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("/")))
path = os.path.realpath(os.path.join(zoneinfopath, *tzname.split("/")))
real_zone_name = "/".join(path.split(os.path.sep)[directory_depth:])
unique_tzs.add(real_zone_name)
@ -141,7 +155,7 @@ def _get_localzone_name(_root="/"):
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)
raise zoneinfo.ZoneInfoNotFoundError(message)
# We found exactly one config! Use it.
return list(found_configs.values())[0]
@ -165,24 +179,25 @@ def _get_localzone(_root="/"):
tzname = _get_localzone_name(_root)
if tzname is None:
# No explicit setting existed. Use localtime
log.debug("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"))
tz = zoneinfo.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)
tz = zoneinfo.ZoneInfo(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)
utils.assert_tz_offset(tz, error=False)
return tz

View file

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
import logging
import os
import time
import datetime
import calendar
import pytz_deprecation_shim as pds
import warnings
try:
import zoneinfo # pragma: no cover
@ -12,35 +12,7 @@ except ImportError:
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():
"""Get system's timezone offset using built-in library time.
For the Timezone constants (altzone, daylight, timezone, and tzname), the
value is determined by the timezone rules in effect at module load time or
the last time tzset() is called and may be incorrect for times in the past.
To keep compatibility with Windows, we're always importing time module here.
"""
localtime = calendar.timegm(time.localtime())
gmtime = calendar.timegm(time.gmtime())
offset = gmtime - localtime
# We could get the localtime and gmtime on either side of a second switch
# 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
else:
return -time.timezone # pragma: no cover
log = logging.getLogger("tzlocal")
def get_tz_offset(tz):
@ -48,19 +20,27 @@ def get_tz_offset(tz):
return int(datetime.datetime.now(tz).utcoffset().total_seconds())
def assert_tz_offset(tz):
def assert_tz_offset(tz, error=True):
"""Assert that system's timezone offset equals to the timezone offset found.
If they don't match, we probably have a misconfiguration, for example, an
incorrect timezone set in /etc/timezone file in systemd distributions."""
incorrect timezone set in /etc/timezone file in systemd distributions.
If error is True, this method will raise a ValueError, otherwise it will
emit a warning.
"""
tz_offset = get_tz_offset(tz)
system_offset = get_system_offset()
if tz_offset != system_offset:
system_offset = calendar.timegm(time.localtime()) - calendar.timegm(time.gmtime())
# No one has timezone offsets less than a minute, so this should be close enough:
if abs(tz_offset - system_offset) > 60:
msg = (
"Timezone offset does not match system offset: {} != {}. "
"Please, check your config files."
).format(tz_offset, system_offset)
if error:
raise ValueError(msg)
warnings.warn(msg)
def _tz_name_from_env(tzenv=None):
@ -70,6 +50,8 @@ def _tz_name_from_env(tzenv=None):
if not tzenv:
return None
log.debug(f"Found a TZ environment: {tzenv}")
if tzenv[0] == ":":
tzenv = tzenv[1:]
@ -92,6 +74,9 @@ def _tz_name_from_env(tzenv=None):
# Indeed
return parts[-1]
log.debug("TZ does not contain a time zone name")
return None
def _tz_from_env(tzenv=None):
if tzenv is None:
@ -112,17 +97,16 @@ def _tz_from_env(tzenv=None):
# 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)
return zoneinfo.ZoneInfo.from_file(tzfile, key=tzname)
# TZ must specify a zoneinfo zone.
try:
tz = pds.timezone(tzenv)
tz = zoneinfo.ZoneInfo(tzenv)
# That worked, so we return this:
return tz
except pds.UnknownTimeZoneError:
except zoneinfo.ZoneInfoNotFoundError:
# Nope, it's something like "PST4DST" etc, we can't handle that.
raise ZoneInfoNotFoundError(
raise zoneinfo.ZoneInfoNotFoundError(
"tzlocal() does not support non-zoneinfo timezones like %s. \n"
"Please use a timezone in the form of Continent/City"
"Please use a timezone in the form of Continent/City" % tzenv
) from None

View file

@ -1,17 +1,24 @@
import logging
from datetime import datetime
import pytz_deprecation_shim as pds
try:
import _winreg as winreg
except ImportError:
import winreg
try:
import zoneinfo # pragma: no cover
except ImportError:
from backports import zoneinfo # pragma: no cover
from tzlocal.windows_tz import win_tz
from tzlocal import utils
_cache_tz = None
_cache_tz_name = None
log = logging.getLogger("tzlocal")
def valuestodict(key):
"""Convert a registry key's values to a dictionary."""
@ -48,6 +55,7 @@ def _get_localzone_name():
if tzenv:
return tzenv
log.debug("Looking up time zone info from registry")
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
@ -74,13 +82,13 @@ def _get_localzone_name():
# Return what we have.
if timezone is None:
raise utils.ZoneInfoNotFoundError(tzkeyname)
raise zoneinfo.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)
tz = zoneinfo.ZoneInfo(timezone)
has_dst, std_offset, dst_offset = _get_dst_info(tz)
if not has_dst:
# The DST is turned off in the windows configuration,
@ -88,13 +96,15 @@ def _get_localzone_name():
return timezone
if std_offset is None:
raise utils.ZoneInfoNotFoundError(
f"{tzkeyname} claims to not have a non-DST time!?")
raise zoneinfo.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.")
raise zoneinfo.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}"
@ -116,13 +126,13 @@ def get_localzone():
global _cache_tz
if _cache_tz is None:
_cache_tz = pds.timezone(get_localzone_name())
_cache_tz = zoneinfo.ZoneInfo(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, error=False)
return _cache_tz
@ -132,6 +142,6 @@ def reload_localzone():
global _cache_tz
global _cache_tz_name
_cache_tz_name = _get_localzone_name()
_cache_tz = pds.timezone(_cache_tz_name)
utils.assert_tz_offset(_cache_tz)
_cache_tz = zoneinfo.ZoneInfo(_cache_tz_name)
utils.assert_tz_offset(_cache_tz, error=False)
return _cache_tz

File diff suppressed because it is too large Load diff

View file

@ -44,7 +44,7 @@ six==1.16.0
tempora==5.5.0
tokenize-rt==5.2.0
tzdata==2023.3
tzlocal==4.2
tzlocal==5.0.1
urllib3<2
webencodings==0.5.1
websocket-client==1.6.2