371 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			371 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- coding: utf-8 -*-
 | |
| """
 | |
| This module provides an interface to the native time zone data on Windows,
 | |
| including :py:class:`datetime.tzinfo` implementations.
 | |
| 
 | |
| Attempting to import this module on a non-Windows platform will raise an
 | |
| :py:obj:`ImportError`.
 | |
| """
 | |
| # 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 is 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 the display name of the time zone.
 | |
|         """
 | |
|         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):
 | |
|     """
 | |
|     Time zone object created from the zone info in the Windows registry
 | |
| 
 | |
|     These are similar to :py:class:`dateutil.tz.tzrange` objects in that
 | |
|     the time zone data is provided in the format of a single offset rule
 | |
|     for either 0 or 2 time zone transitions per year.
 | |
| 
 | |
|     :param: name
 | |
|         The name of a Windows time zone key, e.g. "Eastern Standard Time".
 | |
|         The full list of keys can be retrieved with :func:`tzwin.list`.
 | |
|     """
 | |
| 
 | |
|     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):
 | |
|     """
 | |
|     Class representing the local time zone information in the Windows registry
 | |
| 
 | |
|     While :class:`dateutil.tz.tzlocal` makes system calls (via the :mod:`time`
 | |
|     module) to retrieve time zone information, ``tzwinlocal`` retrieves the
 | |
|     rules directly from the Windows registry and creates an object like
 | |
|     :class:`dateutil.tz.tzwin`.
 | |
| 
 | |
|     Because Windows does not have an equivalent of :func:`time.tzset`, on
 | |
|     Windows, :class:`dateutil.tz.tzlocal` instances will always reflect the
 | |
|     time zone settings *at the time that the process was started*, meaning
 | |
|     changes to the machine's time zone settings during the run of a program
 | |
|     on Windows will **not** be reflected by :class:`dateutil.tz.tzlocal`.
 | |
|     Because ``tzwinlocal`` reads the registry directly, it is unaffected by
 | |
|     this issue.
 | |
|     """
 | |
|     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
 |