Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pendulum/tz/local_timezone.py: 18%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

150 statements  

1from __future__ import annotations 

2 

3import contextlib 

4import os 

5import re 

6import sys 

7import warnings 

8 

9from contextlib import contextmanager 

10from typing import TYPE_CHECKING 

11from typing import cast 

12 

13from pendulum.tz.exceptions import InvalidTimezone 

14from pendulum.tz.timezone import UTC 

15from pendulum.tz.timezone import FixedTimezone 

16from pendulum.tz.timezone import Timezone 

17 

18 

19if TYPE_CHECKING: 

20 from collections.abc import Iterator 

21 

22 

23if sys.platform == "win32": 

24 import winreg 

25 

26_mock_local_timezone = None 

27_local_timezone = None 

28 

29 

30def get_local_timezone() -> Timezone | FixedTimezone: 

31 global _local_timezone 

32 

33 if _mock_local_timezone is not None: 

34 return _mock_local_timezone 

35 

36 if _local_timezone is None: 

37 tz = _get_system_timezone() 

38 

39 _local_timezone = tz 

40 

41 return _local_timezone 

42 

43 

44def set_local_timezone(mock: str | Timezone | None = None) -> None: 

45 global _mock_local_timezone 

46 

47 _mock_local_timezone = mock 

48 

49 

50@contextmanager 

51def test_local_timezone(mock: Timezone) -> Iterator[None]: 

52 set_local_timezone(mock) 

53 

54 yield 

55 

56 set_local_timezone() 

57 

58 

59def _get_system_timezone() -> Timezone: 

60 if sys.platform == "win32": 

61 return _get_windows_timezone() 

62 elif "darwin" in sys.platform: 

63 return _get_darwin_timezone() 

64 

65 return _get_unix_timezone() 

66 

67 

68if sys.platform == "win32": 

69 

70 def _get_windows_timezone() -> Timezone: 

71 from pendulum.tz.data.windows import windows_timezones 

72 

73 # Windows is special. It has unique time zone names (in several 

74 # meanings of the word) available, but unfortunately, they can be 

75 # translated to the language of the operating system, so we need to 

76 # do a backwards lookup, by going through all time zones and see which 

77 # one matches. 

78 handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) 

79 

80 tz_local_key_name = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation" 

81 localtz = winreg.OpenKey(handle, tz_local_key_name) 

82 

83 timezone_info = {} 

84 size = winreg.QueryInfoKey(localtz)[1] 

85 for i in range(size): 

86 data = winreg.EnumValue(localtz, i) 

87 timezone_info[data[0]] = data[1] 

88 

89 localtz.Close() 

90 

91 if "TimeZoneKeyName" in timezone_info: 

92 # Windows 7 (and Vista?) 

93 

94 # For some reason this returns a string with loads of NUL bytes at 

95 # least on some systems. I don't know if this is a bug somewhere, I 

96 # just work around it. 

97 tzkeyname = timezone_info["TimeZoneKeyName"].split("\x00", 1)[0] 

98 else: 

99 # Windows 2000 or XP 

100 

101 # This is the localized name: 

102 tzwin = timezone_info["StandardName"] 

103 

104 # Open the list of timezones to look up the real name: 

105 tz_key_name = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones" 

106 tzkey = winreg.OpenKey(handle, tz_key_name) 

107 

108 # Now, match this value to Time Zone information 

109 tzkeyname = None 

110 for i in range(winreg.QueryInfoKey(tzkey)[0]): 

111 subkey = winreg.EnumKey(tzkey, i) 

112 sub = winreg.OpenKey(tzkey, subkey) 

113 

114 info = {} 

115 size = winreg.QueryInfoKey(sub)[1] 

116 for i in range(size): 

117 data = winreg.EnumValue(sub, i) 

118 info[data[0]] = data[1] 

119 

120 sub.Close() 

121 with contextlib.suppress(KeyError): 

122 # This timezone didn't have proper configuration. 

123 # Ignore it. 

124 if info["Std"] == tzwin: 

125 tzkeyname = subkey 

126 break 

127 

128 tzkey.Close() 

129 handle.Close() 

130 

131 if tzkeyname is None: 

132 raise LookupError("Can not find Windows timezone configuration") 

133 

134 timezone = windows_timezones.get(tzkeyname) 

135 if timezone is None: 

136 # Nope, that didn't work. Try adding "Standard Time", 

137 # it seems to work a lot of times: 

138 timezone = windows_timezones.get(tzkeyname + " Standard Time") 

139 

140 # Return what we have. 

141 if timezone is None: 

142 raise LookupError("Unable to find timezone " + tzkeyname) 

143 

144 return Timezone(timezone) 

145 

146else: 

147 

148 def _get_windows_timezone() -> Timezone: 

149 raise NotImplementedError 

150 

151 

152def _get_darwin_timezone() -> Timezone: 

153 # link will be something like /usr/share/zoneinfo/America/Los_Angeles. 

154 link = os.readlink("/etc/localtime") 

155 tzname = link[link.rfind("zoneinfo/") + 9 :] 

156 

157 return Timezone(tzname) 

158 

159 

160def _get_unix_timezone(_root: str = "/") -> Timezone: 

161 tzenv = os.environ.get("TZ") 

162 if tzenv: 

163 with contextlib.suppress(ValueError): 

164 return _tz_from_env(tzenv) 

165 

166 # Now look for distribution specific configuration files 

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(" ", "_")) 

184 

185 # CentOS has a ZONE setting in /etc/sysconfig/clock, 

186 # OpenSUSE has a TIMEZONE setting in /etc/sysconfig/clock and 

187 # Gentoo has a TIMEZONE setting in /etc/conf.d/clock 

188 # 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('"') 

192 

193 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)) 

225 

226 # systemd distributions use symlinks that include the zone name, 

227 # 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 ) 

233 tzpath_parts: list[str] = [] # type: ignore[no-redef] 

234 while parts: 

235 tzpath_parts.insert(0, parts.pop(0)) 

236 with contextlib.suppress(InvalidTimezone): 

237 return Timezone(os.path.join(*tzpath_parts)) 

238 

239 # No explicit setting existed. Use localtime 

240 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) 

248 

249 warnings.warn( 

250 "Unable not find any timezone configuration, defaulting to UTC.", stacklevel=1 

251 ) 

252 

253 return UTC 

254 

255 

256def _tz_from_env(tzenv: str) -> Timezone: 

257 if tzenv[0] == ":": 

258 tzenv = tzenv[1:] 

259 

260 # TZ specifies a file 

261 if os.path.isfile(tzenv): 

262 with open(tzenv, "rb") as f: 

263 return Timezone.from_file(f) 

264 

265 # TZ specifies a zoneinfo zone. 

266 try: 

267 return Timezone(tzenv) 

268 except ValueError: 

269 raise