blob: 9b9e32a553e7d8e82037e9383e22c856a063328e [file] [log] [blame]
Sergey Fedoseev3a9bb5f2018-07-05 09:47:37 +05001from datetime import tzinfo, timedelta, datetime
Georg Brandl116aa622007-08-15 14:28:22 +00002
3ZERO = timedelta(0)
4HOUR = timedelta(hours=1)
Alexander Belopolsky53868aa2016-08-24 18:30:16 -04005SECOND = timedelta(seconds=1)
Georg Brandl116aa622007-08-15 14:28:22 +00006
7# A class capturing the platform's idea of local time.
Alexander Belopolsky53868aa2016-08-24 18:30:16 -04008# (May result in wrong values on historical times in
9# timezones where UTC offset and/or the DST rules had
10# changed in the past.)
Georg Brandl116aa622007-08-15 14:28:22 +000011import time as _time
12
13STDOFFSET = timedelta(seconds = -_time.timezone)
14if _time.daylight:
15 DSTOFFSET = timedelta(seconds = -_time.altzone)
16else:
17 DSTOFFSET = STDOFFSET
18
19DSTDIFF = DSTOFFSET - STDOFFSET
20
21class LocalTimezone(tzinfo):
22
Alexander Belopolsky53868aa2016-08-24 18:30:16 -040023 def fromutc(self, dt):
24 assert dt.tzinfo is self
25 stamp = (dt - datetime(1970, 1, 1, tzinfo=self)) // SECOND
26 args = _time.localtime(stamp)[:6]
27 dst_diff = DSTDIFF // SECOND
28 # Detect fold
29 fold = (args == _time.localtime(stamp - dst_diff))
30 return datetime(*args, microsecond=dt.microsecond,
31 tzinfo=self, fold=fold)
32
Georg Brandl116aa622007-08-15 14:28:22 +000033 def utcoffset(self, dt):
34 if self._isdst(dt):
35 return DSTOFFSET
36 else:
37 return STDOFFSET
38
39 def dst(self, dt):
40 if self._isdst(dt):
41 return DSTDIFF
42 else:
43 return ZERO
44
45 def tzname(self, dt):
46 return _time.tzname[self._isdst(dt)]
47
48 def _isdst(self, dt):
49 tt = (dt.year, dt.month, dt.day,
50 dt.hour, dt.minute, dt.second,
Alexander Belopolskyb2eacd92010-12-24 00:24:11 +000051 dt.weekday(), 0, 0)
Georg Brandl116aa622007-08-15 14:28:22 +000052 stamp = _time.mktime(tt)
53 tt = _time.localtime(stamp)
54 return tt.tm_isdst > 0
55
56Local = LocalTimezone()
57
58
59# A complete implementation of current DST rules for major US time zones.
60
61def first_sunday_on_or_after(dt):
62 days_to_go = 6 - dt.weekday()
63 if days_to_go:
64 dt += timedelta(days_to_go)
65 return dt
66
Christian Heimes5e696852008-04-09 08:37:03 +000067
68# US DST Rules
69#
70# This is a simplified (i.e., wrong for a few cases) set of rules for US
71# DST start and end times. For a complete and up-to-date set of DST rules
72# and timezone definitions, visit the Olson Database (or try pytz):
73# http://www.twinsun.com/tz/tz-link.htm
74# http://sourceforge.net/projects/pytz/ (might not be up-to-date)
75#
76# In the US, since 2007, DST starts at 2am (standard time) on the second
77# Sunday in March, which is the first Sunday on or after Mar 8.
78DSTSTART_2007 = datetime(1, 3, 8, 2)
Alexander Belopolsky53868aa2016-08-24 18:30:16 -040079# and ends at 2am (DST time) on the first Sunday of Nov.
80DSTEND_2007 = datetime(1, 11, 1, 2)
Christian Heimes5e696852008-04-09 08:37:03 +000081# From 1987 to 2006, DST used to start at 2am (standard time) on the first
Alexander Belopolsky53868aa2016-08-24 18:30:16 -040082# Sunday in April and to end at 2am (DST time) on the last
Christian Heimes5e696852008-04-09 08:37:03 +000083# Sunday of October, which is the first Sunday on or after Oct 25.
84DSTSTART_1987_2006 = datetime(1, 4, 1, 2)
Alexander Belopolsky53868aa2016-08-24 18:30:16 -040085DSTEND_1987_2006 = datetime(1, 10, 25, 2)
Christian Heimes5e696852008-04-09 08:37:03 +000086# From 1967 to 1986, DST used to start at 2am (standard time) on the last
Alexander Belopolsky53868aa2016-08-24 18:30:16 -040087# Sunday in April (the one on or after April 24) and to end at 2am (DST time)
88# on the last Sunday of October, which is the first Sunday
Christian Heimes5e696852008-04-09 08:37:03 +000089# on or after Oct 25.
90DSTSTART_1967_1986 = datetime(1, 4, 24, 2)
91DSTEND_1967_1986 = DSTEND_1987_2006
Georg Brandl116aa622007-08-15 14:28:22 +000092
Alexander Belopolsky53868aa2016-08-24 18:30:16 -040093def us_dst_range(year):
94 # Find start and end times for US DST. For years before 1967, return
95 # start = end for no DST.
96 if 2006 < year:
97 dststart, dstend = DSTSTART_2007, DSTEND_2007
98 elif 1986 < year < 2007:
99 dststart, dstend = DSTSTART_1987_2006, DSTEND_1987_2006
100 elif 1966 < year < 1987:
101 dststart, dstend = DSTSTART_1967_1986, DSTEND_1967_1986
102 else:
103 return (datetime(year, 1, 1), ) * 2
104
105 start = first_sunday_on_or_after(dststart.replace(year=year))
106 end = first_sunday_on_or_after(dstend.replace(year=year))
107 return start, end
108
109
Georg Brandl116aa622007-08-15 14:28:22 +0000110class USTimeZone(tzinfo):
111
112 def __init__(self, hours, reprname, stdname, dstname):
113 self.stdoffset = timedelta(hours=hours)
114 self.reprname = reprname
115 self.stdname = stdname
116 self.dstname = dstname
117
118 def __repr__(self):
119 return self.reprname
120
121 def tzname(self, dt):
122 if self.dst(dt):
123 return self.dstname
124 else:
125 return self.stdname
126
127 def utcoffset(self, dt):
128 return self.stdoffset + self.dst(dt)
129
130 def dst(self, dt):
131 if dt is None or dt.tzinfo is None:
132 # An exception may be sensible here, in one or both cases.
133 # It depends on how you want to treat them. The default
134 # fromutc() implementation (called by the default astimezone()
135 # implementation) passes a datetime with dt.tzinfo is self.
136 return ZERO
137 assert dt.tzinfo is self
Alexander Belopolsky53868aa2016-08-24 18:30:16 -0400138 start, end = us_dst_range(dt.year)
Georg Brandl116aa622007-08-15 14:28:22 +0000139 # Can't compare naive to aware objects, so strip the timezone from
140 # dt first.
Alexander Belopolsky53868aa2016-08-24 18:30:16 -0400141 dt = dt.replace(tzinfo=None)
142 if start + HOUR <= dt < end - HOUR:
143 # DST is in effect.
Georg Brandl116aa622007-08-15 14:28:22 +0000144 return HOUR
Alexander Belopolsky53868aa2016-08-24 18:30:16 -0400145 if end - HOUR <= dt < end:
146 # Fold (an ambiguous hour): use dt.fold to disambiguate.
147 return ZERO if dt.fold else HOUR
148 if start <= dt < start + HOUR:
149 # Gap (a non-existent hour): reverse the fold rule.
150 return HOUR if dt.fold else ZERO
151 # DST is off.
152 return ZERO
153
154 def fromutc(self, dt):
155 assert dt.tzinfo is self
156 start, end = us_dst_range(dt.year)
157 start = start.replace(tzinfo=self)
158 end = end.replace(tzinfo=self)
159 std_time = dt + self.stdoffset
160 dst_time = std_time + HOUR
161 if end <= dst_time < end + HOUR:
162 # Repeated hour
163 return std_time.replace(fold=1)
164 if std_time < start or dst_time >= end:
165 # Standard time
166 return std_time
167 if start <= std_time < end - HOUR:
168 # Daylight saving time
169 return dst_time
170
Georg Brandl116aa622007-08-15 14:28:22 +0000171
172Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
173Central = USTimeZone(-6, "Central", "CST", "CDT")
174Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
175Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")