mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-05 20:51:15 -07:00
Update tzlocal==5.0.1
This commit is contained in:
parent
80e6131a0d
commit
85bc9c39ae
6 changed files with 807 additions and 772 deletions
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue