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