You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			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
 |