1# This code was originally contributed by Jeffrey Harris. 2import datetime 3import struct 4 5from six.moves import winreg 6from six import text_type 7 8try: 9 import ctypes 10 from ctypes import wintypes 11except ValueError: 12 # ValueError is raised on non-Windows systems for some horrible reason. 13 raise ImportError("Running tzwin on non-Windows system") 14 15from ._common import tzname_in_python2, _tzinfo 16from ._common import tzrangebase 17 18__all__ = ["tzwin", "tzwinlocal", "tzres"] 19 20ONEWEEK = datetime.timedelta(7) 21 22TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones" 23TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones" 24TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation" 25 26 27def _settzkeyname(): 28 handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) 29 try: 30 winreg.OpenKey(handle, TZKEYNAMENT).Close() 31 TZKEYNAME = TZKEYNAMENT 32 except WindowsError: 33 TZKEYNAME = TZKEYNAME9X 34 handle.Close() 35 return TZKEYNAME 36 37TZKEYNAME = _settzkeyname() 38 39 40class tzres(object): 41 """ 42 Class for accessing `tzres.dll`, which contains timezone name related 43 resources. 44 45 .. versionadded:: 2.5.0 46 """ 47 p_wchar = ctypes.POINTER(wintypes.WCHAR) # Pointer to a wide char 48 49 def __init__(self, tzres_loc='tzres.dll'): 50 # Load the user32 DLL so we can load strings from tzres 51 user32 = ctypes.WinDLL('user32') 52 53 # Specify the LoadStringW function 54 user32.LoadStringW.argtypes = (wintypes.HINSTANCE, 55 wintypes.UINT, 56 wintypes.LPWSTR, 57 ctypes.c_int) 58 59 self.LoadStringW = user32.LoadStringW 60 self._tzres = ctypes.WinDLL(tzres_loc) 61 self.tzres_loc = tzres_loc 62 63 def load_name(self, offset): 64 """ 65 Load a timezone name from a DLL offset (integer). 66 67 >>> from dateutil.tzwin import tzres 68 >>> tzr = tzres() 69 >>> print(tzr.load_name(112)) 70 'Eastern Standard Time' 71 72 :param offset: 73 A positive integer value referring to a string from the tzres dll. 74 75 ..note: 76 Offsets found in the registry are generally of the form 77 `@tzres.dll,-114`. The offset in this case if 114, not -114. 78 79 """ 80 resource = self.p_wchar() 81 lpBuffer = ctypes.cast(ctypes.byref(resource), wintypes.LPWSTR) 82 nchar = self.LoadStringW(self._tzres._handle, offset, lpBuffer, 0) 83 return resource[:nchar] 84 85 def name_from_string(self, tzname_str): 86 """ 87 Parse strings as returned from the Windows registry into the time zone 88 name as defined in the registry. 89 90 >>> from dateutil.tzwin import tzres 91 >>> tzr = tzres() 92 >>> print(tzr.name_from_string('@tzres.dll,-251')) 93 'Dateline Daylight Time' 94 >>> print(tzr.name_from_string('Eastern Standard Time')) 95 'Eastern Standard Time' 96 97 :param tzname_str: 98 A timezone name string as returned from a Windows registry key. 99 100 :return: 101 Returns the localized timezone string from tzres.dll if the string 102 is of the form `@tzres.dll,-offset`, else returns the input string. 103 """ 104 if not tzname_str.startswith('@'): 105 return tzname_str 106 107 name_splt = tzname_str.split(',-') 108 try: 109 offset = int(name_splt[1]) 110 except: 111 raise ValueError("Malformed timezone string.") 112 113 return self.load_name(offset) 114 115 116class tzwinbase(tzrangebase): 117 """tzinfo class based on win32's timezones available in the registry.""" 118 def __init__(self): 119 raise NotImplementedError('tzwinbase is an abstract base class') 120 121 def __eq__(self, other): 122 # Compare on all relevant dimensions, including name. 123 if not isinstance(other, tzwinbase): 124 return NotImplemented 125 126 return (self._std_offset == other._std_offset and 127 self._dst_offset == other._dst_offset and 128 self._stddayofweek == other._stddayofweek and 129 self._dstdayofweek == other._dstdayofweek and 130 self._stdweeknumber == other._stdweeknumber and 131 self._dstweeknumber == other._dstweeknumber and 132 self._stdhour == other._stdhour and 133 self._dsthour == other._dsthour and 134 self._stdminute == other._stdminute and 135 self._dstminute == other._dstminute and 136 self._std_abbr == other._std_abbr and 137 self._dst_abbr == other._dst_abbr) 138 139 @staticmethod 140 def list(): 141 """Return a list of all time zones known to the system.""" 142 with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: 143 with winreg.OpenKey(handle, TZKEYNAME) as tzkey: 144 result = [winreg.EnumKey(tzkey, i) 145 for i in range(winreg.QueryInfoKey(tzkey)[0])] 146 return result 147 148 def display(self): 149 return self._display 150 151 def transitions(self, year): 152 """ 153 For a given year, get the DST on and off transition times, expressed 154 always on the standard time side. For zones with no transitions, this 155 function returns ``None``. 156 157 :param year: 158 The year whose transitions you would like to query. 159 160 :return: 161 Returns a :class:`tuple` of :class:`datetime.datetime` objects, 162 ``(dston, dstoff)`` for zones with an annual DST transition, or 163 ``None`` for fixed offset zones. 164 """ 165 166 if not self.hasdst: 167 return None 168 169 dston = picknthweekday(year, self._dstmonth, self._dstdayofweek, 170 self._dsthour, self._dstminute, 171 self._dstweeknumber) 172 173 dstoff = picknthweekday(year, self._stdmonth, self._stddayofweek, 174 self._stdhour, self._stdminute, 175 self._stdweeknumber) 176 177 # Ambiguous dates default to the STD side 178 dstoff -= self._dst_base_offset 179 180 return dston, dstoff 181 182 def _get_hasdst(self): 183 return self._dstmonth != 0 184 185 @property 186 def _dst_base_offset(self): 187 return self._dst_base_offset_ 188 189 190class tzwin(tzwinbase): 191 192 def __init__(self, name): 193 self._name = name 194 195 # multiple contexts only possible in 2.7 and 3.1, we still support 2.6 196 with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: 197 tzkeyname = text_type("{kn}\{name}").format(kn=TZKEYNAME, name=name) 198 with winreg.OpenKey(handle, tzkeyname) as tzkey: 199 keydict = valuestodict(tzkey) 200 201 self._std_abbr = keydict["Std"] 202 self._dst_abbr = keydict["Dlt"] 203 204 self._display = keydict["Display"] 205 206 # See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm 207 tup = struct.unpack("=3l16h", keydict["TZI"]) 208 stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1 209 dstoffset = stdoffset-tup[2] # + DaylightBias * -1 210 self._std_offset = datetime.timedelta(minutes=stdoffset) 211 self._dst_offset = datetime.timedelta(minutes=dstoffset) 212 213 # for the meaning see the win32 TIME_ZONE_INFORMATION structure docs 214 # http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx 215 (self._stdmonth, 216 self._stddayofweek, # Sunday = 0 217 self._stdweeknumber, # Last = 5 218 self._stdhour, 219 self._stdminute) = tup[4:9] 220 221 (self._dstmonth, 222 self._dstdayofweek, # Sunday = 0 223 self._dstweeknumber, # Last = 5 224 self._dsthour, 225 self._dstminute) = tup[12:17] 226 227 self._dst_base_offset_ = self._dst_offset - self._std_offset 228 self.hasdst = self._get_hasdst() 229 230 def __repr__(self): 231 return "tzwin(%s)" % repr(self._name) 232 233 def __reduce__(self): 234 return (self.__class__, (self._name,)) 235 236 237class tzwinlocal(tzwinbase): 238 def __init__(self): 239 with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: 240 with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey: 241 keydict = valuestodict(tzlocalkey) 242 243 self._std_abbr = keydict["StandardName"] 244 self._dst_abbr = keydict["DaylightName"] 245 246 try: 247 tzkeyname = text_type('{kn}\{sn}').format(kn=TZKEYNAME, 248 sn=self._std_abbr) 249 with winreg.OpenKey(handle, tzkeyname) as tzkey: 250 _keydict = valuestodict(tzkey) 251 self._display = _keydict["Display"] 252 except OSError: 253 self._display = None 254 255 stdoffset = -keydict["Bias"]-keydict["StandardBias"] 256 dstoffset = stdoffset-keydict["DaylightBias"] 257 258 self._std_offset = datetime.timedelta(minutes=stdoffset) 259 self._dst_offset = datetime.timedelta(minutes=dstoffset) 260 261 # For reasons unclear, in this particular key, the day of week has been 262 # moved to the END of the SYSTEMTIME structure. 263 tup = struct.unpack("=8h", keydict["StandardStart"]) 264 265 (self._stdmonth, 266 self._stdweeknumber, # Last = 5 267 self._stdhour, 268 self._stdminute) = tup[1:5] 269 270 self._stddayofweek = tup[7] 271 272 tup = struct.unpack("=8h", keydict["DaylightStart"]) 273 274 (self._dstmonth, 275 self._dstweeknumber, # Last = 5 276 self._dsthour, 277 self._dstminute) = tup[1:5] 278 279 self._dstdayofweek = tup[7] 280 281 self._dst_base_offset_ = self._dst_offset - self._std_offset 282 self.hasdst = self._get_hasdst() 283 284 def __repr__(self): 285 return "tzwinlocal()" 286 287 def __str__(self): 288 # str will return the standard name, not the daylight name. 289 return "tzwinlocal(%s)" % repr(self._std_abbr) 290 291 def __reduce__(self): 292 return (self.__class__, ()) 293 294 295def picknthweekday(year, month, dayofweek, hour, minute, whichweek): 296 """ dayofweek == 0 means Sunday, whichweek 5 means last instance """ 297 first = datetime.datetime(year, month, 1, hour, minute) 298 299 # This will work if dayofweek is ISO weekday (1-7) or Microsoft-style (0-6), 300 # Because 7 % 7 = 0 301 weekdayone = first.replace(day=((dayofweek - first.isoweekday()) % 7) + 1) 302 wd = weekdayone + ((whichweek - 1) * ONEWEEK) 303 if (wd.month != month): 304 wd -= ONEWEEK 305 306 return wd 307 308 309def valuestodict(key): 310 """Convert a registry key's values to a dictionary.""" 311 dout = {} 312 size = winreg.QueryInfoKey(key)[1] 313 tz_res = None 314 315 for i in range(size): 316 key_name, value, dtype = winreg.EnumValue(key, i) 317 if dtype == winreg.REG_DWORD or dtype == winreg.REG_DWORD_LITTLE_ENDIAN: 318 # If it's a DWORD (32-bit integer), it's stored as unsigned - convert 319 # that to a proper signed integer 320 if value & (1 << 31): 321 value = value - (1 << 32) 322 elif dtype == winreg.REG_SZ: 323 # If it's a reference to the tzres DLL, load the actual string 324 if value.startswith('@tzres'): 325 tz_res = tz_res or tzres() 326 value = tz_res.name_from_string(value) 327 328 value = value.rstrip('\x00') # Remove trailing nulls 329 330 dout[key_name] = value 331 332 return dout 333