|
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.fullmatch(line) |
200 | | - if match is None: |
201 | | - # No ZONE= setting. Look for the TIMEZONE= setting. |
202 | | - match = timezone_re.fullmatch(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