import calendar import datetime import logging import os import time import warnings try: import zoneinfo # pragma: no cover except ImportError: from backports import zoneinfo # pragma: no cover from tzlocal import windows_tz log = logging.getLogger("tzlocal") def get_tz_offset(tz): """Get timezone's offset using built-in function datetime.utcoffset().""" return int(datetime.datetime.now(tz).utcoffset().total_seconds()) 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. If error is True, this method will raise a ValueError, otherwise it will emit a warning. """ tz_offset = get_tz_offset(tz) 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 = ( f"Timezone offset does not match system offset: {tz_offset} != {system_offset}. " "Please, check your config files." ) if error: raise ValueError(msg) warnings.warn(msg) def _tz_name_from_env(tzenv=None): if tzenv is None: tzenv = os.environ.get("TZ") if not tzenv: return None log.debug(f"Found a TZ environment: {tzenv}") if tzenv[0] == ":": tzenv = tzenv[1:] 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, expand it, if possible parts = os.path.realpath(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] log.debug("TZ does not contain a time zone name") return None 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: return zoneinfo.ZoneInfo.from_file(tzfile, key=tzname) # TZ must specify a zoneinfo zone. try: tz = zoneinfo.ZoneInfo(tzenv) # That worked, so we return this: return tz except zoneinfo.ZoneInfoNotFoundError: # Nope, it's something like "PST4DST" etc, we can't handle that. raise zoneinfo.ZoneInfoNotFoundError( f"tzlocal() does not support non-zoneinfo timezones like {tzenv}. \n" "Please use a timezone in the form of Continent/City" ) from None