Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pendulum/tz/local_timezone.py: 30%
148 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-30 06:11 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-30 06:11 +0000
1from __future__ import annotations
3import contextlib
4import os
5import re
6import sys
7import warnings
9from contextlib import contextmanager
10from typing import Iterator
11from typing import cast
13from pendulum.tz.exceptions import InvalidTimezone
14from pendulum.tz.timezone import UTC
15from pendulum.tz.timezone import FixedTimezone
16from pendulum.tz.timezone import Timezone
19if sys.platform == "win32":
20 import winreg
22_mock_local_timezone = None
23_local_timezone = None
26def get_local_timezone() -> Timezone | FixedTimezone:
27 global _local_timezone
29 if _mock_local_timezone is not None:
30 return _mock_local_timezone
32 if _local_timezone is None:
33 tz = _get_system_timezone()
35 _local_timezone = tz
37 return _local_timezone
40def set_local_timezone(mock: str | Timezone | None = None) -> None:
41 global _mock_local_timezone
43 _mock_local_timezone = mock
46@contextmanager
47def test_local_timezone(mock: Timezone) -> Iterator[None]:
48 set_local_timezone(mock)
50 yield
52 set_local_timezone()
55def _get_system_timezone() -> Timezone:
56 if sys.platform == "win32":
57 return _get_windows_timezone()
58 elif "darwin" in sys.platform:
59 return _get_darwin_timezone()
61 return _get_unix_timezone()
64if sys.platform == "win32":
66 def _get_windows_timezone() -> Timezone:
67 from pendulum.tz.data.windows import windows_timezones
69 # Windows is special. It has unique time zone names (in several
70 # meanings of the word) available, but unfortunately, they can be
71 # translated to the language of the operating system, so we need to
72 # do a backwards lookup, by going through all time zones and see which
73 # one matches.
74 handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
76 tz_local_key_name = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
77 localtz = winreg.OpenKey(handle, tz_local_key_name)
79 timezone_info = {}
80 size = winreg.QueryInfoKey(localtz)[1]
81 for i in range(size):
82 data = winreg.EnumValue(localtz, i)
83 timezone_info[data[0]] = data[1]
85 localtz.Close()
87 if "TimeZoneKeyName" in timezone_info:
88 # Windows 7 (and Vista?)
90 # For some reason this returns a string with loads of NUL bytes at
91 # least on some systems. I don't know if this is a bug somewhere, I
92 # just work around it.
93 tzkeyname = timezone_info["TimeZoneKeyName"].split("\x00", 1)[0]
94 else:
95 # Windows 2000 or XP
97 # This is the localized name:
98 tzwin = timezone_info["StandardName"]
100 # Open the list of timezones to look up the real name:
101 tz_key_name = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
102 tzkey = winreg.OpenKey(handle, tz_key_name)
104 # Now, match this value to Time Zone information
105 tzkeyname = None
106 for i in range(winreg.QueryInfoKey(tzkey)[0]):
107 subkey = winreg.EnumKey(tzkey, i)
108 sub = winreg.OpenKey(tzkey, subkey)
110 info = {}
111 size = winreg.QueryInfoKey(sub)[1]
112 for i in range(size):
113 data = winreg.EnumValue(sub, i)
114 info[data[0]] = data[1]
116 sub.Close()
117 with contextlib.suppress(KeyError):
118 # This timezone didn't have proper configuration.
119 # Ignore it.
120 if info["Std"] == tzwin:
121 tzkeyname = subkey
122 break
124 tzkey.Close()
125 handle.Close()
127 if tzkeyname is None:
128 raise LookupError("Can not find Windows timezone configuration")
130 timezone = windows_timezones.get(tzkeyname)
131 if timezone is None:
132 # Nope, that didn't work. Try adding "Standard Time",
133 # it seems to work a lot of times:
134 timezone = windows_timezones.get(tzkeyname + " Standard Time")
136 # Return what we have.
137 if timezone is None:
138 raise LookupError("Unable to find timezone " + tzkeyname)
140 return Timezone(timezone)
142else:
144 def _get_windows_timezone() -> Timezone:
145 raise NotImplementedError
148def _get_darwin_timezone() -> Timezone:
149 # link will be something like /usr/share/zoneinfo/America/Los_Angeles.
150 link = os.readlink("/etc/localtime")
151 tzname = link[link.rfind("zoneinfo/") + 9 :]
153 return Timezone(tzname)
156def _get_unix_timezone(_root: str = "/") -> Timezone:
157 tzenv = os.environ.get("TZ")
158 if tzenv:
159 with contextlib.suppress(ValueError):
160 return _tz_from_env(tzenv)
162 # Now look for distribution specific configuration files
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()
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)
179 return Timezone(etctz.replace(" ", "_"))
181 # CentOS has a ZONE setting in /etc/sysconfig/clock,
182 # OpenSUSE has a TIMEZONE setting in /etc/sysconfig/clock and
183 # Gentoo has a TIMEZONE setting in /etc/conf.d/clock
184 # 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('"')
189 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
194 with open(tzpath) as tzfile:
195 data = tzfile.readlines()
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)
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 ]
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))
218 with contextlib.suppress(InvalidTimezone):
219 return Timezone(os.path.join(*tzpath_parts))
221 # systemd distributions use symlinks that include the zone name,
222 # 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 )
228 tzpath_parts: list[str] = [] # type: ignore[no-redef]
229 while parts:
230 tzpath_parts.insert(0, parts.pop(0))
231 with contextlib.suppress(InvalidTimezone):
232 return Timezone(os.path.join(*tzpath_parts))
234 # No explicit setting existed. Use localtime
235 for filename in ("etc/localtime", "usr/local/etc/localtime"):
236 tzpath = os.path.join(_root, filename)
238 if not os.path.isfile(tzpath):
239 continue
241 with open(tzpath, "rb") as f:
242 return Timezone.from_file(f)
244 warnings.warn(
245 "Unable not find any timezone configuration, defaulting to UTC.", stacklevel=1
246 )
248 return UTC
251def _tz_from_env(tzenv: str) -> Timezone:
252 if tzenv[0] == ":":
253 tzenv = tzenv[1:]
255 # TZ specifies a file
256 if os.path.isfile(tzenv):
257 with open(tzenv, "rb") as f:
258 return Timezone.from_file(f)
260 # TZ specifies a zoneinfo zone.
261 try:
262 return Timezone(tzenv)
263 except ValueError:
264 raise