|
7 | 7 | import warnings
|
8 | 8 |
|
9 | 9 | from contextlib import contextmanager
|
| 10 | +from pathlib import Path |
10 | 11 | from typing import Iterator
|
11 |
| -from typing import cast |
12 | 12 |
|
13 | 13 | from pendulum.tz.exceptions import InvalidTimezone
|
14 | 14 | from pendulum.tz.timezone import UTC
|
@@ -161,85 +161,58 @@ def _get_unix_timezone(_root: str = "/") -> Timezone:
|
161 | 161 |
|
162 | 162 | # Now look for distribution specific configuration files
|
163 | 163 | # that contain the timezone name.
|
164 |
| - tzpath = os.path.join(_root, "etc/timezone") |
165 |
| - if os.path.isfile(tzpath): |
166 |
| - with open(tzpath, "rb") as tzfile: |
167 |
| - tzfile_data = tzfile.read() |
168 |
| - |
169 |
| - # Issue #3 was that /etc/timezone was a zoneinfo file. |
170 |
| - # That's a misconfiguration, but we need to handle it gracefully: |
171 |
| - if tzfile_data[:5] != b"TZif2": |
172 |
| - etctz = tzfile_data.strip().decode() |
173 |
| - # Get rid of host definitions and comments: |
174 |
| - if " " in etctz: |
175 |
| - etctz, dummy = etctz.split(" ", 1) |
176 |
| - if "#" in etctz: |
177 |
| - etctz, dummy = etctz.split("#", 1) |
178 |
| - |
179 |
| - return Timezone(etctz.replace(" ", "_")) |
| 164 | + tzpath = Path(_root) / "etc" / "timezone" |
| 165 | + if tzpath.is_file(): |
| 166 | + tzfile_data = tzpath.read_bytes() |
| 167 | + # Issue #3 was that /etc/timezone was a zoneinfo file. |
| 168 | + # That's a misconfiguration, but we need to handle it gracefully: |
| 169 | + if not tzfile_data.startswith(b"TZif2"): |
| 170 | + etctz = tzfile_data.strip().decode() |
| 171 | + # Get rid of host definitions and comments: |
| 172 | + etctz, _, _ = etctz.partition(" ") |
| 173 | + etctz, _, _ = etctz.partition("#") |
| 174 | + return Timezone(etctz.replace(" ", "_")) |
180 | 175 |
|
181 | 176 | # CentOS has a ZONE setting in /etc/sysconfig/clock,
|
182 | 177 | # OpenSUSE has a TIMEZONE setting in /etc/sysconfig/clock and
|
183 | 178 | # Gentoo has a TIMEZONE setting in /etc/conf.d/clock
|
184 | 179 | # We look through these files for a timezone:
|
185 |
| - zone_re = re.compile(r'\s*ZONE\s*=\s*"') |
186 |
| - timezone_re = re.compile(r'\s*TIMEZONE\s*=\s*"') |
187 |
| - end_re = re.compile('"') |
| 180 | + zone_re = re.compile(r'\s*(TIME)?ZONE\s*=\s*"([^"]+)?"') |
188 | 181 |
|
189 | 182 | for filename in ("etc/sysconfig/clock", "etc/conf.d/clock"):
|
190 |
| - tzpath = os.path.join(_root, filename) |
191 |
| - if not os.path.isfile(tzpath): |
192 |
| - continue |
193 |
| - |
194 |
| - with open(tzpath) as tzfile: |
195 |
| - data = tzfile.readlines() |
196 |
| - |
197 |
| - for line in data: |
198 |
| - # Look for the ZONE= setting. |
199 |
| - match = zone_re.match(line) |
200 |
| - if match is None: |
201 |
| - # No ZONE= setting. Look for the TIMEZONE= setting. |
202 |
| - match = timezone_re.match(line) |
203 |
| - |
204 |
| - if match is not None: |
205 |
| - # Some setting existed |
206 |
| - line = line[match.end() :] |
207 |
| - etctz = line[ |
208 |
| - : cast( |
209 |
| - re.Match, end_re.search(line) # type: ignore[type-arg] |
210 |
| - ).start() |
211 |
| - ] |
212 |
| - |
213 |
| - parts = list(reversed(etctz.replace(" ", "_").split(os.path.sep))) |
214 |
| - tzpath_parts: list[str] = [] |
215 |
| - while parts: |
216 |
| - tzpath_parts.insert(0, parts.pop(0)) |
217 |
| - |
218 |
| - with contextlib.suppress(InvalidTimezone): |
219 |
| - return Timezone(os.path.join(*tzpath_parts)) |
| 183 | + tzpath = Path(_root) / filename |
| 184 | + if tzpath.is_file(): |
| 185 | + data = tzpath.read_text().splitlines() |
| 186 | + for line in data: |
| 187 | + # Look for the ZONE= or TIMEZONE= setting. |
| 188 | + match = zone_re.match(line) |
| 189 | + if match: |
| 190 | + etctz = match.group(2) |
| 191 | + parts = list(reversed(etctz.replace(" ", "_").split("/"))) |
| 192 | + tzpath_parts: list[str] = [] |
| 193 | + while parts: |
| 194 | + tzpath_parts.insert(0, parts.pop(0)) |
| 195 | + |
| 196 | + with contextlib.suppress(InvalidTimezone): |
| 197 | + return Timezone("/".join(tzpath_parts)) |
220 | 198 |
|
221 | 199 | # systemd distributions use symlinks that include the zone name,
|
222 | 200 | # see manpage of localtime(5) and timedatectl(1)
|
223 |
| - tzpath = os.path.join(_root, "etc", "localtime") |
224 |
| - if os.path.isfile(tzpath) and os.path.islink(tzpath): |
225 |
| - parts = list( |
226 |
| - reversed(os.path.realpath(tzpath).replace(" ", "_").split(os.path.sep)) |
227 |
| - ) |
| 201 | + tzpath = Path(_root) / "etc" / "localtime" |
| 202 | + if tzpath.is_file() and tzpath.is_symlink(): |
| 203 | + parts = [p.replace(" ", "_") for p in reversed(tzpath.resolve().parts)] |
228 | 204 | tzpath_parts: list[str] = [] # type: ignore[no-redef]
|
229 | 205 | while parts:
|
230 | 206 | tzpath_parts.insert(0, parts.pop(0))
|
231 | 207 | with contextlib.suppress(InvalidTimezone):
|
232 |
| - return Timezone(os.path.join(*tzpath_parts)) |
| 208 | + return Timezone("/".join(tzpath_parts)) |
233 | 209 |
|
234 | 210 | # No explicit setting existed. Use localtime
|
235 | 211 | for filename in ("etc/localtime", "usr/local/etc/localtime"):
|
236 |
| - tzpath = os.path.join(_root, filename) |
237 |
| - |
238 |
| - if not os.path.isfile(tzpath): |
239 |
| - continue |
240 |
| - |
241 |
| - with open(tzpath, "rb") as f: |
242 |
| - return Timezone.from_file(f) |
| 212 | + tzpath = Path(_root) / filename |
| 213 | + if tzpath.is_file(): |
| 214 | + with tzpath.open("rb") as f: |
| 215 | + return Timezone.from_file(f) |
243 | 216 |
|
244 | 217 | warnings.warn(
|
245 | 218 | "Unable not find any timezone configuration, defaulting to UTC.", stacklevel=1
|
|
0 commit comments