|
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