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

1from __future__ import annotations 

2 

3import contextlib 

4import os 

5import re 

6import sys 

7import warnings 

8 

9from contextlib import contextmanager 

10from typing import Iterator 

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 sys.platform == "win32": 

20 import winreg 

21 

22_mock_local_timezone = None 

23_local_timezone = None 

24 

25 

26def get_local_timezone() -> Timezone | FixedTimezone: 

27 global _local_timezone 

28 

29 if _mock_local_timezone is not None: 

30 return _mock_local_timezone 

31 

32 if _local_timezone is None: 

33 tz = _get_system_timezone() 

34 

35 _local_timezone = tz 

36 

37 return _local_timezone 

38 

39 

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

41 global _mock_local_timezone 

42 

43 _mock_local_timezone = mock 

44 

45 

46@contextmanager 

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

48 set_local_timezone(mock) 

49 

50 yield 

51 

52 set_local_timezone() 

53 

54 

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

60 

61 return _get_unix_timezone() 

62 

63 

64if sys.platform == "win32": 

65 

66 def _get_windows_timezone() -> Timezone: 

67 from pendulum.tz.data.windows import windows_timezones 

68 

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) 

75 

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

77 localtz = winreg.OpenKey(handle, tz_local_key_name) 

78 

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] 

84 

85 localtz.Close() 

86 

87 if "TimeZoneKeyName" in timezone_info: 

88 # Windows 7 (and Vista?) 

89 

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 

96 

97 # This is the localized name: 

98 tzwin = timezone_info["StandardName"] 

99 

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) 

103 

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) 

109 

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] 

115 

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 

123 

124 tzkey.Close() 

125 handle.Close() 

126 

127 if tzkeyname is None: 

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

129 

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

135 

136 # Return what we have. 

137 if timezone is None: 

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

139 

140 return Timezone(timezone) 

141 

142else: 

143 

144 def _get_windows_timezone() -> Timezone: 

145 raise NotImplementedError 

146 

147 

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 :] 

152 

153 return Timezone(tzname) 

154 

155 

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) 

161 

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

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

180 

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

188 

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 

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.match(line) 

200 if match is None: 

201 # No ZONE= setting. Look for the TIMEZONE= setting. 

202 match = timezone_re.match(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)) 

220 

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

233 

234 # No explicit setting existed. Use localtime 

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

243 

244 warnings.warn( 

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

246 ) 

247 

248 return UTC 

249 

250 

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

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

253 tzenv = tzenv[1:] 

254 

255 # TZ specifies a file 

256 if os.path.isfile(tzenv): 

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

258 return Timezone.from_file(f) 

259 

260 # TZ specifies a zoneinfo zone. 

261 try: 

262 return Timezone(tzenv) 

263 except ValueError: 

264 raise