Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pendulum/tz/local_timezone.py: 18%
152 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
1import os
2import re
3import sys
5from contextlib import contextmanager
6from typing import Iterator
7from typing import Optional
8from typing import Union
10from .timezone import Timezone
11from .timezone import TimezoneFile
12from .zoneinfo.exceptions import InvalidTimezone
15try:
16 import _winreg as winreg
17except ImportError:
18 try:
19 import winreg
20 except ImportError:
21 winreg = None
24_mock_local_timezone = None
25_local_timezone = None
28def get_local_timezone(): # type: () -> Timezone
29 global _local_timezone
31 if _mock_local_timezone is not None:
32 return _mock_local_timezone
34 if _local_timezone is None:
35 tz = _get_system_timezone()
37 _local_timezone = tz
39 return _local_timezone
42def set_local_timezone(mock=None): # type: (Optional[Union[str, Timezone]]) -> None
43 global _mock_local_timezone
45 _mock_local_timezone = mock
48@contextmanager
49def test_local_timezone(mock): # type: (Timezone) -> Iterator[None]
50 set_local_timezone(mock)
52 yield
54 set_local_timezone()
57def _get_system_timezone(): # type: () -> Timezone
58 if sys.platform == "win32":
59 return _get_windows_timezone()
60 elif "darwin" in sys.platform:
61 return _get_darwin_timezone()
63 return _get_unix_timezone()
66def _get_windows_timezone(): # type: () -> Timezone
67 from .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 try:
118 if info["Std"] == tzwin:
119 tzkeyname = subkey
120 break
121 except KeyError:
122 # This timezone didn't have proper configuration.
123 # Ignore it.
124 pass
126 tzkey.Close()
127 handle.Close()
129 if tzkeyname is None:
130 raise LookupError("Can not find Windows timezone configuration")
132 timezone = windows_timezones.get(tzkeyname)
133 if timezone is None:
134 # Nope, that didn't work. Try adding "Standard Time",
135 # it seems to work a lot of times:
136 timezone = windows_timezones.get(tzkeyname + " Standard Time")
138 # Return what we have.
139 if timezone is None:
140 raise LookupError("Unable to find timezone " + tzkeyname)
142 return Timezone(timezone)
145def _get_darwin_timezone(): # type: () -> Timezone
146 # link will be something like /usr/share/zoneinfo/America/Los_Angeles.
147 link = os.readlink("/etc/localtime")
148 tzname = link[link.rfind("zoneinfo/") + 9 :]
150 return Timezone(tzname)
153def _get_unix_timezone(_root="/"): # type: (str) -> Timezone
154 tzenv = os.environ.get("TZ")
155 if tzenv:
156 try:
157 return _tz_from_env(tzenv)
158 except ValueError:
159 pass
161 # Now look for distribution specific configuration files
162 # that contain the timezone name.
163 tzpath = os.path.join(_root, "etc/timezone")
164 if os.path.exists(tzpath):
165 with open(tzpath, "rb") as tzfile:
166 data = tzfile.read()
168 # Issue #3 was that /etc/timezone was a zoneinfo file.
169 # That's a misconfiguration, but we need to handle it gracefully:
170 if data[:5] != "TZif2":
171 etctz = data.strip().decode()
172 # Get rid of host definitions and comments:
173 if " " in etctz:
174 etctz, dummy = etctz.split(" ", 1)
175 if "#" in etctz:
176 etctz, dummy = etctz.split("#", 1)
178 return Timezone(etctz.replace(" ", "_"))
180 # CentOS has a ZONE setting in /etc/sysconfig/clock,
181 # OpenSUSE has a TIMEZONE setting in /etc/sysconfig/clock and
182 # Gentoo has a TIMEZONE setting in /etc/conf.d/clock
183 # We look through these files for a timezone:
184 zone_re = re.compile(r'\s*ZONE\s*=\s*"')
185 timezone_re = re.compile(r'\s*TIMEZONE\s*=\s*"')
186 end_re = re.compile('"')
188 for filename in ("etc/sysconfig/clock", "etc/conf.d/clock"):
189 tzpath = os.path.join(_root, filename)
190 if not os.path.exists(tzpath):
191 continue
193 with open(tzpath, "rt") as tzfile:
194 data = tzfile.readlines()
196 for line in data:
197 # Look for the ZONE= setting.
198 match = zone_re.match(line)
199 if match is None:
200 # No ZONE= setting. Look for the TIMEZONE= setting.
201 match = timezone_re.match(line)
203 if match is not None:
204 # Some setting existed
205 line = line[match.end() :]
206 etctz = line[: end_re.search(line).start()]
208 parts = list(reversed(etctz.replace(" ", "_").split(os.path.sep)))
209 tzpath = []
210 while parts:
211 tzpath.insert(0, parts.pop(0))
213 try:
214 return Timezone(os.path.join(*tzpath))
215 except InvalidTimezone:
216 pass
218 # systemd distributions use symlinks that include the zone name,
219 # see manpage of localtime(5) and timedatectl(1)
220 tzpath = os.path.join(_root, "etc", "localtime")
221 if os.path.exists(tzpath) and os.path.islink(tzpath):
222 parts = list(
223 reversed(os.path.realpath(tzpath).replace(" ", "_").split(os.path.sep))
224 )
225 tzpath = []
226 while parts:
227 tzpath.insert(0, parts.pop(0))
228 try:
229 return Timezone(os.path.join(*tzpath))
230 except InvalidTimezone:
231 pass
233 # No explicit setting existed. Use localtime
234 for filename in ("etc/localtime", "usr/local/etc/localtime"):
235 tzpath = os.path.join(_root, filename)
237 if not os.path.exists(tzpath):
238 continue
240 return TimezoneFile(tzpath)
242 raise RuntimeError("Unable to find any timezone configuration")
245def _tz_from_env(tzenv): # type: (str) -> Timezone
246 if tzenv[0] == ":":
247 tzenv = tzenv[1:]
249 # TZ specifies a file
250 if os.path.exists(tzenv):
251 return TimezoneFile(tzenv)
253 # TZ specifies a zoneinfo zone.
254 try:
255 return Timezone(tzenv)
256 except ValueError:
257 raise