1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

# This code was originally contributed by Jeffrey Harris. 

import datetime 

import struct 

 

from six.moves import winreg 

from six import text_type 

 

try: 

import ctypes 

from ctypes import wintypes 

except ValueError: 

# ValueError is raised on non-Windows systems for some horrible reason. 

raise ImportError("Running tzwin on non-Windows system") 

 

from ._common import tzrangebase 

 

__all__ = ["tzwin", "tzwinlocal", "tzres"] 

 

ONEWEEK = datetime.timedelta(7) 

 

TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones" 

TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones" 

TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation" 

 

 

def _settzkeyname(): 

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

try: 

winreg.OpenKey(handle, TZKEYNAMENT).Close() 

TZKEYNAME = TZKEYNAMENT 

except WindowsError: 

TZKEYNAME = TZKEYNAME9X 

handle.Close() 

return TZKEYNAME 

 

 

TZKEYNAME = _settzkeyname() 

 

 

class tzres(object): 

""" 

Class for accessing `tzres.dll`, which contains timezone name related 

resources. 

 

.. versionadded:: 2.5.0 

""" 

p_wchar = ctypes.POINTER(wintypes.WCHAR) # Pointer to a wide char 

 

def __init__(self, tzres_loc='tzres.dll'): 

# Load the user32 DLL so we can load strings from tzres 

user32 = ctypes.WinDLL('user32') 

 

# Specify the LoadStringW function 

user32.LoadStringW.argtypes = (wintypes.HINSTANCE, 

wintypes.UINT, 

wintypes.LPWSTR, 

ctypes.c_int) 

 

self.LoadStringW = user32.LoadStringW 

self._tzres = ctypes.WinDLL(tzres_loc) 

self.tzres_loc = tzres_loc 

 

def load_name(self, offset): 

""" 

Load a timezone name from a DLL offset (integer). 

 

>>> from dateutil.tzwin import tzres 

>>> tzr = tzres() 

>>> print(tzr.load_name(112)) 

'Eastern Standard Time' 

 

:param offset: 

A positive integer value referring to a string from the tzres dll. 

 

..note: 

Offsets found in the registry are generally of the form 

`@tzres.dll,-114`. The offset in this case if 114, not -114. 

 

""" 

resource = self.p_wchar() 

lpBuffer = ctypes.cast(ctypes.byref(resource), wintypes.LPWSTR) 

nchar = self.LoadStringW(self._tzres._handle, offset, lpBuffer, 0) 

return resource[:nchar] 

 

def name_from_string(self, tzname_str): 

""" 

Parse strings as returned from the Windows registry into the time zone 

name as defined in the registry. 

 

>>> from dateutil.tzwin import tzres 

>>> tzr = tzres() 

>>> print(tzr.name_from_string('@tzres.dll,-251')) 

'Dateline Daylight Time' 

>>> print(tzr.name_from_string('Eastern Standard Time')) 

'Eastern Standard Time' 

 

:param tzname_str: 

A timezone name string as returned from a Windows registry key. 

 

:return: 

Returns the localized timezone string from tzres.dll if the string 

is of the form `@tzres.dll,-offset`, else returns the input string. 

""" 

if not tzname_str.startswith('@'): 

return tzname_str 

 

name_splt = tzname_str.split(',-') 

try: 

offset = int(name_splt[1]) 

except: 

raise ValueError("Malformed timezone string.") 

 

return self.load_name(offset) 

 

 

class tzwinbase(tzrangebase): 

"""tzinfo class based on win32's timezones available in the registry.""" 

def __init__(self): 

raise NotImplementedError('tzwinbase is an abstract base class') 

 

def __eq__(self, other): 

# Compare on all relevant dimensions, including name. 

if not isinstance(other, tzwinbase): 

return NotImplemented 

 

return (self._std_offset == other._std_offset and 

self._dst_offset == other._dst_offset and 

self._stddayofweek == other._stddayofweek and 

self._dstdayofweek == other._dstdayofweek and 

self._stdweeknumber == other._stdweeknumber and 

self._dstweeknumber == other._dstweeknumber and 

self._stdhour == other._stdhour and 

self._dsthour == other._dsthour and 

self._stdminute == other._stdminute and 

self._dstminute == other._dstminute and 

self._std_abbr == other._std_abbr and 

self._dst_abbr == other._dst_abbr) 

 

@staticmethod 

def list(): 

"""Return a list of all time zones known to the system.""" 

with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: 

with winreg.OpenKey(handle, TZKEYNAME) as tzkey: 

result = [winreg.EnumKey(tzkey, i) 

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

return result 

 

def display(self): 

return self._display 

 

def transitions(self, year): 

""" 

For a given year, get the DST on and off transition times, expressed 

always on the standard time side. For zones with no transitions, this 

function returns ``None``. 

 

:param year: 

The year whose transitions you would like to query. 

 

:return: 

Returns a :class:`tuple` of :class:`datetime.datetime` objects, 

``(dston, dstoff)`` for zones with an annual DST transition, or 

``None`` for fixed offset zones. 

""" 

 

if not self.hasdst: 

return None 

 

dston = picknthweekday(year, self._dstmonth, self._dstdayofweek, 

self._dsthour, self._dstminute, 

self._dstweeknumber) 

 

dstoff = picknthweekday(year, self._stdmonth, self._stddayofweek, 

self._stdhour, self._stdminute, 

self._stdweeknumber) 

 

# Ambiguous dates default to the STD side 

dstoff -= self._dst_base_offset 

 

return dston, dstoff 

 

def _get_hasdst(self): 

return self._dstmonth != 0 

 

@property 

def _dst_base_offset(self): 

return self._dst_base_offset_ 

 

 

class tzwin(tzwinbase): 

 

def __init__(self, name): 

self._name = name 

 

with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: 

tzkeyname = text_type("{kn}\\{name}").format(kn=TZKEYNAME, name=name) 

with winreg.OpenKey(handle, tzkeyname) as tzkey: 

keydict = valuestodict(tzkey) 

 

self._std_abbr = keydict["Std"] 

self._dst_abbr = keydict["Dlt"] 

 

self._display = keydict["Display"] 

 

# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm 

tup = struct.unpack("=3l16h", keydict["TZI"]) 

stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1 

dstoffset = stdoffset-tup[2] # + DaylightBias * -1 

self._std_offset = datetime.timedelta(minutes=stdoffset) 

self._dst_offset = datetime.timedelta(minutes=dstoffset) 

 

# for the meaning see the win32 TIME_ZONE_INFORMATION structure docs 

# http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx 

(self._stdmonth, 

self._stddayofweek, # Sunday = 0 

self._stdweeknumber, # Last = 5 

self._stdhour, 

self._stdminute) = tup[4:9] 

 

(self._dstmonth, 

self._dstdayofweek, # Sunday = 0 

self._dstweeknumber, # Last = 5 

self._dsthour, 

self._dstminute) = tup[12:17] 

 

self._dst_base_offset_ = self._dst_offset - self._std_offset 

self.hasdst = self._get_hasdst() 

 

def __repr__(self): 

return "tzwin(%s)" % repr(self._name) 

 

def __reduce__(self): 

return (self.__class__, (self._name,)) 

 

 

class tzwinlocal(tzwinbase): 

def __init__(self): 

with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: 

with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey: 

keydict = valuestodict(tzlocalkey) 

 

self._std_abbr = keydict["StandardName"] 

self._dst_abbr = keydict["DaylightName"] 

 

try: 

tzkeyname = text_type('{kn}\\{sn}').format(kn=TZKEYNAME, 

sn=self._std_abbr) 

with winreg.OpenKey(handle, tzkeyname) as tzkey: 

_keydict = valuestodict(tzkey) 

self._display = _keydict["Display"] 

except OSError: 

self._display = None 

 

stdoffset = -keydict["Bias"]-keydict["StandardBias"] 

dstoffset = stdoffset-keydict["DaylightBias"] 

 

self._std_offset = datetime.timedelta(minutes=stdoffset) 

self._dst_offset = datetime.timedelta(minutes=dstoffset) 

 

# For reasons unclear, in this particular key, the day of week has been 

# moved to the END of the SYSTEMTIME structure. 

tup = struct.unpack("=8h", keydict["StandardStart"]) 

 

(self._stdmonth, 

self._stdweeknumber, # Last = 5 

self._stdhour, 

self._stdminute) = tup[1:5] 

 

self._stddayofweek = tup[7] 

 

tup = struct.unpack("=8h", keydict["DaylightStart"]) 

 

(self._dstmonth, 

self._dstweeknumber, # Last = 5 

self._dsthour, 

self._dstminute) = tup[1:5] 

 

self._dstdayofweek = tup[7] 

 

self._dst_base_offset_ = self._dst_offset - self._std_offset 

self.hasdst = self._get_hasdst() 

 

def __repr__(self): 

return "tzwinlocal()" 

 

def __str__(self): 

# str will return the standard name, not the daylight name. 

return "tzwinlocal(%s)" % repr(self._std_abbr) 

 

def __reduce__(self): 

return (self.__class__, ()) 

 

 

def picknthweekday(year, month, dayofweek, hour, minute, whichweek): 

""" dayofweek == 0 means Sunday, whichweek 5 means last instance """ 

first = datetime.datetime(year, month, 1, hour, minute) 

 

# This will work if dayofweek is ISO weekday (1-7) or Microsoft-style (0-6), 

# Because 7 % 7 = 0 

weekdayone = first.replace(day=((dayofweek - first.isoweekday()) % 7) + 1) 

wd = weekdayone + ((whichweek - 1) * ONEWEEK) 

if (wd.month != month): 

wd -= ONEWEEK 

 

return wd 

 

 

def valuestodict(key): 

"""Convert a registry key's values to a dictionary.""" 

dout = {} 

size = winreg.QueryInfoKey(key)[1] 

tz_res = None 

 

for i in range(size): 

key_name, value, dtype = winreg.EnumValue(key, i) 

if dtype == winreg.REG_DWORD or dtype == winreg.REG_DWORD_LITTLE_ENDIAN: 

# If it's a DWORD (32-bit integer), it's stored as unsigned - convert 

# that to a proper signed integer 

if value & (1 << 31): 

value = value - (1 << 32) 

elif dtype == winreg.REG_SZ: 

# If it's a reference to the tzres DLL, load the actual string 

if value.startswith('@tzres'): 

tz_res = tz_res or tzres() 

value = tz_res.name_from_string(value) 

 

value = value.rstrip('\x00') # Remove trailing nulls 

 

dout[key_name] = value 

 

return dout