1import _winreg
2import struct
3import datetime
4
5handle=_winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
6tzparent=_winreg.OpenKey(handle,
7            "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones")
8parentsize=_winreg.QueryInfoKey(tzparent)[0]
9
10localkey=_winreg.OpenKey(handle,
11            "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation")
12WEEKS=datetime.timedelta(7)
13
14def list_timezones():
15    """Return a list of all time zones known to the system."""
16    l=[]
17    for i in xrange(parentsize):
18        l.append(_winreg.EnumKey(tzparent, i))
19    return l
20
21class win32tz(datetime.tzinfo):
22    """tzinfo class based on win32's timezones available in the registry.
23
24    >>> local = win32tz('Central Standard Time')
25    >>> oct1 = datetime.datetime(month=10, year=2004, day=1, tzinfo=local)
26    >>> dec1 = datetime.datetime(month=12, year=2004, day=1, tzinfo=local)
27    >>> oct1.dst()
28    datetime.timedelta(0, 3600)
29    >>> dec1.dst()
30    datetime.timedelta(0)
31    >>> braz = win32tz('E. South America Standard Time')
32    >>> braz.dst(oct1)
33    datetime.timedelta(0)
34    >>> braz.dst(dec1)
35    datetime.timedelta(0, 3600)
36
37    """
38    def __init__(self, name):
39        self.data=win32tz_data(name)
40
41    def utcoffset(self, dt):
42        if self._isdst(dt):
43            return datetime.timedelta(minutes=self.data.dstoffset)
44        else:
45            return datetime.timedelta(minutes=self.data.stdoffset)
46
47    def dst(self, dt):
48        if self._isdst(dt):
49            minutes = self.data.dstoffset - self.data.stdoffset
50            return datetime.timedelta(minutes=minutes)
51        else:
52            return datetime.timedelta(0)
53
54    def tzname(self, dt):
55        if self._isdst(dt): return self.data.dstname
56        else: return self.data.stdname
57
58    def _isdst(self, dt):
59        dat=self.data
60        dston = pickNthWeekday(dt.year, dat.dstmonth, dat.dstdayofweek,
61                               dat.dsthour, dat.dstminute, dat.dstweeknumber)
62        dstoff = pickNthWeekday(dt.year, dat.stdmonth, dat.stddayofweek,
63                                dat.stdhour, dat.stdminute, dat.stdweeknumber)
64        if dston < dstoff:
65            if dston <= dt.replace(tzinfo=None) < dstoff: return True
66            else: return False
67        else:
68            if dstoff <= dt.replace(tzinfo=None) < dston: return False
69            else: return True
70
71    def __repr__(self):
72        return "<win32tz - {0!s}>".format(self.data.display)
73
74def pickNthWeekday(year, month, dayofweek, hour, minute, whichweek):
75    """dayofweek == 0 means Sunday, whichweek > 4 means last instance"""
76    first = datetime.datetime(year=year, month=month, hour=hour, minute=minute,
77                              day=1)
78    weekdayone = first.replace(day=((dayofweek - first.isoweekday()) % 7 + 1))
79    for n in xrange(whichweek - 1, -1, -1):
80        dt=weekdayone + n * WEEKS
81        if dt.month == month: return dt
82
83
84class win32tz_data(object):
85    """Read a registry key for a timezone, expose its contents."""
86
87    def __init__(self, path):
88        """Load path, or if path is empty, load local time."""
89        if path:
90            keydict=valuesToDict(_winreg.OpenKey(tzparent, path))
91            self.display = keydict['Display']
92            self.dstname = keydict['Dlt']
93            self.stdname = keydict['Std']
94
95            #see http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
96            tup = struct.unpack('=3l16h', keydict['TZI'])
97            self.stdoffset = -tup[0]-tup[1] #Bias + StandardBias * -1
98            self.dstoffset = self.stdoffset - tup[2] # + DaylightBias * -1
99
100            offset=3
101            self.stdmonth = tup[1 + offset]
102            self.stddayofweek = tup[2 + offset] #Sunday=0
103            self.stdweeknumber = tup[3 + offset] #Last = 5
104            self.stdhour = tup[4 + offset]
105            self.stdminute = tup[5 + offset]
106
107            offset=11
108            self.dstmonth = tup[1 + offset]
109            self.dstdayofweek = tup[2 + offset] #Sunday=0
110            self.dstweeknumber = tup[3 + offset] #Last = 5
111            self.dsthour = tup[4 + offset]
112            self.dstminute = tup[5 + offset]
113
114        else:
115            keydict=valuesToDict(localkey)
116
117            self.stdname = keydict['StandardName']
118            self.dstname = keydict['DaylightName']
119
120            sourcekey=_winreg.OpenKey(tzparent, self.stdname)
121            self.display = valuesToDict(sourcekey)['Display']
122
123            self.stdoffset = -keydict['Bias']-keydict['StandardBias']
124            self.dstoffset = self.stdoffset - keydict['DaylightBias']
125
126            #see http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
127            tup = struct.unpack('=8h', keydict['StandardStart'])
128
129            offset=0
130            self.stdmonth = tup[1 + offset]
131            self.stddayofweek = tup[2 + offset] #Sunday=0
132            self.stdweeknumber = tup[3 + offset] #Last = 5
133            self.stdhour = tup[4 + offset]
134            self.stdminute = tup[5 + offset]
135
136            tup = struct.unpack('=8h', keydict['DaylightStart'])
137            self.dstmonth = tup[1 + offset]
138            self.dstdayofweek = tup[2 + offset] #Sunday=0
139            self.dstweeknumber = tup[3 + offset] #Last = 5
140            self.dsthour = tup[4 + offset]
141            self.dstminute = tup[5 + offset]
142
143def valuesToDict(key):
144    """Convert a registry key's values to a dictionary."""
145    dict={}
146    size=_winreg.QueryInfoKey(key)[1]
147    for i in xrange(size):
148        dict[_winreg.EnumValue(key, i)[0]]=_winreg.EnumValue(key, i)[1]
149    return dict
150
151def _test():
152    import win32tz, doctest
153    doctest.testmod(win32tz, verbose=0)
154
155if __name__ == '__main__':
156    _test()