You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bazarr/libs/tzlocal/utils.py

129 lines
4.0 KiB

# -*- coding: utf-8 -*-
import os
import time
import datetime
import calendar
import pytz_deprecation_shim as pds
try:
import zoneinfo # pragma: no cover
except ImportError:
from backports import zoneinfo # pragma: no cover
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
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):
"""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."""
tz_offset = get_tz_offset(tz)
system_offset = get_system_offset()
if tz_offset != system_offset:
msg = (
"Timezone offset does not match system offset: {} != {}. "
"Please, check your config files."
).format(tz_offset, system_offset)
raise ValueError(msg)
def _tz_name_from_env(tzenv=None):
if tzenv is None:
tzenv = os.environ.get("TZ")
if not tzenv:
return None
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]
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:
zone = zoneinfo.ZoneInfo.from_file(tzfile, key=tzname)
return pds.wrap_zone(zone)
# TZ must specify a zoneinfo zone.
try:
tz = pds.timezone(tzenv)
# That worked, so we return this:
return tz
except pds.UnknownTimeZoneError:
# Nope, it's something like "PST4DST" etc, we can't handle that.
raise ZoneInfoNotFoundError(
"tzlocal() does not support non-zoneinfo timezones like %s. \n"
"Please use a timezone in the form of Continent/City"
) from None