win.py 12 KB

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