Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[script.module.tzlocal@matrix] 5.0.1 #2508

Merged
merged 1 commit into from
Sep 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions script.module.tzlocal/README.md

This file was deleted.

18 changes: 7 additions & 11 deletions script.module.tzlocal/addon.xml
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="script.module.tzlocal"
name="tzlocal"
version="3.0.0+matrix.2"
provider-name="Lennart Regebro">
<addon id="script.module.tzlocal" name="tzlocal" version="5.0.1" provider-name="Lennart Regebro">
<requires>
<import addon="xbmc.python" version="3.0.0"/>
<import addon="script.module.pytz" version="2019.3.0+matrix.1"/>
<import addon="script.module.backports.zoneinfo" version="0.2.1"/>
<import addon="xbmc.python" version="3.0.0" />
<import addon="script.module.tzdata" version="2023.3.0+matrix.1" />
<import addon="script.module.backports.zoneinfo" version="0.2.1" />
</requires>
<extension point="xbmc.python.module"
library="lib"/>
<extension point="xbmc.python.module" library="lib" />
<extension point="xbmc.addon.metadata">
<summary lang="en_GB">Python Module to find out the current time zone</summary>
<description lang="en_GB">Packed for Kodi from https://github.com/regebro/tzlocal, forked from https://github.com/jdollarKodi/script.module.tzlocal</description>
<summary lang="en_GB">tzinfo object for the local timezone</summary>
<description lang="en_GB">tzinfo object for the local timezone</description>
<platform>all</platform>
<license>MIT</license>
<website>https://github.com/regebro/tzlocal</website>
Expand Down
21 changes: 18 additions & 3 deletions script.module.tzlocal/lib/tzlocal/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
import sys
if sys.platform == 'win32':
from tzlocal.win32 import get_localzone, reload_localzone

if sys.platform == "win32":
from tzlocal.win32 import (
get_localzone,
get_localzone_name,
reload_localzone,
) # pragma: no cover
else:
from tzlocal.unix import get_localzone, reload_localzone
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",
"assert_tz_offset",
]
232 changes: 141 additions & 91 deletions script.module.tzlocal/lib/tzlocal/unix.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import logging
import os
import re
import sys
Expand All @@ -7,96 +8,76 @@
from tzlocal import utils

if sys.version_info >= (3, 9):
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
import zoneinfo # pragma: no cover
else:
from backports.zoneinfo import ZoneInfo, ZoneInfoNotFoundError
from backports import zoneinfo # pragma: no cover

_cache_tz = None
_cache_tz_name = None

log = logging.getLogger("tzlocal")

def _tz_from_env(tzenv):
if tzenv[0] == ':':
tzenv = tzenv[1:]

# TZ specifies a file
if os.path.isabs(tzenv) and os.path.exists(tzenv):
with open(tzenv, 'rb') as tzfile:
return ZoneInfo.from_file(tzfile, key='local')

# TZ specifies a zoneinfo zone.
try:
tz = ZoneInfo(tzenv)
# That worked, so we return this:
return tz
except ZoneInfoNotFoundError:
raise ZoneInfoNotFoundError(
"tzlocal() does not support non-zoneinfo timezones like %s. \n"
"Please use a timezone in the form of Continent/City") from None


def _try_tz_from_env():
tzenv = os.environ.get('TZ')
if tzenv:
try:
return _tz_from_env(tzenv)
except ZoneInfoNotFoundError:
pass


def _get_localzone(_root='/'):
def _get_localzone_name(_root="/"):
"""Tries to find the local timezone configuration.

This method prefers finding the timezone name and passing that to pytz,
over passing in the localtime file, as in the later case the zoneinfo
name is unknown.
This method finds the timezone name, if it can, or it returns None.

The parameter _root makes the function look for files like /etc/localtime
beneath the _root directory. This is primarily used by the tests.
In normal usage you call the function without parameters."""

tzenv = _try_tz_from_env()
# First try the ENV setting.
tzenv = utils._tz_name_from_env()
if tzenv:
return tzenv

# Are we under Termux on Android?
if os.path.exists('/system/bin/getprop'):
if os.path.exists(os.path.join(_root, "system/bin/getprop")):
log.debug("This looks like Termux")

import subprocess
androidtz = subprocess.check_output(['getprop', 'persist.sys.timezone']).strip().decode()
return ZoneInfo(androidtz)

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.
for configfile in ('etc/timezone', 'var/db/zoneinfo'):

# Stick all of them in a dict, to compare later.
found_configs = {}

for configfile in ("etc/timezone", "var/db/zoneinfo"):
tzpath = os.path.join(_root, configfile)
try:
with open(tzpath, 'rb') as tzfile:
with open(tzpath) as tzfile:
data = tzfile.read()
log.debug(f"{tzpath} found, contents:\n {data}")

# Issue #3 was that /etc/timezone was a zoneinfo file.
# That's a misconfiguration, but we need to handle it gracefully:
if data[:5] == b'TZif2':
continue

etctz = data.strip().decode()
etctz = data.strip("/ \t\r\n")
if not etctz:
# Empty file, skip
continue
for etctz in data.decode().splitlines():
for etctz in etctz.splitlines():
# Get rid of host definitions and comments:
if ' ' in etctz:
etctz, dummy = etctz.split(' ', 1)
if '#' in etctz:
etctz, dummy = etctz.split('#', 1)
if " " in etctz:
etctz, dummy = etctz.split(" ", 1)
if "#" in etctz:
etctz, dummy = etctz.split("#", 1)
if not etctz:
continue
tz = ZoneInfo(etctz.replace(' ', '_'))
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)
return tz

except (IOError, UnicodeDecodeError):

found_configs[tzpath] = etctz.replace(" ", "_")

except (OSError, UnicodeDecodeError):
# File doesn't exist or is a directory, or it's a binary file.
continue

Expand All @@ -105,15 +86,16 @@ def _get_localzone(_root='/'):
# Gentoo has a TIMEZONE setting in /etc/conf.d/clock
# We look through these files for a timezone:

zone_re = re.compile(r'\s*ZONE\s*=\s*\"')
timezone_re = re.compile(r'\s*TIMEZONE\s*=\s*\"')
end_re = re.compile('\"')
zone_re = re.compile(r"\s*ZONE\s*=\s*\"")
timezone_re = re.compile(r"\s*TIMEZONE\s*=\s*\"")
end_re = re.compile('"')

for filename in ('etc/sysconfig/clock', 'etc/conf.d/clock'):
for filename in ("etc/sysconfig/clock", "etc/conf.d/clock"):
tzpath = os.path.join(_root, filename)
try:
with open(tzpath, 'rt') as tzfile:
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.
Expand All @@ -123,49 +105,114 @@ def _get_localzone(_root='/'):
match = timezone_re.match(line)
if match is not None:
# Some setting existed
line = line[match.end():]
etctz = line[:end_re.search(line).start()]
line = line[match.end() :]
etctz = line[: end_re.search(line).start()]

# We found a timezone
tz = ZoneInfo(etctz.replace(' ', '_'))
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)
return tz

except (IOError, UnicodeDecodeError) as e:
found_configs[tzpath] = etctz.replace(" ", "_")

except (OSError, UnicodeDecodeError):
# UnicodeDecode handles when clock is symlink to /etc/localtime
continue

# systemd distributions use symlinks that include the zone name,
# see manpage of localtime(5) and timedatectl(1)
tzpath = os.path.join(_root, 'etc/localtime')
tzpath = os.path.join(_root, "etc/localtime")
if os.path.exists(tzpath) and os.path.islink(tzpath):
tzpath = os.path.realpath(tzpath)
start = tzpath.find("/")+1
log.debug(f"{tzpath} found")
etctz = os.path.realpath(tzpath)
start = etctz.find("/") + 1
while start != 0:
tzpath = tzpath[start:]
etctz = etctz[start:]
try:
return ZoneInfo(tzpath)
except ZoneInfoNotFoundError:
zoneinfo.ZoneInfo(etctz)
tzinfo = f"{tzpath} is a symlink to"
found_configs[tzinfo] = etctz.replace(" ", "_")
# Only need first valid relative path in simlink.
break
except zoneinfo.ZoneInfoNotFoundError:
pass
start = tzpath.find("/")+1
start = etctz.find("/") + 1

# No explicit setting existed. Use localtime
for filename in ('etc/localtime', 'usr/local/etc/localtime'):
tzpath = os.path.join(_root, filename)
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()
zoneinfopath = os.path.join(_root, "usr", "share", "zoneinfo")
directory_depth = len(zoneinfopath.split(os.path.sep))

if not os.path.exists(tzpath):
continue
with open(tzpath, 'rb') as tzfile:
return ZoneInfo.from_file(tzfile, key='local')
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(zoneinfopath, *tzname.split("/")))
real_zone_name = "/".join(path.split(os.path.sep)[directory_depth:])
unique_tzs.add(real_zone_name)

if len(unique_tzs) != 1:
message = "Multiple conflicting time zone configurations found:\n"
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 zoneinfo.ZoneInfoNotFoundError(message)

# We found exactly one config! Use it.
return list(found_configs.values())[0]


def _get_localzone(_root="/"):
"""Creates a timezone object from the timezone name.

If there is no timezone config, it will try to create a file from the
localtime timezone, and if there isn't one, it will default to UTC.

The parameter _root makes the function look for files like /etc/localtime
beneath the _root directory. This is primarily used by the tests.
In normal usage you call the function without parameters."""

# First try the ENV setting.
tzenv = utils._tz_from_env()
if tzenv:
return tzenv

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 = 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 = 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, error=False)
return tz


def get_localzone_name():
"""Get the computers configured local timezone name, if any."""
global _cache_tz_name
if _cache_tz_name is None:
_cache_tz_name = _get_localzone_name()

return _cache_tz_name

warnings.warn('Can not find any timezone configuration, defaulting to UTC.')
return timezone.utc

def get_localzone():
"""Get the computers configured local timezone, if any."""

global _cache_tz
if _cache_tz is None:
_cache_tz = _get_localzone()
Expand All @@ -175,6 +222,9 @@ def get_localzone():

def reload_localzone():
"""Reload the cached localzone. You need to call this if the timezone has changed."""
global _cache_tz_name
global _cache_tz
_cache_tz_name = _get_localzone_name()
_cache_tz = _get_localzone()

return _cache_tz
Loading
Loading