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

Refactor: Use pathlib.Path to read unix tz #742

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
99 changes: 36 additions & 63 deletions pendulum/tz/local_timezone.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import warnings

from contextlib import contextmanager
from pathlib import Path
from typing import Iterator
from typing import cast

from pendulum.tz.exceptions import InvalidTimezone
from pendulum.tz.timezone import UTC
Expand Down Expand Up @@ -161,85 +161,58 @@ def _get_unix_timezone(_root: str = "/") -> Timezone:

# Now look for distribution specific configuration files
# that contain the timezone name.
tzpath = os.path.join(_root, "etc/timezone")
if os.path.isfile(tzpath):
with open(tzpath, "rb") as tzfile:
tzfile_data = tzfile.read()

# Issue #3 was that /etc/timezone was a zoneinfo file.
# That's a misconfiguration, but we need to handle it gracefully:
if tzfile_data[:5] != b"TZif2":
etctz = tzfile_data.strip().decode()
# Get rid of host definitions and comments:
if " " in etctz:
etctz, dummy = etctz.split(" ", 1)
if "#" in etctz:
etctz, dummy = etctz.split("#", 1)

return Timezone(etctz.replace(" ", "_"))
tzpath = Path(_root) / "etc" / "timezone"
if tzpath.is_file():
tzfile_data = tzpath.read_bytes()
# Issue #3 was that /etc/timezone was a zoneinfo file.
# That's a misconfiguration, but we need to handle it gracefully:
if not tzfile_data.startswith(b"TZif2"):
etctz = tzfile_data.strip().decode()
# Get rid of host definitions and comments:
etctz, _, _ = etctz.partition(" ")
etctz, _, _ = etctz.partition("#")
return Timezone(etctz.replace(" ", "_"))

# CentOS has a ZONE setting in /etc/sysconfig/clock,
# OpenSUSE has a TIMEZONE setting in /etc/sysconfig/clock and
# 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*(TIME)?ZONE\s*=\s*"([^"]+)?"')

for filename in ("etc/sysconfig/clock", "etc/conf.d/clock"):
tzpath = os.path.join(_root, filename)
if not os.path.isfile(tzpath):
continue

with open(tzpath) as tzfile:
data = tzfile.readlines()

for line in data:
# Look for the ZONE= setting.
match = zone_re.match(line)
if match is None:
# No ZONE= setting. Look for the TIMEZONE= setting.
match = timezone_re.match(line)

if match is not None:
# Some setting existed
line = line[match.end() :]
etctz = line[
: cast(
re.Match, end_re.search(line) # type: ignore[type-arg]
).start()
]

parts = list(reversed(etctz.replace(" ", "_").split(os.path.sep)))
tzpath_parts: list[str] = []
while parts:
tzpath_parts.insert(0, parts.pop(0))

with contextlib.suppress(InvalidTimezone):
return Timezone(os.path.join(*tzpath_parts))
tzpath = Path(_root) / filename
if tzpath.is_file():
data = tzpath.read_text().splitlines()
for line in data:
# Look for the ZONE= or TIMEZONE= setting.
match = zone_re.match(line)
if match:
etctz = match.group(2)
parts = list(reversed(etctz.replace(" ", "_").split("/")))
tzpath_parts: list[str] = []
while parts:
tzpath_parts.insert(0, parts.pop(0))

with contextlib.suppress(InvalidTimezone):
return Timezone("/".join(tzpath_parts))

# systemd distributions use symlinks that include the zone name,
# see manpage of localtime(5) and timedatectl(1)
tzpath = os.path.join(_root, "etc", "localtime")
if os.path.isfile(tzpath) and os.path.islink(tzpath):
parts = list(
reversed(os.path.realpath(tzpath).replace(" ", "_").split(os.path.sep))
)
tzpath = Path(_root) / "etc" / "localtime"
if tzpath.is_file() and tzpath.is_symlink():
parts = [p.replace(" ", "_") for p in reversed(tzpath.resolve().parts)]
tzpath_parts: list[str] = [] # type: ignore[no-redef]
while parts:
tzpath_parts.insert(0, parts.pop(0))
with contextlib.suppress(InvalidTimezone):
return Timezone(os.path.join(*tzpath_parts))
return Timezone("/".join(tzpath_parts))

# 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.isfile(tzpath):
continue

with open(tzpath, "rb") as f:
return Timezone.from_file(f)
tzpath = Path(_root) / filename
if tzpath.is_file():
with tzpath.open("rb") as f:
return Timezone.from_file(f)

warnings.warn(
"Unable not find any timezone configuration, defaulting to UTC.", stacklevel=1
Expand Down