win.py 13 KB

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