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

1import os 

2import re 

3import sys 

4 

5from contextlib import contextmanager 

6from typing import Iterator 

7from typing import Optional 

8from typing import Union 

9 

10from .timezone import Timezone 

11from .timezone import TimezoneFile 

12from .zoneinfo.exceptions import InvalidTimezone 

13 

14 

15try: 

16 import _winreg as winreg 

17except ImportError: 

18 try: 

19 import winreg 

20 except ImportError: 

21 winreg = None 

22 

23 

24_mock_local_timezone = None 

25_local_timezone = None 

26 

27 

28def get_local_timezone(): # type: () -> Timezone 

29 global _local_timezone 

30 

31 if _mock_local_timezone is not None: 

32 return _mock_local_timezone 

33 

34 if _local_timezone is None: 

35 tz = _get_system_timezone() 

36 

37 _local_timezone = tz 

38 

39 return _local_timezone 

40 

41 

42def set_local_timezone(mock=None): # type: (Optional[Union[str, Timezone]]) -> None 

43 global _mock_local_timezone 

44 

45 _mock_local_timezone = mock 

46 

47 

48@contextmanager 

49def test_local_timezone(mock): # type: (Timezone) -> Iterator[None] 

50 set_local_timezone(mock) 

51 

52 yield 

53 

54 set_local_timezone() 

55 

56 

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

62 

63 return _get_unix_timezone() 

64 

65 

66def _get_windows_timezone(): # type: () -> Timezone 

67 from .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 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 

125 

126 tzkey.Close() 

127 handle.Close() 

128 

129 if tzkeyname is None: 

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

131 

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

137 

138 # Return what we have. 

139 if timezone is None: 

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

141 

142 return Timezone(timezone) 

143 

144 

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

149 

150 return Timezone(tzname) 

151 

152 

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 

160 

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

167 

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) 

177 

178 return Timezone(etctz.replace(" ", "_")) 

179 

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

187 

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 

192 

193 with open(tzpath, "rt") as tzfile: 

194 data = tzfile.readlines() 

195 

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) 

202 

203 if match is not None: 

204 # Some setting existed 

205 line = line[match.end() :] 

206 etctz = line[: end_re.search(line).start()] 

207 

208 parts = list(reversed(etctz.replace(" ", "_").split(os.path.sep))) 

209 tzpath = [] 

210 while parts: 

211 tzpath.insert(0, parts.pop(0)) 

212 

213 try: 

214 return Timezone(os.path.join(*tzpath)) 

215 except InvalidTimezone: 

216 pass 

217 

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 

232 

233 # No explicit setting existed. Use localtime 

234 for filename in ("etc/localtime", "usr/local/etc/localtime"): 

235 tzpath = os.path.join(_root, filename) 

236 

237 if not os.path.exists(tzpath): 

238 continue 

239 

240 return TimezoneFile(tzpath) 

241 

242 raise RuntimeError("Unable to find any timezone configuration") 

243 

244 

245def _tz_from_env(tzenv): # type: (str) -> Timezone 

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

247 tzenv = tzenv[1:] 

248 

249 # TZ specifies a file 

250 if os.path.exists(tzenv): 

251 return TimezoneFile(tzenv) 

252 

253 # TZ specifies a zoneinfo zone. 

254 try: 

255 return Timezone(tzenv) 

256 except ValueError: 

257 raise