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