1
- from datetime import tzinfo , timedelta , datetime
1
+ from datetime import tzinfo , timedelta , datetime , timezone
2
2
3
3
ZERO = timedelta (0 )
4
4
HOUR = timedelta (hours = 1 )
5
-
6
- # A UTC class.
7
-
8
- class UTC (tzinfo ):
9
- """UTC"""
10
-
11
- def utcoffset (self , dt ):
12
- return ZERO
13
-
14
- def tzname (self , dt ):
15
- return "UTC"
16
-
17
- def dst (self , dt ):
18
- return ZERO
19
-
20
- utc = UTC ()
21
-
22
- # A class building tzinfo objects for fixed-offset time zones.
23
- # Note that FixedOffset(0, "UTC") is a different way to build a
24
- # UTC tzinfo object.
25
-
26
- class FixedOffset (tzinfo ):
27
- """Fixed offset in minutes east from UTC."""
28
-
29
- def __init__ (self , offset , name ):
30
- self .__offset = timedelta (minutes = offset )
31
- self .__name = name
32
-
33
- def utcoffset (self , dt ):
34
- return self .__offset
35
-
36
- def tzname (self , dt ):
37
- return self .__name
38
-
39
- def dst (self , dt ):
40
- return ZERO
5
+ SECOND = timedelta (seconds = 1 )
41
6
42
7
# A class capturing the platform's idea of local time.
43
-
8
+ # (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.)
44
11
import time as _time
45
12
46
13
STDOFFSET = timedelta (seconds = - _time .timezone )
@@ -53,6 +20,16 @@ def dst(self, dt):
53
20
54
21
class LocalTimezone (tzinfo ):
55
22
23
+ 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
+
56
33
def utcoffset (self , dt ):
57
34
if self ._isdst (dt ):
58
35
return DSTOFFSET
@@ -99,20 +76,37 @@ def first_sunday_on_or_after(dt):
99
76
# In the US, since 2007, DST starts at 2am (standard time) on the second
100
77
# Sunday in March, which is the first Sunday on or after Mar 8.
101
78
DSTSTART_2007 = datetime (1 , 3 , 8 , 2 )
102
- # and ends at 2am (DST time; 1am standard time ) on the first Sunday of Nov.
103
- DSTEND_2007 = datetime (1 , 11 , 1 , 1 )
79
+ # and ends at 2am (DST time) on the first Sunday of Nov.
80
+ DSTEND_2007 = datetime (1 , 11 , 1 , 2 )
104
81
# From 1987 to 2006, DST used to start at 2am (standard time) on the first
105
- # Sunday in April and to end at 2am (DST time; 1am standard time ) on the last
82
+ # Sunday in April and to end at 2am (DST time) on the last
106
83
# Sunday of October, which is the first Sunday on or after Oct 25.
107
84
DSTSTART_1987_2006 = datetime (1 , 4 , 1 , 2 )
108
- DSTEND_1987_2006 = datetime (1 , 10 , 25 , 1 )
85
+ DSTEND_1987_2006 = datetime (1 , 10 , 25 , 2 )
109
86
# From 1967 to 1986, DST used to start at 2am (standard time) on the last
110
- # Sunday in April (the one on or after April 24) and to end at 2am (DST time;
111
- # 1am standard time) on the last Sunday of October, which is the first Sunday
87
+ # 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
112
89
# on or after Oct 25.
113
90
DSTSTART_1967_1986 = datetime (1 , 4 , 24 , 2 )
114
91
DSTEND_1967_1986 = DSTEND_1987_2006
115
92
93
+ def 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
+
116
110
class USTimeZone (tzinfo ):
117
111
118
112
def __init__ (self , hours , reprname , stdname , dstname ):
@@ -141,27 +135,39 @@ def dst(self, dt):
141
135
# implementation) passes a datetime with dt.tzinfo is self.
142
136
return ZERO
143
137
assert dt .tzinfo is self
144
-
145
- # Find start and end times for US DST. For years before 1967, return
146
- # ZERO for no DST.
147
- if 2006 < dt .year :
148
- dststart , dstend = DSTSTART_2007 , DSTEND_2007
149
- elif 1986 < dt .year < 2007 :
150
- dststart , dstend = DSTSTART_1987_2006 , DSTEND_1987_2006
151
- elif 1966 < dt .year < 1987 :
152
- dststart , dstend = DSTSTART_1967_1986 , DSTEND_1967_1986
153
- else :
154
- return ZERO
155
-
156
- start = first_sunday_on_or_after (dststart .replace (year = dt .year ))
157
- end = first_sunday_on_or_after (dstend .replace (year = dt .year ))
158
-
138
+ start , end = us_dst_range (dt .year )
159
139
# Can't compare naive to aware objects, so strip the timezone from
160
140
# dt first.
161
- if start <= dt .replace (tzinfo = None ) < end :
141
+ dt = dt .replace (tzinfo = None )
142
+ if start + HOUR <= dt < end - HOUR :
143
+ # DST is in effect.
162
144
return HOUR
163
- else :
164
- return ZERO
145
+ 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
+
165
171
166
172
Eastern = USTimeZone (- 5 , "Eastern" , "EST" , "EDT" )
167
173
Central = USTimeZone (- 6 , "Central" , "CST" , "CDT" )
0 commit comments