diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/tzlocal/win32.py')
-rw-r--r-- | .venv/lib/python3.12/site-packages/tzlocal/win32.py | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/tzlocal/win32.py b/.venv/lib/python3.12/site-packages/tzlocal/win32.py new file mode 100644 index 00000000..cfcdba53 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/tzlocal/win32.py @@ -0,0 +1,144 @@ +import logging +from datetime import datetime + +try: + import _winreg as winreg +except ImportError: + import winreg + +import zoneinfo + +from tzlocal import utils +from tzlocal.windows_tz import win_tz + +_cache_tz = None +_cache_tz_name = None + +log = logging.getLogger("tzlocal") + + +def valuestodict(key): + """Convert a registry key's values to a dictionary.""" + result = {} + size = winreg.QueryInfoKey(key)[1] + for i in range(size): + data = winreg.EnumValue(key, i) + result[data[0]] = data[1] + return result + + +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 + + log.debug("Looking up time zone info from registry") + handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) + + TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation" + localtz = winreg.OpenKey(handle, TZLOCALKEYNAME) + keyvalues = valuestodict(localtz) + localtz.Close() + + 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] + else: + # Don't support XP any longer + raise LookupError("Can not find Windows timezone configuration") + + timezone = win_tz.get(tzkeyname) + if timezone is None: + # Nope, that didn't work. Try adding "Standard Time", + # it seems to work a lot of times: + timezone = win_tz.get(tzkeyname + " Standard Time") + + # Return what we have. + if timezone is None: + 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 = 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, + # but this timezone doesn't have DST so it doesn't matter + return timezone + + if std_offset is None: + 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 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}" + + return timezone + + +def get_localzone_name() -> str: + """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() -> zoneinfo.ZoneInfo: + """Returns the zoneinfo-based tzinfo object that matches the Windows-configured timezone.""" + + global _cache_tz + if _cache_tz is None: + _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, error=False) + + return _cache_tz + + +def reload_localzone() -> zoneinfo.ZoneInfo: + """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 = zoneinfo.ZoneInfo(_cache_tz_name) + utils.assert_tz_offset(_cache_tz, error=False) + return _cache_tz |