import logging from datetime import datetime 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 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