jpayne@69
|
1 '''Base classes and helpers for building zone specific tzinfo classes'''
|
jpayne@69
|
2
|
jpayne@69
|
3 from datetime import datetime, timedelta, tzinfo
|
jpayne@69
|
4 from bisect import bisect_right
|
jpayne@69
|
5 try:
|
jpayne@69
|
6 set
|
jpayne@69
|
7 except NameError:
|
jpayne@69
|
8 from sets import Set as set
|
jpayne@69
|
9
|
jpayne@69
|
10 import pytz
|
jpayne@69
|
11 from pytz.exceptions import AmbiguousTimeError, NonExistentTimeError
|
jpayne@69
|
12
|
jpayne@69
|
13 __all__ = []
|
jpayne@69
|
14
|
jpayne@69
|
15 _timedelta_cache = {}
|
jpayne@69
|
16
|
jpayne@69
|
17
|
jpayne@69
|
18 def memorized_timedelta(seconds):
|
jpayne@69
|
19 '''Create only one instance of each distinct timedelta'''
|
jpayne@69
|
20 try:
|
jpayne@69
|
21 return _timedelta_cache[seconds]
|
jpayne@69
|
22 except KeyError:
|
jpayne@69
|
23 delta = timedelta(seconds=seconds)
|
jpayne@69
|
24 _timedelta_cache[seconds] = delta
|
jpayne@69
|
25 return delta
|
jpayne@69
|
26
|
jpayne@69
|
27
|
jpayne@69
|
28 _epoch = datetime(1970, 1, 1, 0, 0) # datetime.utcfromtimestamp(0)
|
jpayne@69
|
29 _datetime_cache = {0: _epoch}
|
jpayne@69
|
30
|
jpayne@69
|
31
|
jpayne@69
|
32 def memorized_datetime(seconds):
|
jpayne@69
|
33 '''Create only one instance of each distinct datetime'''
|
jpayne@69
|
34 try:
|
jpayne@69
|
35 return _datetime_cache[seconds]
|
jpayne@69
|
36 except KeyError:
|
jpayne@69
|
37 # NB. We can't just do datetime.fromtimestamp(seconds, tz=timezone.utc).replace(tzinfo=None)
|
jpayne@69
|
38 # as this fails with negative values under Windows (Bug #90096)
|
jpayne@69
|
39 dt = _epoch + timedelta(seconds=seconds)
|
jpayne@69
|
40 _datetime_cache[seconds] = dt
|
jpayne@69
|
41 return dt
|
jpayne@69
|
42
|
jpayne@69
|
43
|
jpayne@69
|
44 _ttinfo_cache = {}
|
jpayne@69
|
45
|
jpayne@69
|
46
|
jpayne@69
|
47 def memorized_ttinfo(*args):
|
jpayne@69
|
48 '''Create only one instance of each distinct tuple'''
|
jpayne@69
|
49 try:
|
jpayne@69
|
50 return _ttinfo_cache[args]
|
jpayne@69
|
51 except KeyError:
|
jpayne@69
|
52 ttinfo = (
|
jpayne@69
|
53 memorized_timedelta(args[0]),
|
jpayne@69
|
54 memorized_timedelta(args[1]),
|
jpayne@69
|
55 args[2]
|
jpayne@69
|
56 )
|
jpayne@69
|
57 _ttinfo_cache[args] = ttinfo
|
jpayne@69
|
58 return ttinfo
|
jpayne@69
|
59
|
jpayne@69
|
60
|
jpayne@69
|
61 _notime = memorized_timedelta(0)
|
jpayne@69
|
62
|
jpayne@69
|
63
|
jpayne@69
|
64 def _to_seconds(td):
|
jpayne@69
|
65 '''Convert a timedelta to seconds'''
|
jpayne@69
|
66 return td.seconds + td.days * 24 * 60 * 60
|
jpayne@69
|
67
|
jpayne@69
|
68
|
jpayne@69
|
69 class BaseTzInfo(tzinfo):
|
jpayne@69
|
70 # Overridden in subclass
|
jpayne@69
|
71 _utcoffset = None
|
jpayne@69
|
72 _tzname = None
|
jpayne@69
|
73 zone = None
|
jpayne@69
|
74
|
jpayne@69
|
75 def __str__(self):
|
jpayne@69
|
76 return self.zone
|
jpayne@69
|
77
|
jpayne@69
|
78
|
jpayne@69
|
79 class StaticTzInfo(BaseTzInfo):
|
jpayne@69
|
80 '''A timezone that has a constant offset from UTC
|
jpayne@69
|
81
|
jpayne@69
|
82 These timezones are rare, as most locations have changed their
|
jpayne@69
|
83 offset at some point in their history
|
jpayne@69
|
84 '''
|
jpayne@69
|
85 def fromutc(self, dt):
|
jpayne@69
|
86 '''See datetime.tzinfo.fromutc'''
|
jpayne@69
|
87 if dt.tzinfo is not None and dt.tzinfo is not self:
|
jpayne@69
|
88 raise ValueError('fromutc: dt.tzinfo is not self')
|
jpayne@69
|
89 return (dt + self._utcoffset).replace(tzinfo=self)
|
jpayne@69
|
90
|
jpayne@69
|
91 def utcoffset(self, dt, is_dst=None):
|
jpayne@69
|
92 '''See datetime.tzinfo.utcoffset
|
jpayne@69
|
93
|
jpayne@69
|
94 is_dst is ignored for StaticTzInfo, and exists only to
|
jpayne@69
|
95 retain compatibility with DstTzInfo.
|
jpayne@69
|
96 '''
|
jpayne@69
|
97 return self._utcoffset
|
jpayne@69
|
98
|
jpayne@69
|
99 def dst(self, dt, is_dst=None):
|
jpayne@69
|
100 '''See datetime.tzinfo.dst
|
jpayne@69
|
101
|
jpayne@69
|
102 is_dst is ignored for StaticTzInfo, and exists only to
|
jpayne@69
|
103 retain compatibility with DstTzInfo.
|
jpayne@69
|
104 '''
|
jpayne@69
|
105 return _notime
|
jpayne@69
|
106
|
jpayne@69
|
107 def tzname(self, dt, is_dst=None):
|
jpayne@69
|
108 '''See datetime.tzinfo.tzname
|
jpayne@69
|
109
|
jpayne@69
|
110 is_dst is ignored for StaticTzInfo, and exists only to
|
jpayne@69
|
111 retain compatibility with DstTzInfo.
|
jpayne@69
|
112 '''
|
jpayne@69
|
113 return self._tzname
|
jpayne@69
|
114
|
jpayne@69
|
115 def localize(self, dt, is_dst=False):
|
jpayne@69
|
116 '''Convert naive time to local time'''
|
jpayne@69
|
117 if dt.tzinfo is not None:
|
jpayne@69
|
118 raise ValueError('Not naive datetime (tzinfo is already set)')
|
jpayne@69
|
119 return dt.replace(tzinfo=self)
|
jpayne@69
|
120
|
jpayne@69
|
121 def normalize(self, dt, is_dst=False):
|
jpayne@69
|
122 '''Correct the timezone information on the given datetime.
|
jpayne@69
|
123
|
jpayne@69
|
124 This is normally a no-op, as StaticTzInfo timezones never have
|
jpayne@69
|
125 ambiguous cases to correct:
|
jpayne@69
|
126
|
jpayne@69
|
127 >>> from pytz import timezone
|
jpayne@69
|
128 >>> gmt = timezone('GMT')
|
jpayne@69
|
129 >>> isinstance(gmt, StaticTzInfo)
|
jpayne@69
|
130 True
|
jpayne@69
|
131 >>> dt = datetime(2011, 5, 8, 1, 2, 3, tzinfo=gmt)
|
jpayne@69
|
132 >>> gmt.normalize(dt) is dt
|
jpayne@69
|
133 True
|
jpayne@69
|
134
|
jpayne@69
|
135 The supported method of converting between timezones is to use
|
jpayne@69
|
136 datetime.astimezone(). Currently normalize() also works:
|
jpayne@69
|
137
|
jpayne@69
|
138 >>> la = timezone('America/Los_Angeles')
|
jpayne@69
|
139 >>> dt = la.localize(datetime(2011, 5, 7, 1, 2, 3))
|
jpayne@69
|
140 >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'
|
jpayne@69
|
141 >>> gmt.normalize(dt).strftime(fmt)
|
jpayne@69
|
142 '2011-05-07 08:02:03 GMT (+0000)'
|
jpayne@69
|
143 '''
|
jpayne@69
|
144 if dt.tzinfo is self:
|
jpayne@69
|
145 return dt
|
jpayne@69
|
146 if dt.tzinfo is None:
|
jpayne@69
|
147 raise ValueError('Naive time - no tzinfo set')
|
jpayne@69
|
148 return dt.astimezone(self)
|
jpayne@69
|
149
|
jpayne@69
|
150 def __repr__(self):
|
jpayne@69
|
151 return '<StaticTzInfo %r>' % (self.zone,)
|
jpayne@69
|
152
|
jpayne@69
|
153 def __reduce__(self):
|
jpayne@69
|
154 # Special pickle to zone remains a singleton and to cope with
|
jpayne@69
|
155 # database changes.
|
jpayne@69
|
156 return pytz._p, (self.zone,)
|
jpayne@69
|
157
|
jpayne@69
|
158
|
jpayne@69
|
159 class DstTzInfo(BaseTzInfo):
|
jpayne@69
|
160 '''A timezone that has a variable offset from UTC
|
jpayne@69
|
161
|
jpayne@69
|
162 The offset might change if daylight saving time comes into effect,
|
jpayne@69
|
163 or at a point in history when the region decides to change their
|
jpayne@69
|
164 timezone definition.
|
jpayne@69
|
165 '''
|
jpayne@69
|
166 # Overridden in subclass
|
jpayne@69
|
167
|
jpayne@69
|
168 # Sorted list of DST transition times, UTC
|
jpayne@69
|
169 _utc_transition_times = None
|
jpayne@69
|
170
|
jpayne@69
|
171 # [(utcoffset, dstoffset, tzname)] corresponding to
|
jpayne@69
|
172 # _utc_transition_times entries
|
jpayne@69
|
173 _transition_info = None
|
jpayne@69
|
174
|
jpayne@69
|
175 zone = None
|
jpayne@69
|
176
|
jpayne@69
|
177 # Set in __init__
|
jpayne@69
|
178
|
jpayne@69
|
179 _tzinfos = None
|
jpayne@69
|
180 _dst = None # DST offset
|
jpayne@69
|
181
|
jpayne@69
|
182 def __init__(self, _inf=None, _tzinfos=None):
|
jpayne@69
|
183 if _inf:
|
jpayne@69
|
184 self._tzinfos = _tzinfos
|
jpayne@69
|
185 self._utcoffset, self._dst, self._tzname = _inf
|
jpayne@69
|
186 else:
|
jpayne@69
|
187 _tzinfos = {}
|
jpayne@69
|
188 self._tzinfos = _tzinfos
|
jpayne@69
|
189 self._utcoffset, self._dst, self._tzname = (
|
jpayne@69
|
190 self._transition_info[0])
|
jpayne@69
|
191 _tzinfos[self._transition_info[0]] = self
|
jpayne@69
|
192 for inf in self._transition_info[1:]:
|
jpayne@69
|
193 if inf not in _tzinfos:
|
jpayne@69
|
194 _tzinfos[inf] = self.__class__(inf, _tzinfos)
|
jpayne@69
|
195
|
jpayne@69
|
196 def fromutc(self, dt):
|
jpayne@69
|
197 '''See datetime.tzinfo.fromutc'''
|
jpayne@69
|
198 if (dt.tzinfo is not None and
|
jpayne@69
|
199 getattr(dt.tzinfo, '_tzinfos', None) is not self._tzinfos):
|
jpayne@69
|
200 raise ValueError('fromutc: dt.tzinfo is not self')
|
jpayne@69
|
201 dt = dt.replace(tzinfo=None)
|
jpayne@69
|
202 idx = max(0, bisect_right(self._utc_transition_times, dt) - 1)
|
jpayne@69
|
203 inf = self._transition_info[idx]
|
jpayne@69
|
204 return (dt + inf[0]).replace(tzinfo=self._tzinfos[inf])
|
jpayne@69
|
205
|
jpayne@69
|
206 def normalize(self, dt):
|
jpayne@69
|
207 '''Correct the timezone information on the given datetime
|
jpayne@69
|
208
|
jpayne@69
|
209 If date arithmetic crosses DST boundaries, the tzinfo
|
jpayne@69
|
210 is not magically adjusted. This method normalizes the
|
jpayne@69
|
211 tzinfo to the correct one.
|
jpayne@69
|
212
|
jpayne@69
|
213 To test, first we need to do some setup
|
jpayne@69
|
214
|
jpayne@69
|
215 >>> from pytz import timezone
|
jpayne@69
|
216 >>> utc = timezone('UTC')
|
jpayne@69
|
217 >>> eastern = timezone('US/Eastern')
|
jpayne@69
|
218 >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'
|
jpayne@69
|
219
|
jpayne@69
|
220 We next create a datetime right on an end-of-DST transition point,
|
jpayne@69
|
221 the instant when the wallclocks are wound back one hour.
|
jpayne@69
|
222
|
jpayne@69
|
223 >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc)
|
jpayne@69
|
224 >>> loc_dt = utc_dt.astimezone(eastern)
|
jpayne@69
|
225 >>> loc_dt.strftime(fmt)
|
jpayne@69
|
226 '2002-10-27 01:00:00 EST (-0500)'
|
jpayne@69
|
227
|
jpayne@69
|
228 Now, if we subtract a few minutes from it, note that the timezone
|
jpayne@69
|
229 information has not changed.
|
jpayne@69
|
230
|
jpayne@69
|
231 >>> before = loc_dt - timedelta(minutes=10)
|
jpayne@69
|
232 >>> before.strftime(fmt)
|
jpayne@69
|
233 '2002-10-27 00:50:00 EST (-0500)'
|
jpayne@69
|
234
|
jpayne@69
|
235 But we can fix that by calling the normalize method
|
jpayne@69
|
236
|
jpayne@69
|
237 >>> before = eastern.normalize(before)
|
jpayne@69
|
238 >>> before.strftime(fmt)
|
jpayne@69
|
239 '2002-10-27 01:50:00 EDT (-0400)'
|
jpayne@69
|
240
|
jpayne@69
|
241 The supported method of converting between timezones is to use
|
jpayne@69
|
242 datetime.astimezone(). Currently, normalize() also works:
|
jpayne@69
|
243
|
jpayne@69
|
244 >>> th = timezone('Asia/Bangkok')
|
jpayne@69
|
245 >>> am = timezone('Europe/Amsterdam')
|
jpayne@69
|
246 >>> dt = th.localize(datetime(2011, 5, 7, 1, 2, 3))
|
jpayne@69
|
247 >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'
|
jpayne@69
|
248 >>> am.normalize(dt).strftime(fmt)
|
jpayne@69
|
249 '2011-05-06 20:02:03 CEST (+0200)'
|
jpayne@69
|
250 '''
|
jpayne@69
|
251 if dt.tzinfo is None:
|
jpayne@69
|
252 raise ValueError('Naive time - no tzinfo set')
|
jpayne@69
|
253
|
jpayne@69
|
254 # Convert dt in localtime to UTC
|
jpayne@69
|
255 offset = dt.tzinfo._utcoffset
|
jpayne@69
|
256 dt = dt.replace(tzinfo=None)
|
jpayne@69
|
257 dt = dt - offset
|
jpayne@69
|
258 # convert it back, and return it
|
jpayne@69
|
259 return self.fromutc(dt)
|
jpayne@69
|
260
|
jpayne@69
|
261 def localize(self, dt, is_dst=False):
|
jpayne@69
|
262 '''Convert naive time to local time.
|
jpayne@69
|
263
|
jpayne@69
|
264 This method should be used to construct localtimes, rather
|
jpayne@69
|
265 than passing a tzinfo argument to a datetime constructor.
|
jpayne@69
|
266
|
jpayne@69
|
267 is_dst is used to determine the correct timezone in the ambigous
|
jpayne@69
|
268 period at the end of daylight saving time.
|
jpayne@69
|
269
|
jpayne@69
|
270 >>> from pytz import timezone
|
jpayne@69
|
271 >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'
|
jpayne@69
|
272 >>> amdam = timezone('Europe/Amsterdam')
|
jpayne@69
|
273 >>> dt = datetime(2004, 10, 31, 2, 0, 0)
|
jpayne@69
|
274 >>> loc_dt1 = amdam.localize(dt, is_dst=True)
|
jpayne@69
|
275 >>> loc_dt2 = amdam.localize(dt, is_dst=False)
|
jpayne@69
|
276 >>> loc_dt1.strftime(fmt)
|
jpayne@69
|
277 '2004-10-31 02:00:00 CEST (+0200)'
|
jpayne@69
|
278 >>> loc_dt2.strftime(fmt)
|
jpayne@69
|
279 '2004-10-31 02:00:00 CET (+0100)'
|
jpayne@69
|
280 >>> str(loc_dt2 - loc_dt1)
|
jpayne@69
|
281 '1:00:00'
|
jpayne@69
|
282
|
jpayne@69
|
283 Use is_dst=None to raise an AmbiguousTimeError for ambiguous
|
jpayne@69
|
284 times at the end of daylight saving time
|
jpayne@69
|
285
|
jpayne@69
|
286 >>> try:
|
jpayne@69
|
287 ... loc_dt1 = amdam.localize(dt, is_dst=None)
|
jpayne@69
|
288 ... except AmbiguousTimeError:
|
jpayne@69
|
289 ... print('Ambiguous')
|
jpayne@69
|
290 Ambiguous
|
jpayne@69
|
291
|
jpayne@69
|
292 is_dst defaults to False
|
jpayne@69
|
293
|
jpayne@69
|
294 >>> amdam.localize(dt) == amdam.localize(dt, False)
|
jpayne@69
|
295 True
|
jpayne@69
|
296
|
jpayne@69
|
297 is_dst is also used to determine the correct timezone in the
|
jpayne@69
|
298 wallclock times jumped over at the start of daylight saving time.
|
jpayne@69
|
299
|
jpayne@69
|
300 >>> pacific = timezone('US/Pacific')
|
jpayne@69
|
301 >>> dt = datetime(2008, 3, 9, 2, 0, 0)
|
jpayne@69
|
302 >>> ploc_dt1 = pacific.localize(dt, is_dst=True)
|
jpayne@69
|
303 >>> ploc_dt2 = pacific.localize(dt, is_dst=False)
|
jpayne@69
|
304 >>> ploc_dt1.strftime(fmt)
|
jpayne@69
|
305 '2008-03-09 02:00:00 PDT (-0700)'
|
jpayne@69
|
306 >>> ploc_dt2.strftime(fmt)
|
jpayne@69
|
307 '2008-03-09 02:00:00 PST (-0800)'
|
jpayne@69
|
308 >>> str(ploc_dt2 - ploc_dt1)
|
jpayne@69
|
309 '1:00:00'
|
jpayne@69
|
310
|
jpayne@69
|
311 Use is_dst=None to raise a NonExistentTimeError for these skipped
|
jpayne@69
|
312 times.
|
jpayne@69
|
313
|
jpayne@69
|
314 >>> try:
|
jpayne@69
|
315 ... loc_dt1 = pacific.localize(dt, is_dst=None)
|
jpayne@69
|
316 ... except NonExistentTimeError:
|
jpayne@69
|
317 ... print('Non-existent')
|
jpayne@69
|
318 Non-existent
|
jpayne@69
|
319 '''
|
jpayne@69
|
320 if dt.tzinfo is not None:
|
jpayne@69
|
321 raise ValueError('Not naive datetime (tzinfo is already set)')
|
jpayne@69
|
322
|
jpayne@69
|
323 # Find the two best possibilities.
|
jpayne@69
|
324 possible_loc_dt = set()
|
jpayne@69
|
325 for delta in [timedelta(days=-1), timedelta(days=1)]:
|
jpayne@69
|
326 loc_dt = dt + delta
|
jpayne@69
|
327 idx = max(0, bisect_right(
|
jpayne@69
|
328 self._utc_transition_times, loc_dt) - 1)
|
jpayne@69
|
329 inf = self._transition_info[idx]
|
jpayne@69
|
330 tzinfo = self._tzinfos[inf]
|
jpayne@69
|
331 loc_dt = tzinfo.normalize(dt.replace(tzinfo=tzinfo))
|
jpayne@69
|
332 if loc_dt.replace(tzinfo=None) == dt:
|
jpayne@69
|
333 possible_loc_dt.add(loc_dt)
|
jpayne@69
|
334
|
jpayne@69
|
335 if len(possible_loc_dt) == 1:
|
jpayne@69
|
336 return possible_loc_dt.pop()
|
jpayne@69
|
337
|
jpayne@69
|
338 # If there are no possibly correct timezones, we are attempting
|
jpayne@69
|
339 # to convert a time that never happened - the time period jumped
|
jpayne@69
|
340 # during the start-of-DST transition period.
|
jpayne@69
|
341 if len(possible_loc_dt) == 0:
|
jpayne@69
|
342 # If we refuse to guess, raise an exception.
|
jpayne@69
|
343 if is_dst is None:
|
jpayne@69
|
344 raise NonExistentTimeError(dt)
|
jpayne@69
|
345
|
jpayne@69
|
346 # If we are forcing the pre-DST side of the DST transition, we
|
jpayne@69
|
347 # obtain the correct timezone by winding the clock forward a few
|
jpayne@69
|
348 # hours.
|
jpayne@69
|
349 elif is_dst:
|
jpayne@69
|
350 return self.localize(
|
jpayne@69
|
351 dt + timedelta(hours=6), is_dst=True) - timedelta(hours=6)
|
jpayne@69
|
352
|
jpayne@69
|
353 # If we are forcing the post-DST side of the DST transition, we
|
jpayne@69
|
354 # obtain the correct timezone by winding the clock back.
|
jpayne@69
|
355 else:
|
jpayne@69
|
356 return self.localize(
|
jpayne@69
|
357 dt - timedelta(hours=6),
|
jpayne@69
|
358 is_dst=False) + timedelta(hours=6)
|
jpayne@69
|
359
|
jpayne@69
|
360 # If we get this far, we have multiple possible timezones - this
|
jpayne@69
|
361 # is an ambiguous case occurring during the end-of-DST transition.
|
jpayne@69
|
362
|
jpayne@69
|
363 # If told to be strict, raise an exception since we have an
|
jpayne@69
|
364 # ambiguous case
|
jpayne@69
|
365 if is_dst is None:
|
jpayne@69
|
366 raise AmbiguousTimeError(dt)
|
jpayne@69
|
367
|
jpayne@69
|
368 # Filter out the possiblilities that don't match the requested
|
jpayne@69
|
369 # is_dst
|
jpayne@69
|
370 filtered_possible_loc_dt = [
|
jpayne@69
|
371 p for p in possible_loc_dt if bool(p.tzinfo._dst) == is_dst
|
jpayne@69
|
372 ]
|
jpayne@69
|
373
|
jpayne@69
|
374 # Hopefully we only have one possibility left. Return it.
|
jpayne@69
|
375 if len(filtered_possible_loc_dt) == 1:
|
jpayne@69
|
376 return filtered_possible_loc_dt[0]
|
jpayne@69
|
377
|
jpayne@69
|
378 if len(filtered_possible_loc_dt) == 0:
|
jpayne@69
|
379 filtered_possible_loc_dt = list(possible_loc_dt)
|
jpayne@69
|
380
|
jpayne@69
|
381 # If we get this far, we have in a wierd timezone transition
|
jpayne@69
|
382 # where the clocks have been wound back but is_dst is the same
|
jpayne@69
|
383 # in both (eg. Europe/Warsaw 1915 when they switched to CET).
|
jpayne@69
|
384 # At this point, we just have to guess unless we allow more
|
jpayne@69
|
385 # hints to be passed in (such as the UTC offset or abbreviation),
|
jpayne@69
|
386 # but that is just getting silly.
|
jpayne@69
|
387 #
|
jpayne@69
|
388 # Choose the earliest (by UTC) applicable timezone if is_dst=True
|
jpayne@69
|
389 # Choose the latest (by UTC) applicable timezone if is_dst=False
|
jpayne@69
|
390 # i.e., behave like end-of-DST transition
|
jpayne@69
|
391 dates = {} # utc -> local
|
jpayne@69
|
392 for local_dt in filtered_possible_loc_dt:
|
jpayne@69
|
393 utc_time = (
|
jpayne@69
|
394 local_dt.replace(tzinfo=None) - local_dt.tzinfo._utcoffset)
|
jpayne@69
|
395 assert utc_time not in dates
|
jpayne@69
|
396 dates[utc_time] = local_dt
|
jpayne@69
|
397 return dates[[min, max][not is_dst](dates)]
|
jpayne@69
|
398
|
jpayne@69
|
399 def utcoffset(self, dt, is_dst=None):
|
jpayne@69
|
400 '''See datetime.tzinfo.utcoffset
|
jpayne@69
|
401
|
jpayne@69
|
402 The is_dst parameter may be used to remove ambiguity during DST
|
jpayne@69
|
403 transitions.
|
jpayne@69
|
404
|
jpayne@69
|
405 >>> from pytz import timezone
|
jpayne@69
|
406 >>> tz = timezone('America/St_Johns')
|
jpayne@69
|
407 >>> ambiguous = datetime(2009, 10, 31, 23, 30)
|
jpayne@69
|
408
|
jpayne@69
|
409 >>> str(tz.utcoffset(ambiguous, is_dst=False))
|
jpayne@69
|
410 '-1 day, 20:30:00'
|
jpayne@69
|
411
|
jpayne@69
|
412 >>> str(tz.utcoffset(ambiguous, is_dst=True))
|
jpayne@69
|
413 '-1 day, 21:30:00'
|
jpayne@69
|
414
|
jpayne@69
|
415 >>> try:
|
jpayne@69
|
416 ... tz.utcoffset(ambiguous)
|
jpayne@69
|
417 ... except AmbiguousTimeError:
|
jpayne@69
|
418 ... print('Ambiguous')
|
jpayne@69
|
419 Ambiguous
|
jpayne@69
|
420
|
jpayne@69
|
421 '''
|
jpayne@69
|
422 if dt is None:
|
jpayne@69
|
423 return None
|
jpayne@69
|
424 elif dt.tzinfo is not self:
|
jpayne@69
|
425 dt = self.localize(dt, is_dst)
|
jpayne@69
|
426 return dt.tzinfo._utcoffset
|
jpayne@69
|
427 else:
|
jpayne@69
|
428 return self._utcoffset
|
jpayne@69
|
429
|
jpayne@69
|
430 def dst(self, dt, is_dst=None):
|
jpayne@69
|
431 '''See datetime.tzinfo.dst
|
jpayne@69
|
432
|
jpayne@69
|
433 The is_dst parameter may be used to remove ambiguity during DST
|
jpayne@69
|
434 transitions.
|
jpayne@69
|
435
|
jpayne@69
|
436 >>> from pytz import timezone
|
jpayne@69
|
437 >>> tz = timezone('America/St_Johns')
|
jpayne@69
|
438
|
jpayne@69
|
439 >>> normal = datetime(2009, 9, 1)
|
jpayne@69
|
440
|
jpayne@69
|
441 >>> str(tz.dst(normal))
|
jpayne@69
|
442 '1:00:00'
|
jpayne@69
|
443 >>> str(tz.dst(normal, is_dst=False))
|
jpayne@69
|
444 '1:00:00'
|
jpayne@69
|
445 >>> str(tz.dst(normal, is_dst=True))
|
jpayne@69
|
446 '1:00:00'
|
jpayne@69
|
447
|
jpayne@69
|
448 >>> ambiguous = datetime(2009, 10, 31, 23, 30)
|
jpayne@69
|
449
|
jpayne@69
|
450 >>> str(tz.dst(ambiguous, is_dst=False))
|
jpayne@69
|
451 '0:00:00'
|
jpayne@69
|
452 >>> str(tz.dst(ambiguous, is_dst=True))
|
jpayne@69
|
453 '1:00:00'
|
jpayne@69
|
454 >>> try:
|
jpayne@69
|
455 ... tz.dst(ambiguous)
|
jpayne@69
|
456 ... except AmbiguousTimeError:
|
jpayne@69
|
457 ... print('Ambiguous')
|
jpayne@69
|
458 Ambiguous
|
jpayne@69
|
459
|
jpayne@69
|
460 '''
|
jpayne@69
|
461 if dt is None:
|
jpayne@69
|
462 return None
|
jpayne@69
|
463 elif dt.tzinfo is not self:
|
jpayne@69
|
464 dt = self.localize(dt, is_dst)
|
jpayne@69
|
465 return dt.tzinfo._dst
|
jpayne@69
|
466 else:
|
jpayne@69
|
467 return self._dst
|
jpayne@69
|
468
|
jpayne@69
|
469 def tzname(self, dt, is_dst=None):
|
jpayne@69
|
470 '''See datetime.tzinfo.tzname
|
jpayne@69
|
471
|
jpayne@69
|
472 The is_dst parameter may be used to remove ambiguity during DST
|
jpayne@69
|
473 transitions.
|
jpayne@69
|
474
|
jpayne@69
|
475 >>> from pytz import timezone
|
jpayne@69
|
476 >>> tz = timezone('America/St_Johns')
|
jpayne@69
|
477
|
jpayne@69
|
478 >>> normal = datetime(2009, 9, 1)
|
jpayne@69
|
479
|
jpayne@69
|
480 >>> tz.tzname(normal)
|
jpayne@69
|
481 'NDT'
|
jpayne@69
|
482 >>> tz.tzname(normal, is_dst=False)
|
jpayne@69
|
483 'NDT'
|
jpayne@69
|
484 >>> tz.tzname(normal, is_dst=True)
|
jpayne@69
|
485 'NDT'
|
jpayne@69
|
486
|
jpayne@69
|
487 >>> ambiguous = datetime(2009, 10, 31, 23, 30)
|
jpayne@69
|
488
|
jpayne@69
|
489 >>> tz.tzname(ambiguous, is_dst=False)
|
jpayne@69
|
490 'NST'
|
jpayne@69
|
491 >>> tz.tzname(ambiguous, is_dst=True)
|
jpayne@69
|
492 'NDT'
|
jpayne@69
|
493 >>> try:
|
jpayne@69
|
494 ... tz.tzname(ambiguous)
|
jpayne@69
|
495 ... except AmbiguousTimeError:
|
jpayne@69
|
496 ... print('Ambiguous')
|
jpayne@69
|
497 Ambiguous
|
jpayne@69
|
498 '''
|
jpayne@69
|
499 if dt is None:
|
jpayne@69
|
500 return self.zone
|
jpayne@69
|
501 elif dt.tzinfo is not self:
|
jpayne@69
|
502 dt = self.localize(dt, is_dst)
|
jpayne@69
|
503 return dt.tzinfo._tzname
|
jpayne@69
|
504 else:
|
jpayne@69
|
505 return self._tzname
|
jpayne@69
|
506
|
jpayne@69
|
507 def __repr__(self):
|
jpayne@69
|
508 if self._dst:
|
jpayne@69
|
509 dst = 'DST'
|
jpayne@69
|
510 else:
|
jpayne@69
|
511 dst = 'STD'
|
jpayne@69
|
512 if self._utcoffset > _notime:
|
jpayne@69
|
513 return '<DstTzInfo %r %s+%s %s>' % (
|
jpayne@69
|
514 self.zone, self._tzname, self._utcoffset, dst
|
jpayne@69
|
515 )
|
jpayne@69
|
516 else:
|
jpayne@69
|
517 return '<DstTzInfo %r %s%s %s>' % (
|
jpayne@69
|
518 self.zone, self._tzname, self._utcoffset, dst
|
jpayne@69
|
519 )
|
jpayne@69
|
520
|
jpayne@69
|
521 def __reduce__(self):
|
jpayne@69
|
522 # Special pickle to zone remains a singleton and to cope with
|
jpayne@69
|
523 # database changes.
|
jpayne@69
|
524 return pytz._p, (
|
jpayne@69
|
525 self.zone,
|
jpayne@69
|
526 _to_seconds(self._utcoffset),
|
jpayne@69
|
527 _to_seconds(self._dst),
|
jpayne@69
|
528 self._tzname
|
jpayne@69
|
529 )
|
jpayne@69
|
530
|
jpayne@69
|
531
|
jpayne@69
|
532 def unpickler(zone, utcoffset=None, dstoffset=None, tzname=None):
|
jpayne@69
|
533 """Factory function for unpickling pytz tzinfo instances.
|
jpayne@69
|
534
|
jpayne@69
|
535 This is shared for both StaticTzInfo and DstTzInfo instances, because
|
jpayne@69
|
536 database changes could cause a zones implementation to switch between
|
jpayne@69
|
537 these two base classes and we can't break pickles on a pytz version
|
jpayne@69
|
538 upgrade.
|
jpayne@69
|
539 """
|
jpayne@69
|
540 # Raises a KeyError if zone no longer exists, which should never happen
|
jpayne@69
|
541 # and would be a bug.
|
jpayne@69
|
542 tz = pytz.timezone(zone)
|
jpayne@69
|
543
|
jpayne@69
|
544 # A StaticTzInfo - just return it
|
jpayne@69
|
545 if utcoffset is None:
|
jpayne@69
|
546 return tz
|
jpayne@69
|
547
|
jpayne@69
|
548 # This pickle was created from a DstTzInfo. We need to
|
jpayne@69
|
549 # determine which of the list of tzinfo instances for this zone
|
jpayne@69
|
550 # to use in order to restore the state of any datetime instances using
|
jpayne@69
|
551 # it correctly.
|
jpayne@69
|
552 utcoffset = memorized_timedelta(utcoffset)
|
jpayne@69
|
553 dstoffset = memorized_timedelta(dstoffset)
|
jpayne@69
|
554 try:
|
jpayne@69
|
555 return tz._tzinfos[(utcoffset, dstoffset, tzname)]
|
jpayne@69
|
556 except KeyError:
|
jpayne@69
|
557 # The particular state requested in this timezone no longer exists.
|
jpayne@69
|
558 # This indicates a corrupt pickle, or the timezone database has been
|
jpayne@69
|
559 # corrected violently enough to make this particular
|
jpayne@69
|
560 # (utcoffset,dstoffset) no longer exist in the zone, or the
|
jpayne@69
|
561 # abbreviation has been changed.
|
jpayne@69
|
562 pass
|
jpayne@69
|
563
|
jpayne@69
|
564 # See if we can find an entry differing only by tzname. Abbreviations
|
jpayne@69
|
565 # get changed from the initial guess by the database maintainers to
|
jpayne@69
|
566 # match reality when this information is discovered.
|
jpayne@69
|
567 for localized_tz in tz._tzinfos.values():
|
jpayne@69
|
568 if (localized_tz._utcoffset == utcoffset and
|
jpayne@69
|
569 localized_tz._dst == dstoffset):
|
jpayne@69
|
570 return localized_tz
|
jpayne@69
|
571
|
jpayne@69
|
572 # This (utcoffset, dstoffset) information has been removed from the
|
jpayne@69
|
573 # zone. Add it back. This might occur when the database maintainers have
|
jpayne@69
|
574 # corrected incorrect information. datetime instances using this
|
jpayne@69
|
575 # incorrect information will continue to do so, exactly as they were
|
jpayne@69
|
576 # before being pickled. This is purely an overly paranoid safety net - I
|
jpayne@69
|
577 # doubt this will ever been needed in real life.
|
jpayne@69
|
578 inf = (utcoffset, dstoffset, tzname)
|
jpayne@69
|
579 tz._tzinfos[inf] = tz.__class__(inf, tz._tzinfos)
|
jpayne@69
|
580 return tz._tzinfos[inf]
|