Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
allendowney
GitHub Repository: allendowney/cpython
Path: blob/main/Lib/_pydatetime.py
12 views
1
"""Concrete date/time and related types.
2
3
See http://www.iana.org/time-zones/repository/tz-link.html for
4
time zone and DST data sources.
5
"""
6
7
__all__ = ("date", "datetime", "time", "timedelta", "timezone", "tzinfo",
8
"MINYEAR", "MAXYEAR", "UTC")
9
10
11
import time as _time
12
import math as _math
13
import sys
14
from operator import index as _index
15
16
def _cmp(x, y):
17
return 0 if x == y else 1 if x > y else -1
18
19
def _get_class_module(self):
20
module_name = self.__class__.__module__
21
if module_name == '_pydatetime':
22
return 'datetime'
23
else:
24
return module_name
25
26
MINYEAR = 1
27
MAXYEAR = 9999
28
_MAXORDINAL = 3652059 # date.max.toordinal()
29
30
# Utility functions, adapted from Python's Demo/classes/Dates.py, which
31
# also assumes the current Gregorian calendar indefinitely extended in
32
# both directions. Difference: Dates.py calls January 1 of year 0 day
33
# number 1. The code here calls January 1 of year 1 day number 1. This is
34
# to match the definition of the "proleptic Gregorian" calendar in Dershowitz
35
# and Reingold's "Calendrical Calculations", where it's the base calendar
36
# for all computations. See the book for algorithms for converting between
37
# proleptic Gregorian ordinals and many other calendar systems.
38
39
# -1 is a placeholder for indexing purposes.
40
_DAYS_IN_MONTH = [-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
41
42
_DAYS_BEFORE_MONTH = [-1] # -1 is a placeholder for indexing purposes.
43
dbm = 0
44
for dim in _DAYS_IN_MONTH[1:]:
45
_DAYS_BEFORE_MONTH.append(dbm)
46
dbm += dim
47
del dbm, dim
48
49
def _is_leap(year):
50
"year -> 1 if leap year, else 0."
51
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
52
53
def _days_before_year(year):
54
"year -> number of days before January 1st of year."
55
y = year - 1
56
return y*365 + y//4 - y//100 + y//400
57
58
def _days_in_month(year, month):
59
"year, month -> number of days in that month in that year."
60
assert 1 <= month <= 12, month
61
if month == 2 and _is_leap(year):
62
return 29
63
return _DAYS_IN_MONTH[month]
64
65
def _days_before_month(year, month):
66
"year, month -> number of days in year preceding first day of month."
67
assert 1 <= month <= 12, 'month must be in 1..12'
68
return _DAYS_BEFORE_MONTH[month] + (month > 2 and _is_leap(year))
69
70
def _ymd2ord(year, month, day):
71
"year, month, day -> ordinal, considering 01-Jan-0001 as day 1."
72
assert 1 <= month <= 12, 'month must be in 1..12'
73
dim = _days_in_month(year, month)
74
assert 1 <= day <= dim, ('day must be in 1..%d' % dim)
75
return (_days_before_year(year) +
76
_days_before_month(year, month) +
77
day)
78
79
_DI400Y = _days_before_year(401) # number of days in 400 years
80
_DI100Y = _days_before_year(101) # " " " " 100 "
81
_DI4Y = _days_before_year(5) # " " " " 4 "
82
83
# A 4-year cycle has an extra leap day over what we'd get from pasting
84
# together 4 single years.
85
assert _DI4Y == 4 * 365 + 1
86
87
# Similarly, a 400-year cycle has an extra leap day over what we'd get from
88
# pasting together 4 100-year cycles.
89
assert _DI400Y == 4 * _DI100Y + 1
90
91
# OTOH, a 100-year cycle has one fewer leap day than we'd get from
92
# pasting together 25 4-year cycles.
93
assert _DI100Y == 25 * _DI4Y - 1
94
95
def _ord2ymd(n):
96
"ordinal -> (year, month, day), considering 01-Jan-0001 as day 1."
97
98
# n is a 1-based index, starting at 1-Jan-1. The pattern of leap years
99
# repeats exactly every 400 years. The basic strategy is to find the
100
# closest 400-year boundary at or before n, then work with the offset
101
# from that boundary to n. Life is much clearer if we subtract 1 from
102
# n first -- then the values of n at 400-year boundaries are exactly
103
# those divisible by _DI400Y:
104
#
105
# D M Y n n-1
106
# -- --- ---- ---------- ----------------
107
# 31 Dec -400 -_DI400Y -_DI400Y -1
108
# 1 Jan -399 -_DI400Y +1 -_DI400Y 400-year boundary
109
# ...
110
# 30 Dec 000 -1 -2
111
# 31 Dec 000 0 -1
112
# 1 Jan 001 1 0 400-year boundary
113
# 2 Jan 001 2 1
114
# 3 Jan 001 3 2
115
# ...
116
# 31 Dec 400 _DI400Y _DI400Y -1
117
# 1 Jan 401 _DI400Y +1 _DI400Y 400-year boundary
118
n -= 1
119
n400, n = divmod(n, _DI400Y)
120
year = n400 * 400 + 1 # ..., -399, 1, 401, ...
121
122
# Now n is the (non-negative) offset, in days, from January 1 of year, to
123
# the desired date. Now compute how many 100-year cycles precede n.
124
# Note that it's possible for n100 to equal 4! In that case 4 full
125
# 100-year cycles precede the desired day, which implies the desired
126
# day is December 31 at the end of a 400-year cycle.
127
n100, n = divmod(n, _DI100Y)
128
129
# Now compute how many 4-year cycles precede it.
130
n4, n = divmod(n, _DI4Y)
131
132
# And now how many single years. Again n1 can be 4, and again meaning
133
# that the desired day is December 31 at the end of the 4-year cycle.
134
n1, n = divmod(n, 365)
135
136
year += n100 * 100 + n4 * 4 + n1
137
if n1 == 4 or n100 == 4:
138
assert n == 0
139
return year-1, 12, 31
140
141
# Now the year is correct, and n is the offset from January 1. We find
142
# the month via an estimate that's either exact or one too large.
143
leapyear = n1 == 3 and (n4 != 24 or n100 == 3)
144
assert leapyear == _is_leap(year)
145
month = (n + 50) >> 5
146
preceding = _DAYS_BEFORE_MONTH[month] + (month > 2 and leapyear)
147
if preceding > n: # estimate is too large
148
month -= 1
149
preceding -= _DAYS_IN_MONTH[month] + (month == 2 and leapyear)
150
n -= preceding
151
assert 0 <= n < _days_in_month(year, month)
152
153
# Now the year and month are correct, and n is the offset from the
154
# start of that month: we're done!
155
return year, month, n+1
156
157
# Month and day names. For localized versions, see the calendar module.
158
_MONTHNAMES = [None, "Jan", "Feb", "Mar", "Apr", "May", "Jun",
159
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
160
_DAYNAMES = [None, "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
161
162
163
def _build_struct_time(y, m, d, hh, mm, ss, dstflag):
164
wday = (_ymd2ord(y, m, d) + 6) % 7
165
dnum = _days_before_month(y, m) + d
166
return _time.struct_time((y, m, d, hh, mm, ss, wday, dnum, dstflag))
167
168
def _format_time(hh, mm, ss, us, timespec='auto'):
169
specs = {
170
'hours': '{:02d}',
171
'minutes': '{:02d}:{:02d}',
172
'seconds': '{:02d}:{:02d}:{:02d}',
173
'milliseconds': '{:02d}:{:02d}:{:02d}.{:03d}',
174
'microseconds': '{:02d}:{:02d}:{:02d}.{:06d}'
175
}
176
177
if timespec == 'auto':
178
# Skip trailing microseconds when us==0.
179
timespec = 'microseconds' if us else 'seconds'
180
elif timespec == 'milliseconds':
181
us //= 1000
182
try:
183
fmt = specs[timespec]
184
except KeyError:
185
raise ValueError('Unknown timespec value')
186
else:
187
return fmt.format(hh, mm, ss, us)
188
189
def _format_offset(off, sep=':'):
190
s = ''
191
if off is not None:
192
if off.days < 0:
193
sign = "-"
194
off = -off
195
else:
196
sign = "+"
197
hh, mm = divmod(off, timedelta(hours=1))
198
mm, ss = divmod(mm, timedelta(minutes=1))
199
s += "%s%02d%s%02d" % (sign, hh, sep, mm)
200
if ss or ss.microseconds:
201
s += "%s%02d" % (sep, ss.seconds)
202
203
if ss.microseconds:
204
s += '.%06d' % ss.microseconds
205
return s
206
207
# Correctly substitute for %z and %Z escapes in strftime formats.
208
def _wrap_strftime(object, format, timetuple):
209
# Don't call utcoffset() or tzname() unless actually needed.
210
freplace = None # the string to use for %f
211
zreplace = None # the string to use for %z
212
colonzreplace = None # the string to use for %:z
213
Zreplace = None # the string to use for %Z
214
215
# Scan format for %z, %:z and %Z escapes, replacing as needed.
216
newformat = []
217
push = newformat.append
218
i, n = 0, len(format)
219
while i < n:
220
ch = format[i]
221
i += 1
222
if ch == '%':
223
if i < n:
224
ch = format[i]
225
i += 1
226
if ch == 'f':
227
if freplace is None:
228
freplace = '%06d' % getattr(object,
229
'microsecond', 0)
230
newformat.append(freplace)
231
elif ch == 'z':
232
if zreplace is None:
233
if hasattr(object, "utcoffset"):
234
zreplace = _format_offset(object.utcoffset(), sep="")
235
else:
236
zreplace = ""
237
assert '%' not in zreplace
238
newformat.append(zreplace)
239
elif ch == ':':
240
if i < n:
241
ch2 = format[i]
242
i += 1
243
if ch2 == 'z':
244
if colonzreplace is None:
245
if hasattr(object, "utcoffset"):
246
colonzreplace = _format_offset(object.utcoffset(), sep=":")
247
else:
248
colonzreplace = ""
249
assert '%' not in colonzreplace
250
newformat.append(colonzreplace)
251
else:
252
push('%')
253
push(ch)
254
push(ch2)
255
elif ch == 'Z':
256
if Zreplace is None:
257
Zreplace = ""
258
if hasattr(object, "tzname"):
259
s = object.tzname()
260
if s is not None:
261
# strftime is going to have at this: escape %
262
Zreplace = s.replace('%', '%%')
263
newformat.append(Zreplace)
264
else:
265
push('%')
266
push(ch)
267
else:
268
push('%')
269
else:
270
push(ch)
271
newformat = "".join(newformat)
272
return _time.strftime(newformat, timetuple)
273
274
# Helpers for parsing the result of isoformat()
275
def _is_ascii_digit(c):
276
return c in "0123456789"
277
278
def _find_isoformat_datetime_separator(dtstr):
279
# See the comment in _datetimemodule.c:_find_isoformat_datetime_separator
280
len_dtstr = len(dtstr)
281
if len_dtstr == 7:
282
return 7
283
284
assert len_dtstr > 7
285
date_separator = "-"
286
week_indicator = "W"
287
288
if dtstr[4] == date_separator:
289
if dtstr[5] == week_indicator:
290
if len_dtstr < 8:
291
raise ValueError("Invalid ISO string")
292
if len_dtstr > 8 and dtstr[8] == date_separator:
293
if len_dtstr == 9:
294
raise ValueError("Invalid ISO string")
295
if len_dtstr > 10 and _is_ascii_digit(dtstr[10]):
296
# This is as far as we need to resolve the ambiguity for
297
# the moment - if we have YYYY-Www-##, the separator is
298
# either a hyphen at 8 or a number at 10.
299
#
300
# We'll assume it's a hyphen at 8 because it's way more
301
# likely that someone will use a hyphen as a separator than
302
# a number, but at this point it's really best effort
303
# because this is an extension of the spec anyway.
304
# TODO(pganssle): Document this
305
return 8
306
return 10
307
else:
308
# YYYY-Www (8)
309
return 8
310
else:
311
# YYYY-MM-DD (10)
312
return 10
313
else:
314
if dtstr[4] == week_indicator:
315
# YYYYWww (7) or YYYYWwwd (8)
316
idx = 7
317
while idx < len_dtstr:
318
if not _is_ascii_digit(dtstr[idx]):
319
break
320
idx += 1
321
322
if idx < 9:
323
return idx
324
325
if idx % 2 == 0:
326
# If the index of the last number is even, it's YYYYWwwd
327
return 7
328
else:
329
return 8
330
else:
331
# YYYYMMDD (8)
332
return 8
333
334
335
def _parse_isoformat_date(dtstr):
336
# It is assumed that this is an ASCII-only string of lengths 7, 8 or 10,
337
# see the comment on Modules/_datetimemodule.c:_find_isoformat_datetime_separator
338
assert len(dtstr) in (7, 8, 10)
339
year = int(dtstr[0:4])
340
has_sep = dtstr[4] == '-'
341
342
pos = 4 + has_sep
343
if dtstr[pos:pos + 1] == "W":
344
# YYYY-?Www-?D?
345
pos += 1
346
weekno = int(dtstr[pos:pos + 2])
347
pos += 2
348
349
dayno = 1
350
if len(dtstr) > pos:
351
if (dtstr[pos:pos + 1] == '-') != has_sep:
352
raise ValueError("Inconsistent use of dash separator")
353
354
pos += has_sep
355
356
dayno = int(dtstr[pos:pos + 1])
357
358
return list(_isoweek_to_gregorian(year, weekno, dayno))
359
else:
360
month = int(dtstr[pos:pos + 2])
361
pos += 2
362
if (dtstr[pos:pos + 1] == "-") != has_sep:
363
raise ValueError("Inconsistent use of dash separator")
364
365
pos += has_sep
366
day = int(dtstr[pos:pos + 2])
367
368
return [year, month, day]
369
370
371
_FRACTION_CORRECTION = [100000, 10000, 1000, 100, 10]
372
373
374
def _parse_hh_mm_ss_ff(tstr):
375
# Parses things of the form HH[:?MM[:?SS[{.,}fff[fff]]]]
376
len_str = len(tstr)
377
378
time_comps = [0, 0, 0, 0]
379
pos = 0
380
for comp in range(0, 3):
381
if (len_str - pos) < 2:
382
raise ValueError("Incomplete time component")
383
384
time_comps[comp] = int(tstr[pos:pos+2])
385
386
pos += 2
387
next_char = tstr[pos:pos+1]
388
389
if comp == 0:
390
has_sep = next_char == ':'
391
392
if not next_char or comp >= 2:
393
break
394
395
if has_sep and next_char != ':':
396
raise ValueError("Invalid time separator: %c" % next_char)
397
398
pos += has_sep
399
400
if pos < len_str:
401
if tstr[pos] not in '.,':
402
raise ValueError("Invalid microsecond component")
403
else:
404
pos += 1
405
406
len_remainder = len_str - pos
407
408
if len_remainder >= 6:
409
to_parse = 6
410
else:
411
to_parse = len_remainder
412
413
time_comps[3] = int(tstr[pos:(pos+to_parse)])
414
if to_parse < 6:
415
time_comps[3] *= _FRACTION_CORRECTION[to_parse-1]
416
if (len_remainder > to_parse
417
and not all(map(_is_ascii_digit, tstr[(pos+to_parse):]))):
418
raise ValueError("Non-digit values in unparsed fraction")
419
420
return time_comps
421
422
def _parse_isoformat_time(tstr):
423
# Format supported is HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]
424
len_str = len(tstr)
425
if len_str < 2:
426
raise ValueError("Isoformat time too short")
427
428
# This is equivalent to re.search('[+-Z]', tstr), but faster
429
tz_pos = (tstr.find('-') + 1 or tstr.find('+') + 1 or tstr.find('Z') + 1)
430
timestr = tstr[:tz_pos-1] if tz_pos > 0 else tstr
431
432
time_comps = _parse_hh_mm_ss_ff(timestr)
433
434
tzi = None
435
if tz_pos == len_str and tstr[-1] == 'Z':
436
tzi = timezone.utc
437
elif tz_pos > 0:
438
tzstr = tstr[tz_pos:]
439
440
# Valid time zone strings are:
441
# HH len: 2
442
# HHMM len: 4
443
# HH:MM len: 5
444
# HHMMSS len: 6
445
# HHMMSS.f+ len: 7+
446
# HH:MM:SS len: 8
447
# HH:MM:SS.f+ len: 10+
448
449
if len(tzstr) in (0, 1, 3):
450
raise ValueError("Malformed time zone string")
451
452
tz_comps = _parse_hh_mm_ss_ff(tzstr)
453
454
if all(x == 0 for x in tz_comps):
455
tzi = timezone.utc
456
else:
457
tzsign = -1 if tstr[tz_pos - 1] == '-' else 1
458
459
td = timedelta(hours=tz_comps[0], minutes=tz_comps[1],
460
seconds=tz_comps[2], microseconds=tz_comps[3])
461
462
tzi = timezone(tzsign * td)
463
464
time_comps.append(tzi)
465
466
return time_comps
467
468
# tuple[int, int, int] -> tuple[int, int, int] version of date.fromisocalendar
469
def _isoweek_to_gregorian(year, week, day):
470
# Year is bounded this way because 9999-12-31 is (9999, 52, 5)
471
if not MINYEAR <= year <= MAXYEAR:
472
raise ValueError(f"Year is out of range: {year}")
473
474
if not 0 < week < 53:
475
out_of_range = True
476
477
if week == 53:
478
# ISO years have 53 weeks in them on years starting with a
479
# Thursday and leap years starting on a Wednesday
480
first_weekday = _ymd2ord(year, 1, 1) % 7
481
if (first_weekday == 4 or (first_weekday == 3 and
482
_is_leap(year))):
483
out_of_range = False
484
485
if out_of_range:
486
raise ValueError(f"Invalid week: {week}")
487
488
if not 0 < day < 8:
489
raise ValueError(f"Invalid weekday: {day} (range is [1, 7])")
490
491
# Now compute the offset from (Y, 1, 1) in days:
492
day_offset = (week - 1) * 7 + (day - 1)
493
494
# Calculate the ordinal day for monday, week 1
495
day_1 = _isoweek1monday(year)
496
ord_day = day_1 + day_offset
497
498
return _ord2ymd(ord_day)
499
500
501
# Just raise TypeError if the arg isn't None or a string.
502
def _check_tzname(name):
503
if name is not None and not isinstance(name, str):
504
raise TypeError("tzinfo.tzname() must return None or string, "
505
"not '%s'" % type(name))
506
507
# name is the offset-producing method, "utcoffset" or "dst".
508
# offset is what it returned.
509
# If offset isn't None or timedelta, raises TypeError.
510
# If offset is None, returns None.
511
# Else offset is checked for being in range.
512
# If it is, its integer value is returned. Else ValueError is raised.
513
def _check_utc_offset(name, offset):
514
assert name in ("utcoffset", "dst")
515
if offset is None:
516
return
517
if not isinstance(offset, timedelta):
518
raise TypeError("tzinfo.%s() must return None "
519
"or timedelta, not '%s'" % (name, type(offset)))
520
if not -timedelta(1) < offset < timedelta(1):
521
raise ValueError("%s()=%s, must be strictly between "
522
"-timedelta(hours=24) and timedelta(hours=24)" %
523
(name, offset))
524
525
def _check_date_fields(year, month, day):
526
year = _index(year)
527
month = _index(month)
528
day = _index(day)
529
if not MINYEAR <= year <= MAXYEAR:
530
raise ValueError('year must be in %d..%d' % (MINYEAR, MAXYEAR), year)
531
if not 1 <= month <= 12:
532
raise ValueError('month must be in 1..12', month)
533
dim = _days_in_month(year, month)
534
if not 1 <= day <= dim:
535
raise ValueError('day must be in 1..%d' % dim, day)
536
return year, month, day
537
538
def _check_time_fields(hour, minute, second, microsecond, fold):
539
hour = _index(hour)
540
minute = _index(minute)
541
second = _index(second)
542
microsecond = _index(microsecond)
543
if not 0 <= hour <= 23:
544
raise ValueError('hour must be in 0..23', hour)
545
if not 0 <= minute <= 59:
546
raise ValueError('minute must be in 0..59', minute)
547
if not 0 <= second <= 59:
548
raise ValueError('second must be in 0..59', second)
549
if not 0 <= microsecond <= 999999:
550
raise ValueError('microsecond must be in 0..999999', microsecond)
551
if fold not in (0, 1):
552
raise ValueError('fold must be either 0 or 1', fold)
553
return hour, minute, second, microsecond, fold
554
555
def _check_tzinfo_arg(tz):
556
if tz is not None and not isinstance(tz, tzinfo):
557
raise TypeError("tzinfo argument must be None or of a tzinfo subclass")
558
559
def _cmperror(x, y):
560
raise TypeError("can't compare '%s' to '%s'" % (
561
type(x).__name__, type(y).__name__))
562
563
def _divide_and_round(a, b):
564
"""divide a by b and round result to the nearest integer
565
566
When the ratio is exactly half-way between two integers,
567
the even integer is returned.
568
"""
569
# Based on the reference implementation for divmod_near
570
# in Objects/longobject.c.
571
q, r = divmod(a, b)
572
# round up if either r / b > 0.5, or r / b == 0.5 and q is odd.
573
# The expression r / b > 0.5 is equivalent to 2 * r > b if b is
574
# positive, 2 * r < b if b negative.
575
r *= 2
576
greater_than_half = r > b if b > 0 else r < b
577
if greater_than_half or r == b and q % 2 == 1:
578
q += 1
579
580
return q
581
582
583
class timedelta:
584
"""Represent the difference between two datetime objects.
585
586
Supported operators:
587
588
- add, subtract timedelta
589
- unary plus, minus, abs
590
- compare to timedelta
591
- multiply, divide by int
592
593
In addition, datetime supports subtraction of two datetime objects
594
returning a timedelta, and addition or subtraction of a datetime
595
and a timedelta giving a datetime.
596
597
Representation: (days, seconds, microseconds).
598
"""
599
# The representation of (days, seconds, microseconds) was chosen
600
# arbitrarily; the exact rationale originally specified in the docstring
601
# was "Because I felt like it."
602
603
__slots__ = '_days', '_seconds', '_microseconds', '_hashcode'
604
605
def __new__(cls, days=0, seconds=0, microseconds=0,
606
milliseconds=0, minutes=0, hours=0, weeks=0):
607
# Doing this efficiently and accurately in C is going to be difficult
608
# and error-prone, due to ubiquitous overflow possibilities, and that
609
# C double doesn't have enough bits of precision to represent
610
# microseconds over 10K years faithfully. The code here tries to make
611
# explicit where go-fast assumptions can be relied on, in order to
612
# guide the C implementation; it's way more convoluted than speed-
613
# ignoring auto-overflow-to-long idiomatic Python could be.
614
615
# XXX Check that all inputs are ints or floats.
616
617
# Final values, all integer.
618
# s and us fit in 32-bit signed ints; d isn't bounded.
619
d = s = us = 0
620
621
# Normalize everything to days, seconds, microseconds.
622
days += weeks*7
623
seconds += minutes*60 + hours*3600
624
microseconds += milliseconds*1000
625
626
# Get rid of all fractions, and normalize s and us.
627
# Take a deep breath <wink>.
628
if isinstance(days, float):
629
dayfrac, days = _math.modf(days)
630
daysecondsfrac, daysecondswhole = _math.modf(dayfrac * (24.*3600.))
631
assert daysecondswhole == int(daysecondswhole) # can't overflow
632
s = int(daysecondswhole)
633
assert days == int(days)
634
d = int(days)
635
else:
636
daysecondsfrac = 0.0
637
d = days
638
assert isinstance(daysecondsfrac, float)
639
assert abs(daysecondsfrac) <= 1.0
640
assert isinstance(d, int)
641
assert abs(s) <= 24 * 3600
642
# days isn't referenced again before redefinition
643
644
if isinstance(seconds, float):
645
secondsfrac, seconds = _math.modf(seconds)
646
assert seconds == int(seconds)
647
seconds = int(seconds)
648
secondsfrac += daysecondsfrac
649
assert abs(secondsfrac) <= 2.0
650
else:
651
secondsfrac = daysecondsfrac
652
# daysecondsfrac isn't referenced again
653
assert isinstance(secondsfrac, float)
654
assert abs(secondsfrac) <= 2.0
655
656
assert isinstance(seconds, int)
657
days, seconds = divmod(seconds, 24*3600)
658
d += days
659
s += int(seconds) # can't overflow
660
assert isinstance(s, int)
661
assert abs(s) <= 2 * 24 * 3600
662
# seconds isn't referenced again before redefinition
663
664
usdouble = secondsfrac * 1e6
665
assert abs(usdouble) < 2.1e6 # exact value not critical
666
# secondsfrac isn't referenced again
667
668
if isinstance(microseconds, float):
669
microseconds = round(microseconds + usdouble)
670
seconds, microseconds = divmod(microseconds, 1000000)
671
days, seconds = divmod(seconds, 24*3600)
672
d += days
673
s += seconds
674
else:
675
microseconds = int(microseconds)
676
seconds, microseconds = divmod(microseconds, 1000000)
677
days, seconds = divmod(seconds, 24*3600)
678
d += days
679
s += seconds
680
microseconds = round(microseconds + usdouble)
681
assert isinstance(s, int)
682
assert isinstance(microseconds, int)
683
assert abs(s) <= 3 * 24 * 3600
684
assert abs(microseconds) < 3.1e6
685
686
# Just a little bit of carrying possible for microseconds and seconds.
687
seconds, us = divmod(microseconds, 1000000)
688
s += seconds
689
days, s = divmod(s, 24*3600)
690
d += days
691
692
assert isinstance(d, int)
693
assert isinstance(s, int) and 0 <= s < 24*3600
694
assert isinstance(us, int) and 0 <= us < 1000000
695
696
if abs(d) > 999999999:
697
raise OverflowError("timedelta # of days is too large: %d" % d)
698
699
self = object.__new__(cls)
700
self._days = d
701
self._seconds = s
702
self._microseconds = us
703
self._hashcode = -1
704
return self
705
706
def __repr__(self):
707
args = []
708
if self._days:
709
args.append("days=%d" % self._days)
710
if self._seconds:
711
args.append("seconds=%d" % self._seconds)
712
if self._microseconds:
713
args.append("microseconds=%d" % self._microseconds)
714
if not args:
715
args.append('0')
716
return "%s.%s(%s)" % (_get_class_module(self),
717
self.__class__.__qualname__,
718
', '.join(args))
719
720
def __str__(self):
721
mm, ss = divmod(self._seconds, 60)
722
hh, mm = divmod(mm, 60)
723
s = "%d:%02d:%02d" % (hh, mm, ss)
724
if self._days:
725
def plural(n):
726
return n, abs(n) != 1 and "s" or ""
727
s = ("%d day%s, " % plural(self._days)) + s
728
if self._microseconds:
729
s = s + ".%06d" % self._microseconds
730
return s
731
732
def total_seconds(self):
733
"""Total seconds in the duration."""
734
return ((self.days * 86400 + self.seconds) * 10**6 +
735
self.microseconds) / 10**6
736
737
# Read-only field accessors
738
@property
739
def days(self):
740
"""days"""
741
return self._days
742
743
@property
744
def seconds(self):
745
"""seconds"""
746
return self._seconds
747
748
@property
749
def microseconds(self):
750
"""microseconds"""
751
return self._microseconds
752
753
def __add__(self, other):
754
if isinstance(other, timedelta):
755
# for CPython compatibility, we cannot use
756
# our __class__ here, but need a real timedelta
757
return timedelta(self._days + other._days,
758
self._seconds + other._seconds,
759
self._microseconds + other._microseconds)
760
return NotImplemented
761
762
__radd__ = __add__
763
764
def __sub__(self, other):
765
if isinstance(other, timedelta):
766
# for CPython compatibility, we cannot use
767
# our __class__ here, but need a real timedelta
768
return timedelta(self._days - other._days,
769
self._seconds - other._seconds,
770
self._microseconds - other._microseconds)
771
return NotImplemented
772
773
def __rsub__(self, other):
774
if isinstance(other, timedelta):
775
return -self + other
776
return NotImplemented
777
778
def __neg__(self):
779
# for CPython compatibility, we cannot use
780
# our __class__ here, but need a real timedelta
781
return timedelta(-self._days,
782
-self._seconds,
783
-self._microseconds)
784
785
def __pos__(self):
786
return self
787
788
def __abs__(self):
789
if self._days < 0:
790
return -self
791
else:
792
return self
793
794
def __mul__(self, other):
795
if isinstance(other, int):
796
# for CPython compatibility, we cannot use
797
# our __class__ here, but need a real timedelta
798
return timedelta(self._days * other,
799
self._seconds * other,
800
self._microseconds * other)
801
if isinstance(other, float):
802
usec = self._to_microseconds()
803
a, b = other.as_integer_ratio()
804
return timedelta(0, 0, _divide_and_round(usec * a, b))
805
return NotImplemented
806
807
__rmul__ = __mul__
808
809
def _to_microseconds(self):
810
return ((self._days * (24*3600) + self._seconds) * 1000000 +
811
self._microseconds)
812
813
def __floordiv__(self, other):
814
if not isinstance(other, (int, timedelta)):
815
return NotImplemented
816
usec = self._to_microseconds()
817
if isinstance(other, timedelta):
818
return usec // other._to_microseconds()
819
if isinstance(other, int):
820
return timedelta(0, 0, usec // other)
821
822
def __truediv__(self, other):
823
if not isinstance(other, (int, float, timedelta)):
824
return NotImplemented
825
usec = self._to_microseconds()
826
if isinstance(other, timedelta):
827
return usec / other._to_microseconds()
828
if isinstance(other, int):
829
return timedelta(0, 0, _divide_and_round(usec, other))
830
if isinstance(other, float):
831
a, b = other.as_integer_ratio()
832
return timedelta(0, 0, _divide_and_round(b * usec, a))
833
834
def __mod__(self, other):
835
if isinstance(other, timedelta):
836
r = self._to_microseconds() % other._to_microseconds()
837
return timedelta(0, 0, r)
838
return NotImplemented
839
840
def __divmod__(self, other):
841
if isinstance(other, timedelta):
842
q, r = divmod(self._to_microseconds(),
843
other._to_microseconds())
844
return q, timedelta(0, 0, r)
845
return NotImplemented
846
847
# Comparisons of timedelta objects with other.
848
849
def __eq__(self, other):
850
if isinstance(other, timedelta):
851
return self._cmp(other) == 0
852
else:
853
return NotImplemented
854
855
def __le__(self, other):
856
if isinstance(other, timedelta):
857
return self._cmp(other) <= 0
858
else:
859
return NotImplemented
860
861
def __lt__(self, other):
862
if isinstance(other, timedelta):
863
return self._cmp(other) < 0
864
else:
865
return NotImplemented
866
867
def __ge__(self, other):
868
if isinstance(other, timedelta):
869
return self._cmp(other) >= 0
870
else:
871
return NotImplemented
872
873
def __gt__(self, other):
874
if isinstance(other, timedelta):
875
return self._cmp(other) > 0
876
else:
877
return NotImplemented
878
879
def _cmp(self, other):
880
assert isinstance(other, timedelta)
881
return _cmp(self._getstate(), other._getstate())
882
883
def __hash__(self):
884
if self._hashcode == -1:
885
self._hashcode = hash(self._getstate())
886
return self._hashcode
887
888
def __bool__(self):
889
return (self._days != 0 or
890
self._seconds != 0 or
891
self._microseconds != 0)
892
893
# Pickle support.
894
895
def _getstate(self):
896
return (self._days, self._seconds, self._microseconds)
897
898
def __reduce__(self):
899
return (self.__class__, self._getstate())
900
901
timedelta.min = timedelta(-999999999)
902
timedelta.max = timedelta(days=999999999, hours=23, minutes=59, seconds=59,
903
microseconds=999999)
904
timedelta.resolution = timedelta(microseconds=1)
905
906
class date:
907
"""Concrete date type.
908
909
Constructors:
910
911
__new__()
912
fromtimestamp()
913
today()
914
fromordinal()
915
916
Operators:
917
918
__repr__, __str__
919
__eq__, __le__, __lt__, __ge__, __gt__, __hash__
920
__add__, __radd__, __sub__ (add/radd only with timedelta arg)
921
922
Methods:
923
924
timetuple()
925
toordinal()
926
weekday()
927
isoweekday(), isocalendar(), isoformat()
928
ctime()
929
strftime()
930
931
Properties (readonly):
932
year, month, day
933
"""
934
__slots__ = '_year', '_month', '_day', '_hashcode'
935
936
def __new__(cls, year, month=None, day=None):
937
"""Constructor.
938
939
Arguments:
940
941
year, month, day (required, base 1)
942
"""
943
if (month is None and
944
isinstance(year, (bytes, str)) and len(year) == 4 and
945
1 <= ord(year[2:3]) <= 12):
946
# Pickle support
947
if isinstance(year, str):
948
try:
949
year = year.encode('latin1')
950
except UnicodeEncodeError:
951
# More informative error message.
952
raise ValueError(
953
"Failed to encode latin1 string when unpickling "
954
"a date object. "
955
"pickle.load(data, encoding='latin1') is assumed.")
956
self = object.__new__(cls)
957
self.__setstate(year)
958
self._hashcode = -1
959
return self
960
year, month, day = _check_date_fields(year, month, day)
961
self = object.__new__(cls)
962
self._year = year
963
self._month = month
964
self._day = day
965
self._hashcode = -1
966
return self
967
968
# Additional constructors
969
970
@classmethod
971
def fromtimestamp(cls, t):
972
"Construct a date from a POSIX timestamp (like time.time())."
973
y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t)
974
return cls(y, m, d)
975
976
@classmethod
977
def today(cls):
978
"Construct a date from time.time()."
979
t = _time.time()
980
return cls.fromtimestamp(t)
981
982
@classmethod
983
def fromordinal(cls, n):
984
"""Construct a date from a proleptic Gregorian ordinal.
985
986
January 1 of year 1 is day 1. Only the year, month and day are
987
non-zero in the result.
988
"""
989
y, m, d = _ord2ymd(n)
990
return cls(y, m, d)
991
992
@classmethod
993
def fromisoformat(cls, date_string):
994
"""Construct a date from a string in ISO 8601 format."""
995
if not isinstance(date_string, str):
996
raise TypeError('fromisoformat: argument must be str')
997
998
if len(date_string) not in (7, 8, 10):
999
raise ValueError(f'Invalid isoformat string: {date_string!r}')
1000
1001
try:
1002
return cls(*_parse_isoformat_date(date_string))
1003
except Exception:
1004
raise ValueError(f'Invalid isoformat string: {date_string!r}')
1005
1006
@classmethod
1007
def fromisocalendar(cls, year, week, day):
1008
"""Construct a date from the ISO year, week number and weekday.
1009
1010
This is the inverse of the date.isocalendar() function"""
1011
return cls(*_isoweek_to_gregorian(year, week, day))
1012
1013
# Conversions to string
1014
1015
def __repr__(self):
1016
"""Convert to formal string, for repr().
1017
1018
>>> dt = datetime(2010, 1, 1)
1019
>>> repr(dt)
1020
'datetime.datetime(2010, 1, 1, 0, 0)'
1021
1022
>>> dt = datetime(2010, 1, 1, tzinfo=timezone.utc)
1023
>>> repr(dt)
1024
'datetime.datetime(2010, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)'
1025
"""
1026
return "%s.%s(%d, %d, %d)" % (_get_class_module(self),
1027
self.__class__.__qualname__,
1028
self._year,
1029
self._month,
1030
self._day)
1031
# XXX These shouldn't depend on time.localtime(), because that
1032
# clips the usable dates to [1970 .. 2038). At least ctime() is
1033
# easily done without using strftime() -- that's better too because
1034
# strftime("%c", ...) is locale specific.
1035
1036
1037
def ctime(self):
1038
"Return ctime() style string."
1039
weekday = self.toordinal() % 7 or 7
1040
return "%s %s %2d 00:00:00 %04d" % (
1041
_DAYNAMES[weekday],
1042
_MONTHNAMES[self._month],
1043
self._day, self._year)
1044
1045
def strftime(self, format):
1046
"""
1047
Format using strftime().
1048
1049
Example: "%d/%m/%Y, %H:%M:%S"
1050
"""
1051
return _wrap_strftime(self, format, self.timetuple())
1052
1053
def __format__(self, fmt):
1054
if not isinstance(fmt, str):
1055
raise TypeError("must be str, not %s" % type(fmt).__name__)
1056
if len(fmt) != 0:
1057
return self.strftime(fmt)
1058
return str(self)
1059
1060
def isoformat(self):
1061
"""Return the date formatted according to ISO.
1062
1063
This is 'YYYY-MM-DD'.
1064
1065
References:
1066
- http://www.w3.org/TR/NOTE-datetime
1067
- http://www.cl.cam.ac.uk/~mgk25/iso-time.html
1068
"""
1069
return "%04d-%02d-%02d" % (self._year, self._month, self._day)
1070
1071
__str__ = isoformat
1072
1073
# Read-only field accessors
1074
@property
1075
def year(self):
1076
"""year (1-9999)"""
1077
return self._year
1078
1079
@property
1080
def month(self):
1081
"""month (1-12)"""
1082
return self._month
1083
1084
@property
1085
def day(self):
1086
"""day (1-31)"""
1087
return self._day
1088
1089
# Standard conversions, __eq__, __le__, __lt__, __ge__, __gt__,
1090
# __hash__ (and helpers)
1091
1092
def timetuple(self):
1093
"Return local time tuple compatible with time.localtime()."
1094
return _build_struct_time(self._year, self._month, self._day,
1095
0, 0, 0, -1)
1096
1097
def toordinal(self):
1098
"""Return proleptic Gregorian ordinal for the year, month and day.
1099
1100
January 1 of year 1 is day 1. Only the year, month and day values
1101
contribute to the result.
1102
"""
1103
return _ymd2ord(self._year, self._month, self._day)
1104
1105
def replace(self, year=None, month=None, day=None):
1106
"""Return a new date with new values for the specified fields."""
1107
if year is None:
1108
year = self._year
1109
if month is None:
1110
month = self._month
1111
if day is None:
1112
day = self._day
1113
return type(self)(year, month, day)
1114
1115
# Comparisons of date objects with other.
1116
1117
def __eq__(self, other):
1118
if isinstance(other, date):
1119
return self._cmp(other) == 0
1120
return NotImplemented
1121
1122
def __le__(self, other):
1123
if isinstance(other, date):
1124
return self._cmp(other) <= 0
1125
return NotImplemented
1126
1127
def __lt__(self, other):
1128
if isinstance(other, date):
1129
return self._cmp(other) < 0
1130
return NotImplemented
1131
1132
def __ge__(self, other):
1133
if isinstance(other, date):
1134
return self._cmp(other) >= 0
1135
return NotImplemented
1136
1137
def __gt__(self, other):
1138
if isinstance(other, date):
1139
return self._cmp(other) > 0
1140
return NotImplemented
1141
1142
def _cmp(self, other):
1143
assert isinstance(other, date)
1144
y, m, d = self._year, self._month, self._day
1145
y2, m2, d2 = other._year, other._month, other._day
1146
return _cmp((y, m, d), (y2, m2, d2))
1147
1148
def __hash__(self):
1149
"Hash."
1150
if self._hashcode == -1:
1151
self._hashcode = hash(self._getstate())
1152
return self._hashcode
1153
1154
# Computations
1155
1156
def __add__(self, other):
1157
"Add a date to a timedelta."
1158
if isinstance(other, timedelta):
1159
o = self.toordinal() + other.days
1160
if 0 < o <= _MAXORDINAL:
1161
return type(self).fromordinal(o)
1162
raise OverflowError("result out of range")
1163
return NotImplemented
1164
1165
__radd__ = __add__
1166
1167
def __sub__(self, other):
1168
"""Subtract two dates, or a date and a timedelta."""
1169
if isinstance(other, timedelta):
1170
return self + timedelta(-other.days)
1171
if isinstance(other, date):
1172
days1 = self.toordinal()
1173
days2 = other.toordinal()
1174
return timedelta(days1 - days2)
1175
return NotImplemented
1176
1177
def weekday(self):
1178
"Return day of the week, where Monday == 0 ... Sunday == 6."
1179
return (self.toordinal() + 6) % 7
1180
1181
# Day-of-the-week and week-of-the-year, according to ISO
1182
1183
def isoweekday(self):
1184
"Return day of the week, where Monday == 1 ... Sunday == 7."
1185
# 1-Jan-0001 is a Monday
1186
return self.toordinal() % 7 or 7
1187
1188
def isocalendar(self):
1189
"""Return a named tuple containing ISO year, week number, and weekday.
1190
1191
The first ISO week of the year is the (Mon-Sun) week
1192
containing the year's first Thursday; everything else derives
1193
from that.
1194
1195
The first week is 1; Monday is 1 ... Sunday is 7.
1196
1197
ISO calendar algorithm taken from
1198
http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1199
(used with permission)
1200
"""
1201
year = self._year
1202
week1monday = _isoweek1monday(year)
1203
today = _ymd2ord(self._year, self._month, self._day)
1204
# Internally, week and day have origin 0
1205
week, day = divmod(today - week1monday, 7)
1206
if week < 0:
1207
year -= 1
1208
week1monday = _isoweek1monday(year)
1209
week, day = divmod(today - week1monday, 7)
1210
elif week >= 52:
1211
if today >= _isoweek1monday(year+1):
1212
year += 1
1213
week = 0
1214
return _IsoCalendarDate(year, week+1, day+1)
1215
1216
# Pickle support.
1217
1218
def _getstate(self):
1219
yhi, ylo = divmod(self._year, 256)
1220
return bytes([yhi, ylo, self._month, self._day]),
1221
1222
def __setstate(self, string):
1223
yhi, ylo, self._month, self._day = string
1224
self._year = yhi * 256 + ylo
1225
1226
def __reduce__(self):
1227
return (self.__class__, self._getstate())
1228
1229
_date_class = date # so functions w/ args named "date" can get at the class
1230
1231
date.min = date(1, 1, 1)
1232
date.max = date(9999, 12, 31)
1233
date.resolution = timedelta(days=1)
1234
1235
1236
class tzinfo:
1237
"""Abstract base class for time zone info classes.
1238
1239
Subclasses must override the name(), utcoffset() and dst() methods.
1240
"""
1241
__slots__ = ()
1242
1243
def tzname(self, dt):
1244
"datetime -> string name of time zone."
1245
raise NotImplementedError("tzinfo subclass must override tzname()")
1246
1247
def utcoffset(self, dt):
1248
"datetime -> timedelta, positive for east of UTC, negative for west of UTC"
1249
raise NotImplementedError("tzinfo subclass must override utcoffset()")
1250
1251
def dst(self, dt):
1252
"""datetime -> DST offset as timedelta, positive for east of UTC.
1253
1254
Return 0 if DST not in effect. utcoffset() must include the DST
1255
offset.
1256
"""
1257
raise NotImplementedError("tzinfo subclass must override dst()")
1258
1259
def fromutc(self, dt):
1260
"datetime in UTC -> datetime in local time."
1261
1262
if not isinstance(dt, datetime):
1263
raise TypeError("fromutc() requires a datetime argument")
1264
if dt.tzinfo is not self:
1265
raise ValueError("dt.tzinfo is not self")
1266
1267
dtoff = dt.utcoffset()
1268
if dtoff is None:
1269
raise ValueError("fromutc() requires a non-None utcoffset() "
1270
"result")
1271
1272
# See the long comment block at the end of this file for an
1273
# explanation of this algorithm.
1274
dtdst = dt.dst()
1275
if dtdst is None:
1276
raise ValueError("fromutc() requires a non-None dst() result")
1277
delta = dtoff - dtdst
1278
if delta:
1279
dt += delta
1280
dtdst = dt.dst()
1281
if dtdst is None:
1282
raise ValueError("fromutc(): dt.dst gave inconsistent "
1283
"results; cannot convert")
1284
return dt + dtdst
1285
1286
# Pickle support.
1287
1288
def __reduce__(self):
1289
getinitargs = getattr(self, "__getinitargs__", None)
1290
if getinitargs:
1291
args = getinitargs()
1292
else:
1293
args = ()
1294
return (self.__class__, args, self.__getstate__())
1295
1296
1297
class IsoCalendarDate(tuple):
1298
1299
def __new__(cls, year, week, weekday, /):
1300
return super().__new__(cls, (year, week, weekday))
1301
1302
@property
1303
def year(self):
1304
return self[0]
1305
1306
@property
1307
def week(self):
1308
return self[1]
1309
1310
@property
1311
def weekday(self):
1312
return self[2]
1313
1314
def __reduce__(self):
1315
# This code is intended to pickle the object without making the
1316
# class public. See https://bugs.python.org/msg352381
1317
return (tuple, (tuple(self),))
1318
1319
def __repr__(self):
1320
return (f'{self.__class__.__name__}'
1321
f'(year={self[0]}, week={self[1]}, weekday={self[2]})')
1322
1323
1324
_IsoCalendarDate = IsoCalendarDate
1325
del IsoCalendarDate
1326
_tzinfo_class = tzinfo
1327
1328
class time:
1329
"""Time with time zone.
1330
1331
Constructors:
1332
1333
__new__()
1334
1335
Operators:
1336
1337
__repr__, __str__
1338
__eq__, __le__, __lt__, __ge__, __gt__, __hash__
1339
1340
Methods:
1341
1342
strftime()
1343
isoformat()
1344
utcoffset()
1345
tzname()
1346
dst()
1347
1348
Properties (readonly):
1349
hour, minute, second, microsecond, tzinfo, fold
1350
"""
1351
__slots__ = '_hour', '_minute', '_second', '_microsecond', '_tzinfo', '_hashcode', '_fold'
1352
1353
def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0):
1354
"""Constructor.
1355
1356
Arguments:
1357
1358
hour, minute (required)
1359
second, microsecond (default to zero)
1360
tzinfo (default to None)
1361
fold (keyword only, default to zero)
1362
"""
1363
if (isinstance(hour, (bytes, str)) and len(hour) == 6 and
1364
ord(hour[0:1])&0x7F < 24):
1365
# Pickle support
1366
if isinstance(hour, str):
1367
try:
1368
hour = hour.encode('latin1')
1369
except UnicodeEncodeError:
1370
# More informative error message.
1371
raise ValueError(
1372
"Failed to encode latin1 string when unpickling "
1373
"a time object. "
1374
"pickle.load(data, encoding='latin1') is assumed.")
1375
self = object.__new__(cls)
1376
self.__setstate(hour, minute or None)
1377
self._hashcode = -1
1378
return self
1379
hour, minute, second, microsecond, fold = _check_time_fields(
1380
hour, minute, second, microsecond, fold)
1381
_check_tzinfo_arg(tzinfo)
1382
self = object.__new__(cls)
1383
self._hour = hour
1384
self._minute = minute
1385
self._second = second
1386
self._microsecond = microsecond
1387
self._tzinfo = tzinfo
1388
self._hashcode = -1
1389
self._fold = fold
1390
return self
1391
1392
# Read-only field accessors
1393
@property
1394
def hour(self):
1395
"""hour (0-23)"""
1396
return self._hour
1397
1398
@property
1399
def minute(self):
1400
"""minute (0-59)"""
1401
return self._minute
1402
1403
@property
1404
def second(self):
1405
"""second (0-59)"""
1406
return self._second
1407
1408
@property
1409
def microsecond(self):
1410
"""microsecond (0-999999)"""
1411
return self._microsecond
1412
1413
@property
1414
def tzinfo(self):
1415
"""timezone info object"""
1416
return self._tzinfo
1417
1418
@property
1419
def fold(self):
1420
return self._fold
1421
1422
# Standard conversions, __hash__ (and helpers)
1423
1424
# Comparisons of time objects with other.
1425
1426
def __eq__(self, other):
1427
if isinstance(other, time):
1428
return self._cmp(other, allow_mixed=True) == 0
1429
else:
1430
return NotImplemented
1431
1432
def __le__(self, other):
1433
if isinstance(other, time):
1434
return self._cmp(other) <= 0
1435
else:
1436
return NotImplemented
1437
1438
def __lt__(self, other):
1439
if isinstance(other, time):
1440
return self._cmp(other) < 0
1441
else:
1442
return NotImplemented
1443
1444
def __ge__(self, other):
1445
if isinstance(other, time):
1446
return self._cmp(other) >= 0
1447
else:
1448
return NotImplemented
1449
1450
def __gt__(self, other):
1451
if isinstance(other, time):
1452
return self._cmp(other) > 0
1453
else:
1454
return NotImplemented
1455
1456
def _cmp(self, other, allow_mixed=False):
1457
assert isinstance(other, time)
1458
mytz = self._tzinfo
1459
ottz = other._tzinfo
1460
myoff = otoff = None
1461
1462
if mytz is ottz:
1463
base_compare = True
1464
else:
1465
myoff = self.utcoffset()
1466
otoff = other.utcoffset()
1467
base_compare = myoff == otoff
1468
1469
if base_compare:
1470
return _cmp((self._hour, self._minute, self._second,
1471
self._microsecond),
1472
(other._hour, other._minute, other._second,
1473
other._microsecond))
1474
if myoff is None or otoff is None:
1475
if allow_mixed:
1476
return 2 # arbitrary non-zero value
1477
else:
1478
raise TypeError("cannot compare naive and aware times")
1479
myhhmm = self._hour * 60 + self._minute - myoff//timedelta(minutes=1)
1480
othhmm = other._hour * 60 + other._minute - otoff//timedelta(minutes=1)
1481
return _cmp((myhhmm, self._second, self._microsecond),
1482
(othhmm, other._second, other._microsecond))
1483
1484
def __hash__(self):
1485
"""Hash."""
1486
if self._hashcode == -1:
1487
if self.fold:
1488
t = self.replace(fold=0)
1489
else:
1490
t = self
1491
tzoff = t.utcoffset()
1492
if not tzoff: # zero or None
1493
self._hashcode = hash(t._getstate()[0])
1494
else:
1495
h, m = divmod(timedelta(hours=self.hour, minutes=self.minute) - tzoff,
1496
timedelta(hours=1))
1497
assert not m % timedelta(minutes=1), "whole minute"
1498
m //= timedelta(minutes=1)
1499
if 0 <= h < 24:
1500
self._hashcode = hash(time(h, m, self.second, self.microsecond))
1501
else:
1502
self._hashcode = hash((h, m, self.second, self.microsecond))
1503
return self._hashcode
1504
1505
# Conversion to string
1506
1507
def _tzstr(self):
1508
"""Return formatted timezone offset (+xx:xx) or an empty string."""
1509
off = self.utcoffset()
1510
return _format_offset(off)
1511
1512
def __repr__(self):
1513
"""Convert to formal string, for repr()."""
1514
if self._microsecond != 0:
1515
s = ", %d, %d" % (self._second, self._microsecond)
1516
elif self._second != 0:
1517
s = ", %d" % self._second
1518
else:
1519
s = ""
1520
s= "%s.%s(%d, %d%s)" % (_get_class_module(self),
1521
self.__class__.__qualname__,
1522
self._hour, self._minute, s)
1523
if self._tzinfo is not None:
1524
assert s[-1:] == ")"
1525
s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")"
1526
if self._fold:
1527
assert s[-1:] == ")"
1528
s = s[:-1] + ", fold=1)"
1529
return s
1530
1531
def isoformat(self, timespec='auto'):
1532
"""Return the time formatted according to ISO.
1533
1534
The full format is 'HH:MM:SS.mmmmmm+zz:zz'. By default, the fractional
1535
part is omitted if self.microsecond == 0.
1536
1537
The optional argument timespec specifies the number of additional
1538
terms of the time to include. Valid options are 'auto', 'hours',
1539
'minutes', 'seconds', 'milliseconds' and 'microseconds'.
1540
"""
1541
s = _format_time(self._hour, self._minute, self._second,
1542
self._microsecond, timespec)
1543
tz = self._tzstr()
1544
if tz:
1545
s += tz
1546
return s
1547
1548
__str__ = isoformat
1549
1550
@classmethod
1551
def fromisoformat(cls, time_string):
1552
"""Construct a time from a string in one of the ISO 8601 formats."""
1553
if not isinstance(time_string, str):
1554
raise TypeError('fromisoformat: argument must be str')
1555
1556
# The spec actually requires that time-only ISO 8601 strings start with
1557
# T, but the extended format allows this to be omitted as long as there
1558
# is no ambiguity with date strings.
1559
time_string = time_string.removeprefix('T')
1560
1561
try:
1562
return cls(*_parse_isoformat_time(time_string))
1563
except Exception:
1564
raise ValueError(f'Invalid isoformat string: {time_string!r}')
1565
1566
def strftime(self, format):
1567
"""Format using strftime(). The date part of the timestamp passed
1568
to underlying strftime should not be used.
1569
"""
1570
# The year must be >= 1000 else Python's strftime implementation
1571
# can raise a bogus exception.
1572
timetuple = (1900, 1, 1,
1573
self._hour, self._minute, self._second,
1574
0, 1, -1)
1575
return _wrap_strftime(self, format, timetuple)
1576
1577
def __format__(self, fmt):
1578
if not isinstance(fmt, str):
1579
raise TypeError("must be str, not %s" % type(fmt).__name__)
1580
if len(fmt) != 0:
1581
return self.strftime(fmt)
1582
return str(self)
1583
1584
# Timezone functions
1585
1586
def utcoffset(self):
1587
"""Return the timezone offset as timedelta, positive east of UTC
1588
(negative west of UTC)."""
1589
if self._tzinfo is None:
1590
return None
1591
offset = self._tzinfo.utcoffset(None)
1592
_check_utc_offset("utcoffset", offset)
1593
return offset
1594
1595
def tzname(self):
1596
"""Return the timezone name.
1597
1598
Note that the name is 100% informational -- there's no requirement that
1599
it mean anything in particular. For example, "GMT", "UTC", "-500",
1600
"-5:00", "EDT", "US/Eastern", "America/New York" are all valid replies.
1601
"""
1602
if self._tzinfo is None:
1603
return None
1604
name = self._tzinfo.tzname(None)
1605
_check_tzname(name)
1606
return name
1607
1608
def dst(self):
1609
"""Return 0 if DST is not in effect, or the DST offset (as timedelta
1610
positive eastward) if DST is in effect.
1611
1612
This is purely informational; the DST offset has already been added to
1613
the UTC offset returned by utcoffset() if applicable, so there's no
1614
need to consult dst() unless you're interested in displaying the DST
1615
info.
1616
"""
1617
if self._tzinfo is None:
1618
return None
1619
offset = self._tzinfo.dst(None)
1620
_check_utc_offset("dst", offset)
1621
return offset
1622
1623
def replace(self, hour=None, minute=None, second=None, microsecond=None,
1624
tzinfo=True, *, fold=None):
1625
"""Return a new time with new values for the specified fields."""
1626
if hour is None:
1627
hour = self.hour
1628
if minute is None:
1629
minute = self.minute
1630
if second is None:
1631
second = self.second
1632
if microsecond is None:
1633
microsecond = self.microsecond
1634
if tzinfo is True:
1635
tzinfo = self.tzinfo
1636
if fold is None:
1637
fold = self._fold
1638
return type(self)(hour, minute, second, microsecond, tzinfo, fold=fold)
1639
1640
# Pickle support.
1641
1642
def _getstate(self, protocol=3):
1643
us2, us3 = divmod(self._microsecond, 256)
1644
us1, us2 = divmod(us2, 256)
1645
h = self._hour
1646
if self._fold and protocol > 3:
1647
h += 128
1648
basestate = bytes([h, self._minute, self._second,
1649
us1, us2, us3])
1650
if self._tzinfo is None:
1651
return (basestate,)
1652
else:
1653
return (basestate, self._tzinfo)
1654
1655
def __setstate(self, string, tzinfo):
1656
if tzinfo is not None and not isinstance(tzinfo, _tzinfo_class):
1657
raise TypeError("bad tzinfo state arg")
1658
h, self._minute, self._second, us1, us2, us3 = string
1659
if h > 127:
1660
self._fold = 1
1661
self._hour = h - 128
1662
else:
1663
self._fold = 0
1664
self._hour = h
1665
self._microsecond = (((us1 << 8) | us2) << 8) | us3
1666
self._tzinfo = tzinfo
1667
1668
def __reduce_ex__(self, protocol):
1669
return (self.__class__, self._getstate(protocol))
1670
1671
def __reduce__(self):
1672
return self.__reduce_ex__(2)
1673
1674
_time_class = time # so functions w/ args named "time" can get at the class
1675
1676
time.min = time(0, 0, 0)
1677
time.max = time(23, 59, 59, 999999)
1678
time.resolution = timedelta(microseconds=1)
1679
1680
1681
class datetime(date):
1682
"""datetime(year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]])
1683
1684
The year, month and day arguments are required. tzinfo may be None, or an
1685
instance of a tzinfo subclass. The remaining arguments may be ints.
1686
"""
1687
__slots__ = date.__slots__ + time.__slots__
1688
1689
def __new__(cls, year, month=None, day=None, hour=0, minute=0, second=0,
1690
microsecond=0, tzinfo=None, *, fold=0):
1691
if (isinstance(year, (bytes, str)) and len(year) == 10 and
1692
1 <= ord(year[2:3])&0x7F <= 12):
1693
# Pickle support
1694
if isinstance(year, str):
1695
try:
1696
year = bytes(year, 'latin1')
1697
except UnicodeEncodeError:
1698
# More informative error message.
1699
raise ValueError(
1700
"Failed to encode latin1 string when unpickling "
1701
"a datetime object. "
1702
"pickle.load(data, encoding='latin1') is assumed.")
1703
self = object.__new__(cls)
1704
self.__setstate(year, month)
1705
self._hashcode = -1
1706
return self
1707
year, month, day = _check_date_fields(year, month, day)
1708
hour, minute, second, microsecond, fold = _check_time_fields(
1709
hour, minute, second, microsecond, fold)
1710
_check_tzinfo_arg(tzinfo)
1711
self = object.__new__(cls)
1712
self._year = year
1713
self._month = month
1714
self._day = day
1715
self._hour = hour
1716
self._minute = minute
1717
self._second = second
1718
self._microsecond = microsecond
1719
self._tzinfo = tzinfo
1720
self._hashcode = -1
1721
self._fold = fold
1722
return self
1723
1724
# Read-only field accessors
1725
@property
1726
def hour(self):
1727
"""hour (0-23)"""
1728
return self._hour
1729
1730
@property
1731
def minute(self):
1732
"""minute (0-59)"""
1733
return self._minute
1734
1735
@property
1736
def second(self):
1737
"""second (0-59)"""
1738
return self._second
1739
1740
@property
1741
def microsecond(self):
1742
"""microsecond (0-999999)"""
1743
return self._microsecond
1744
1745
@property
1746
def tzinfo(self):
1747
"""timezone info object"""
1748
return self._tzinfo
1749
1750
@property
1751
def fold(self):
1752
return self._fold
1753
1754
@classmethod
1755
def _fromtimestamp(cls, t, utc, tz):
1756
"""Construct a datetime from a POSIX timestamp (like time.time()).
1757
1758
A timezone info object may be passed in as well.
1759
"""
1760
frac, t = _math.modf(t)
1761
us = round(frac * 1e6)
1762
if us >= 1000000:
1763
t += 1
1764
us -= 1000000
1765
elif us < 0:
1766
t -= 1
1767
us += 1000000
1768
1769
converter = _time.gmtime if utc else _time.localtime
1770
y, m, d, hh, mm, ss, weekday, jday, dst = converter(t)
1771
ss = min(ss, 59) # clamp out leap seconds if the platform has them
1772
result = cls(y, m, d, hh, mm, ss, us, tz)
1773
if tz is None and not utc:
1774
# As of version 2015f max fold in IANA database is
1775
# 23 hours at 1969-09-30 13:00:00 in Kwajalein.
1776
# Let's probe 24 hours in the past to detect a transition:
1777
max_fold_seconds = 24 * 3600
1778
1779
# On Windows localtime_s throws an OSError for negative values,
1780
# thus we can't perform fold detection for values of time less
1781
# than the max time fold. See comments in _datetimemodule's
1782
# version of this method for more details.
1783
if t < max_fold_seconds and sys.platform.startswith("win"):
1784
return result
1785
1786
y, m, d, hh, mm, ss = converter(t - max_fold_seconds)[:6]
1787
probe1 = cls(y, m, d, hh, mm, ss, us, tz)
1788
trans = result - probe1 - timedelta(0, max_fold_seconds)
1789
if trans.days < 0:
1790
y, m, d, hh, mm, ss = converter(t + trans // timedelta(0, 1))[:6]
1791
probe2 = cls(y, m, d, hh, mm, ss, us, tz)
1792
if probe2 == result:
1793
result._fold = 1
1794
elif tz is not None:
1795
result = tz.fromutc(result)
1796
return result
1797
1798
@classmethod
1799
def fromtimestamp(cls, timestamp, tz=None):
1800
"""Construct a datetime from a POSIX timestamp (like time.time()).
1801
1802
A timezone info object may be passed in as well.
1803
"""
1804
_check_tzinfo_arg(tz)
1805
1806
return cls._fromtimestamp(timestamp, tz is not None, tz)
1807
1808
@classmethod
1809
def utcfromtimestamp(cls, t):
1810
"""Construct a naive UTC datetime from a POSIX timestamp."""
1811
import warnings
1812
warnings.warn("datetime.utcfromtimestamp() is deprecated and scheduled "
1813
"for removal in a future version. Use timezone-aware "
1814
"objects to represent datetimes in UTC: "
1815
"datetime.fromtimestamp(t, datetime.UTC).",
1816
DeprecationWarning,
1817
stacklevel=2)
1818
return cls._fromtimestamp(t, True, None)
1819
1820
@classmethod
1821
def now(cls, tz=None):
1822
"Construct a datetime from time.time() and optional time zone info."
1823
t = _time.time()
1824
return cls.fromtimestamp(t, tz)
1825
1826
@classmethod
1827
def utcnow(cls):
1828
"Construct a UTC datetime from time.time()."
1829
import warnings
1830
warnings.warn("datetime.utcnow() is deprecated and scheduled for "
1831
"removal in a future version. Instead, Use timezone-aware "
1832
"objects to represent datetimes in UTC: "
1833
"datetime.now(datetime.UTC).",
1834
DeprecationWarning,
1835
stacklevel=2)
1836
t = _time.time()
1837
return cls._fromtimestamp(t, True, None)
1838
1839
@classmethod
1840
def combine(cls, date, time, tzinfo=True):
1841
"Construct a datetime from a given date and a given time."
1842
if not isinstance(date, _date_class):
1843
raise TypeError("date argument must be a date instance")
1844
if not isinstance(time, _time_class):
1845
raise TypeError("time argument must be a time instance")
1846
if tzinfo is True:
1847
tzinfo = time.tzinfo
1848
return cls(date.year, date.month, date.day,
1849
time.hour, time.minute, time.second, time.microsecond,
1850
tzinfo, fold=time.fold)
1851
1852
@classmethod
1853
def fromisoformat(cls, date_string):
1854
"""Construct a datetime from a string in one of the ISO 8601 formats."""
1855
if not isinstance(date_string, str):
1856
raise TypeError('fromisoformat: argument must be str')
1857
1858
if len(date_string) < 7:
1859
raise ValueError(f'Invalid isoformat string: {date_string!r}')
1860
1861
# Split this at the separator
1862
try:
1863
separator_location = _find_isoformat_datetime_separator(date_string)
1864
dstr = date_string[0:separator_location]
1865
tstr = date_string[(separator_location+1):]
1866
1867
date_components = _parse_isoformat_date(dstr)
1868
except ValueError:
1869
raise ValueError(
1870
f'Invalid isoformat string: {date_string!r}') from None
1871
1872
if tstr:
1873
try:
1874
time_components = _parse_isoformat_time(tstr)
1875
except ValueError:
1876
raise ValueError(
1877
f'Invalid isoformat string: {date_string!r}') from None
1878
else:
1879
time_components = [0, 0, 0, 0, None]
1880
1881
return cls(*(date_components + time_components))
1882
1883
def timetuple(self):
1884
"Return local time tuple compatible with time.localtime()."
1885
dst = self.dst()
1886
if dst is None:
1887
dst = -1
1888
elif dst:
1889
dst = 1
1890
else:
1891
dst = 0
1892
return _build_struct_time(self.year, self.month, self.day,
1893
self.hour, self.minute, self.second,
1894
dst)
1895
1896
def _mktime(self):
1897
"""Return integer POSIX timestamp."""
1898
epoch = datetime(1970, 1, 1)
1899
max_fold_seconds = 24 * 3600
1900
t = (self - epoch) // timedelta(0, 1)
1901
def local(u):
1902
y, m, d, hh, mm, ss = _time.localtime(u)[:6]
1903
return (datetime(y, m, d, hh, mm, ss) - epoch) // timedelta(0, 1)
1904
1905
# Our goal is to solve t = local(u) for u.
1906
a = local(t) - t
1907
u1 = t - a
1908
t1 = local(u1)
1909
if t1 == t:
1910
# We found one solution, but it may not be the one we need.
1911
# Look for an earlier solution (if `fold` is 0), or a
1912
# later one (if `fold` is 1).
1913
u2 = u1 + (-max_fold_seconds, max_fold_seconds)[self.fold]
1914
b = local(u2) - u2
1915
if a == b:
1916
return u1
1917
else:
1918
b = t1 - u1
1919
assert a != b
1920
u2 = t - b
1921
t2 = local(u2)
1922
if t2 == t:
1923
return u2
1924
if t1 == t:
1925
return u1
1926
# We have found both offsets a and b, but neither t - a nor t - b is
1927
# a solution. This means t is in the gap.
1928
return (max, min)[self.fold](u1, u2)
1929
1930
1931
def timestamp(self):
1932
"Return POSIX timestamp as float"
1933
if self._tzinfo is None:
1934
s = self._mktime()
1935
return s + self.microsecond / 1e6
1936
else:
1937
return (self - _EPOCH).total_seconds()
1938
1939
def utctimetuple(self):
1940
"Return UTC time tuple compatible with time.gmtime()."
1941
offset = self.utcoffset()
1942
if offset:
1943
self -= offset
1944
y, m, d = self.year, self.month, self.day
1945
hh, mm, ss = self.hour, self.minute, self.second
1946
return _build_struct_time(y, m, d, hh, mm, ss, 0)
1947
1948
def date(self):
1949
"Return the date part."
1950
return date(self._year, self._month, self._day)
1951
1952
def time(self):
1953
"Return the time part, with tzinfo None."
1954
return time(self.hour, self.minute, self.second, self.microsecond, fold=self.fold)
1955
1956
def timetz(self):
1957
"Return the time part, with same tzinfo."
1958
return time(self.hour, self.minute, self.second, self.microsecond,
1959
self._tzinfo, fold=self.fold)
1960
1961
def replace(self, year=None, month=None, day=None, hour=None,
1962
minute=None, second=None, microsecond=None, tzinfo=True,
1963
*, fold=None):
1964
"""Return a new datetime with new values for the specified fields."""
1965
if year is None:
1966
year = self.year
1967
if month is None:
1968
month = self.month
1969
if day is None:
1970
day = self.day
1971
if hour is None:
1972
hour = self.hour
1973
if minute is None:
1974
minute = self.minute
1975
if second is None:
1976
second = self.second
1977
if microsecond is None:
1978
microsecond = self.microsecond
1979
if tzinfo is True:
1980
tzinfo = self.tzinfo
1981
if fold is None:
1982
fold = self.fold
1983
return type(self)(year, month, day, hour, minute, second,
1984
microsecond, tzinfo, fold=fold)
1985
1986
def _local_timezone(self):
1987
if self.tzinfo is None:
1988
ts = self._mktime()
1989
# Detect gap
1990
ts2 = self.replace(fold=1-self.fold)._mktime()
1991
if ts2 != ts: # This happens in a gap or a fold
1992
if (ts2 > ts) == self.fold:
1993
ts = ts2
1994
else:
1995
ts = (self - _EPOCH) // timedelta(seconds=1)
1996
localtm = _time.localtime(ts)
1997
local = datetime(*localtm[:6])
1998
# Extract TZ data
1999
gmtoff = localtm.tm_gmtoff
2000
zone = localtm.tm_zone
2001
return timezone(timedelta(seconds=gmtoff), zone)
2002
2003
def astimezone(self, tz=None):
2004
if tz is None:
2005
tz = self._local_timezone()
2006
elif not isinstance(tz, tzinfo):
2007
raise TypeError("tz argument must be an instance of tzinfo")
2008
2009
mytz = self.tzinfo
2010
if mytz is None:
2011
mytz = self._local_timezone()
2012
myoffset = mytz.utcoffset(self)
2013
else:
2014
myoffset = mytz.utcoffset(self)
2015
if myoffset is None:
2016
mytz = self.replace(tzinfo=None)._local_timezone()
2017
myoffset = mytz.utcoffset(self)
2018
2019
if tz is mytz:
2020
return self
2021
2022
# Convert self to UTC, and attach the new time zone object.
2023
utc = (self - myoffset).replace(tzinfo=tz)
2024
2025
# Convert from UTC to tz's local time.
2026
return tz.fromutc(utc)
2027
2028
# Ways to produce a string.
2029
2030
def ctime(self):
2031
"Return ctime() style string."
2032
weekday = self.toordinal() % 7 or 7
2033
return "%s %s %2d %02d:%02d:%02d %04d" % (
2034
_DAYNAMES[weekday],
2035
_MONTHNAMES[self._month],
2036
self._day,
2037
self._hour, self._minute, self._second,
2038
self._year)
2039
2040
def isoformat(self, sep='T', timespec='auto'):
2041
"""Return the time formatted according to ISO.
2042
2043
The full format looks like 'YYYY-MM-DD HH:MM:SS.mmmmmm'.
2044
By default, the fractional part is omitted if self.microsecond == 0.
2045
2046
If self.tzinfo is not None, the UTC offset is also attached, giving
2047
giving a full format of 'YYYY-MM-DD HH:MM:SS.mmmmmm+HH:MM'.
2048
2049
Optional argument sep specifies the separator between date and
2050
time, default 'T'.
2051
2052
The optional argument timespec specifies the number of additional
2053
terms of the time to include. Valid options are 'auto', 'hours',
2054
'minutes', 'seconds', 'milliseconds' and 'microseconds'.
2055
"""
2056
s = ("%04d-%02d-%02d%c" % (self._year, self._month, self._day, sep) +
2057
_format_time(self._hour, self._minute, self._second,
2058
self._microsecond, timespec))
2059
2060
off = self.utcoffset()
2061
tz = _format_offset(off)
2062
if tz:
2063
s += tz
2064
2065
return s
2066
2067
def __repr__(self):
2068
"""Convert to formal string, for repr()."""
2069
L = [self._year, self._month, self._day, # These are never zero
2070
self._hour, self._minute, self._second, self._microsecond]
2071
if L[-1] == 0:
2072
del L[-1]
2073
if L[-1] == 0:
2074
del L[-1]
2075
s = "%s.%s(%s)" % (_get_class_module(self),
2076
self.__class__.__qualname__,
2077
", ".join(map(str, L)))
2078
if self._tzinfo is not None:
2079
assert s[-1:] == ")"
2080
s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")"
2081
if self._fold:
2082
assert s[-1:] == ")"
2083
s = s[:-1] + ", fold=1)"
2084
return s
2085
2086
def __str__(self):
2087
"Convert to string, for str()."
2088
return self.isoformat(sep=' ')
2089
2090
@classmethod
2091
def strptime(cls, date_string, format):
2092
'string, format -> new datetime parsed from a string (like time.strptime()).'
2093
import _strptime
2094
return _strptime._strptime_datetime(cls, date_string, format)
2095
2096
def utcoffset(self):
2097
"""Return the timezone offset as timedelta positive east of UTC (negative west of
2098
UTC)."""
2099
if self._tzinfo is None:
2100
return None
2101
offset = self._tzinfo.utcoffset(self)
2102
_check_utc_offset("utcoffset", offset)
2103
return offset
2104
2105
def tzname(self):
2106
"""Return the timezone name.
2107
2108
Note that the name is 100% informational -- there's no requirement that
2109
it mean anything in particular. For example, "GMT", "UTC", "-500",
2110
"-5:00", "EDT", "US/Eastern", "America/New York" are all valid replies.
2111
"""
2112
if self._tzinfo is None:
2113
return None
2114
name = self._tzinfo.tzname(self)
2115
_check_tzname(name)
2116
return name
2117
2118
def dst(self):
2119
"""Return 0 if DST is not in effect, or the DST offset (as timedelta
2120
positive eastward) if DST is in effect.
2121
2122
This is purely informational; the DST offset has already been added to
2123
the UTC offset returned by utcoffset() if applicable, so there's no
2124
need to consult dst() unless you're interested in displaying the DST
2125
info.
2126
"""
2127
if self._tzinfo is None:
2128
return None
2129
offset = self._tzinfo.dst(self)
2130
_check_utc_offset("dst", offset)
2131
return offset
2132
2133
# Comparisons of datetime objects with other.
2134
2135
def __eq__(self, other):
2136
if isinstance(other, datetime):
2137
return self._cmp(other, allow_mixed=True) == 0
2138
elif not isinstance(other, date):
2139
return NotImplemented
2140
else:
2141
return False
2142
2143
def __le__(self, other):
2144
if isinstance(other, datetime):
2145
return self._cmp(other) <= 0
2146
elif not isinstance(other, date):
2147
return NotImplemented
2148
else:
2149
_cmperror(self, other)
2150
2151
def __lt__(self, other):
2152
if isinstance(other, datetime):
2153
return self._cmp(other) < 0
2154
elif not isinstance(other, date):
2155
return NotImplemented
2156
else:
2157
_cmperror(self, other)
2158
2159
def __ge__(self, other):
2160
if isinstance(other, datetime):
2161
return self._cmp(other) >= 0
2162
elif not isinstance(other, date):
2163
return NotImplemented
2164
else:
2165
_cmperror(self, other)
2166
2167
def __gt__(self, other):
2168
if isinstance(other, datetime):
2169
return self._cmp(other) > 0
2170
elif not isinstance(other, date):
2171
return NotImplemented
2172
else:
2173
_cmperror(self, other)
2174
2175
def _cmp(self, other, allow_mixed=False):
2176
assert isinstance(other, datetime)
2177
mytz = self._tzinfo
2178
ottz = other._tzinfo
2179
myoff = otoff = None
2180
2181
if mytz is ottz:
2182
base_compare = True
2183
else:
2184
myoff = self.utcoffset()
2185
otoff = other.utcoffset()
2186
# Assume that allow_mixed means that we are called from __eq__
2187
if allow_mixed:
2188
if myoff != self.replace(fold=not self.fold).utcoffset():
2189
return 2
2190
if otoff != other.replace(fold=not other.fold).utcoffset():
2191
return 2
2192
base_compare = myoff == otoff
2193
2194
if base_compare:
2195
return _cmp((self._year, self._month, self._day,
2196
self._hour, self._minute, self._second,
2197
self._microsecond),
2198
(other._year, other._month, other._day,
2199
other._hour, other._minute, other._second,
2200
other._microsecond))
2201
if myoff is None or otoff is None:
2202
if allow_mixed:
2203
return 2 # arbitrary non-zero value
2204
else:
2205
raise TypeError("cannot compare naive and aware datetimes")
2206
# XXX What follows could be done more efficiently...
2207
diff = self - other # this will take offsets into account
2208
if diff.days < 0:
2209
return -1
2210
return diff and 1 or 0
2211
2212
def __add__(self, other):
2213
"Add a datetime and a timedelta."
2214
if not isinstance(other, timedelta):
2215
return NotImplemented
2216
delta = timedelta(self.toordinal(),
2217
hours=self._hour,
2218
minutes=self._minute,
2219
seconds=self._second,
2220
microseconds=self._microsecond)
2221
delta += other
2222
hour, rem = divmod(delta.seconds, 3600)
2223
minute, second = divmod(rem, 60)
2224
if 0 < delta.days <= _MAXORDINAL:
2225
return type(self).combine(date.fromordinal(delta.days),
2226
time(hour, minute, second,
2227
delta.microseconds,
2228
tzinfo=self._tzinfo))
2229
raise OverflowError("result out of range")
2230
2231
__radd__ = __add__
2232
2233
def __sub__(self, other):
2234
"Subtract two datetimes, or a datetime and a timedelta."
2235
if not isinstance(other, datetime):
2236
if isinstance(other, timedelta):
2237
return self + -other
2238
return NotImplemented
2239
2240
days1 = self.toordinal()
2241
days2 = other.toordinal()
2242
secs1 = self._second + self._minute * 60 + self._hour * 3600
2243
secs2 = other._second + other._minute * 60 + other._hour * 3600
2244
base = timedelta(days1 - days2,
2245
secs1 - secs2,
2246
self._microsecond - other._microsecond)
2247
if self._tzinfo is other._tzinfo:
2248
return base
2249
myoff = self.utcoffset()
2250
otoff = other.utcoffset()
2251
if myoff == otoff:
2252
return base
2253
if myoff is None or otoff is None:
2254
raise TypeError("cannot mix naive and timezone-aware time")
2255
return base + otoff - myoff
2256
2257
def __hash__(self):
2258
if self._hashcode == -1:
2259
if self.fold:
2260
t = self.replace(fold=0)
2261
else:
2262
t = self
2263
tzoff = t.utcoffset()
2264
if tzoff is None:
2265
self._hashcode = hash(t._getstate()[0])
2266
else:
2267
days = _ymd2ord(self.year, self.month, self.day)
2268
seconds = self.hour * 3600 + self.minute * 60 + self.second
2269
self._hashcode = hash(timedelta(days, seconds, self.microsecond) - tzoff)
2270
return self._hashcode
2271
2272
# Pickle support.
2273
2274
def _getstate(self, protocol=3):
2275
yhi, ylo = divmod(self._year, 256)
2276
us2, us3 = divmod(self._microsecond, 256)
2277
us1, us2 = divmod(us2, 256)
2278
m = self._month
2279
if self._fold and protocol > 3:
2280
m += 128
2281
basestate = bytes([yhi, ylo, m, self._day,
2282
self._hour, self._minute, self._second,
2283
us1, us2, us3])
2284
if self._tzinfo is None:
2285
return (basestate,)
2286
else:
2287
return (basestate, self._tzinfo)
2288
2289
def __setstate(self, string, tzinfo):
2290
if tzinfo is not None and not isinstance(tzinfo, _tzinfo_class):
2291
raise TypeError("bad tzinfo state arg")
2292
(yhi, ylo, m, self._day, self._hour,
2293
self._minute, self._second, us1, us2, us3) = string
2294
if m > 127:
2295
self._fold = 1
2296
self._month = m - 128
2297
else:
2298
self._fold = 0
2299
self._month = m
2300
self._year = yhi * 256 + ylo
2301
self._microsecond = (((us1 << 8) | us2) << 8) | us3
2302
self._tzinfo = tzinfo
2303
2304
def __reduce_ex__(self, protocol):
2305
return (self.__class__, self._getstate(protocol))
2306
2307
def __reduce__(self):
2308
return self.__reduce_ex__(2)
2309
2310
2311
datetime.min = datetime(1, 1, 1)
2312
datetime.max = datetime(9999, 12, 31, 23, 59, 59, 999999)
2313
datetime.resolution = timedelta(microseconds=1)
2314
2315
2316
def _isoweek1monday(year):
2317
# Helper to calculate the day number of the Monday starting week 1
2318
# XXX This could be done more efficiently
2319
THURSDAY = 3
2320
firstday = _ymd2ord(year, 1, 1)
2321
firstweekday = (firstday + 6) % 7 # See weekday() above
2322
week1monday = firstday - firstweekday
2323
if firstweekday > THURSDAY:
2324
week1monday += 7
2325
return week1monday
2326
2327
2328
class timezone(tzinfo):
2329
__slots__ = '_offset', '_name'
2330
2331
# Sentinel value to disallow None
2332
_Omitted = object()
2333
def __new__(cls, offset, name=_Omitted):
2334
if not isinstance(offset, timedelta):
2335
raise TypeError("offset must be a timedelta")
2336
if name is cls._Omitted:
2337
if not offset:
2338
return cls.utc
2339
name = None
2340
elif not isinstance(name, str):
2341
raise TypeError("name must be a string")
2342
if not cls._minoffset <= offset <= cls._maxoffset:
2343
raise ValueError("offset must be a timedelta "
2344
"strictly between -timedelta(hours=24) and "
2345
"timedelta(hours=24).")
2346
return cls._create(offset, name)
2347
2348
@classmethod
2349
def _create(cls, offset, name=None):
2350
self = tzinfo.__new__(cls)
2351
self._offset = offset
2352
self._name = name
2353
return self
2354
2355
def __getinitargs__(self):
2356
"""pickle support"""
2357
if self._name is None:
2358
return (self._offset,)
2359
return (self._offset, self._name)
2360
2361
def __eq__(self, other):
2362
if isinstance(other, timezone):
2363
return self._offset == other._offset
2364
return NotImplemented
2365
2366
def __hash__(self):
2367
return hash(self._offset)
2368
2369
def __repr__(self):
2370
"""Convert to formal string, for repr().
2371
2372
>>> tz = timezone.utc
2373
>>> repr(tz)
2374
'datetime.timezone.utc'
2375
>>> tz = timezone(timedelta(hours=-5), 'EST')
2376
>>> repr(tz)
2377
"datetime.timezone(datetime.timedelta(-1, 68400), 'EST')"
2378
"""
2379
if self is self.utc:
2380
return 'datetime.timezone.utc'
2381
if self._name is None:
2382
return "%s.%s(%r)" % (_get_class_module(self),
2383
self.__class__.__qualname__,
2384
self._offset)
2385
return "%s.%s(%r, %r)" % (_get_class_module(self),
2386
self.__class__.__qualname__,
2387
self._offset, self._name)
2388
2389
def __str__(self):
2390
return self.tzname(None)
2391
2392
def utcoffset(self, dt):
2393
if isinstance(dt, datetime) or dt is None:
2394
return self._offset
2395
raise TypeError("utcoffset() argument must be a datetime instance"
2396
" or None")
2397
2398
def tzname(self, dt):
2399
if isinstance(dt, datetime) or dt is None:
2400
if self._name is None:
2401
return self._name_from_offset(self._offset)
2402
return self._name
2403
raise TypeError("tzname() argument must be a datetime instance"
2404
" or None")
2405
2406
def dst(self, dt):
2407
if isinstance(dt, datetime) or dt is None:
2408
return None
2409
raise TypeError("dst() argument must be a datetime instance"
2410
" or None")
2411
2412
def fromutc(self, dt):
2413
if isinstance(dt, datetime):
2414
if dt.tzinfo is not self:
2415
raise ValueError("fromutc: dt.tzinfo "
2416
"is not self")
2417
return dt + self._offset
2418
raise TypeError("fromutc() argument must be a datetime instance"
2419
" or None")
2420
2421
_maxoffset = timedelta(hours=24, microseconds=-1)
2422
_minoffset = -_maxoffset
2423
2424
@staticmethod
2425
def _name_from_offset(delta):
2426
if not delta:
2427
return 'UTC'
2428
if delta < timedelta(0):
2429
sign = '-'
2430
delta = -delta
2431
else:
2432
sign = '+'
2433
hours, rest = divmod(delta, timedelta(hours=1))
2434
minutes, rest = divmod(rest, timedelta(minutes=1))
2435
seconds = rest.seconds
2436
microseconds = rest.microseconds
2437
if microseconds:
2438
return (f'UTC{sign}{hours:02d}:{minutes:02d}:{seconds:02d}'
2439
f'.{microseconds:06d}')
2440
if seconds:
2441
return f'UTC{sign}{hours:02d}:{minutes:02d}:{seconds:02d}'
2442
return f'UTC{sign}{hours:02d}:{minutes:02d}'
2443
2444
UTC = timezone.utc = timezone._create(timedelta(0))
2445
2446
# bpo-37642: These attributes are rounded to the nearest minute for backwards
2447
# compatibility, even though the constructor will accept a wider range of
2448
# values. This may change in the future.
2449
timezone.min = timezone._create(-timedelta(hours=23, minutes=59))
2450
timezone.max = timezone._create(timedelta(hours=23, minutes=59))
2451
_EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)
2452
2453
# Some time zone algebra. For a datetime x, let
2454
# x.n = x stripped of its timezone -- its naive time.
2455
# x.o = x.utcoffset(), and assuming that doesn't raise an exception or
2456
# return None
2457
# x.d = x.dst(), and assuming that doesn't raise an exception or
2458
# return None
2459
# x.s = x's standard offset, x.o - x.d
2460
#
2461
# Now some derived rules, where k is a duration (timedelta).
2462
#
2463
# 1. x.o = x.s + x.d
2464
# This follows from the definition of x.s.
2465
#
2466
# 2. If x and y have the same tzinfo member, x.s = y.s.
2467
# This is actually a requirement, an assumption we need to make about
2468
# sane tzinfo classes.
2469
#
2470
# 3. The naive UTC time corresponding to x is x.n - x.o.
2471
# This is again a requirement for a sane tzinfo class.
2472
#
2473
# 4. (x+k).s = x.s
2474
# This follows from #2, and that datetime.timetz+timedelta preserves tzinfo.
2475
#
2476
# 5. (x+k).n = x.n + k
2477
# Again follows from how arithmetic is defined.
2478
#
2479
# Now we can explain tz.fromutc(x). Let's assume it's an interesting case
2480
# (meaning that the various tzinfo methods exist, and don't blow up or return
2481
# None when called).
2482
#
2483
# The function wants to return a datetime y with timezone tz, equivalent to x.
2484
# x is already in UTC.
2485
#
2486
# By #3, we want
2487
#
2488
# y.n - y.o = x.n [1]
2489
#
2490
# The algorithm starts by attaching tz to x.n, and calling that y. So
2491
# x.n = y.n at the start. Then it wants to add a duration k to y, so that [1]
2492
# becomes true; in effect, we want to solve [2] for k:
2493
#
2494
# (y+k).n - (y+k).o = x.n [2]
2495
#
2496
# By #1, this is the same as
2497
#
2498
# (y+k).n - ((y+k).s + (y+k).d) = x.n [3]
2499
#
2500
# By #5, (y+k).n = y.n + k, which equals x.n + k because x.n=y.n at the start.
2501
# Substituting that into [3],
2502
#
2503
# x.n + k - (y+k).s - (y+k).d = x.n; the x.n terms cancel, leaving
2504
# k - (y+k).s - (y+k).d = 0; rearranging,
2505
# k = (y+k).s - (y+k).d; by #4, (y+k).s == y.s, so
2506
# k = y.s - (y+k).d
2507
#
2508
# On the RHS, (y+k).d can't be computed directly, but y.s can be, and we
2509
# approximate k by ignoring the (y+k).d term at first. Note that k can't be
2510
# very large, since all offset-returning methods return a duration of magnitude
2511
# less than 24 hours. For that reason, if y is firmly in std time, (y+k).d must
2512
# be 0, so ignoring it has no consequence then.
2513
#
2514
# In any case, the new value is
2515
#
2516
# z = y + y.s [4]
2517
#
2518
# It's helpful to step back at look at [4] from a higher level: it's simply
2519
# mapping from UTC to tz's standard time.
2520
#
2521
# At this point, if
2522
#
2523
# z.n - z.o = x.n [5]
2524
#
2525
# we have an equivalent time, and are almost done. The insecurity here is
2526
# at the start of daylight time. Picture US Eastern for concreteness. The wall
2527
# time jumps from 1:59 to 3:00, and wall hours of the form 2:MM don't make good
2528
# sense then. The docs ask that an Eastern tzinfo class consider such a time to
2529
# be EDT (because it's "after 2"), which is a redundant spelling of 1:MM EST
2530
# on the day DST starts. We want to return the 1:MM EST spelling because that's
2531
# the only spelling that makes sense on the local wall clock.
2532
#
2533
# In fact, if [5] holds at this point, we do have the standard-time spelling,
2534
# but that takes a bit of proof. We first prove a stronger result. What's the
2535
# difference between the LHS and RHS of [5]? Let
2536
#
2537
# diff = x.n - (z.n - z.o) [6]
2538
#
2539
# Now
2540
# z.n = by [4]
2541
# (y + y.s).n = by #5
2542
# y.n + y.s = since y.n = x.n
2543
# x.n + y.s = since z and y are have the same tzinfo member,
2544
# y.s = z.s by #2
2545
# x.n + z.s
2546
#
2547
# Plugging that back into [6] gives
2548
#
2549
# diff =
2550
# x.n - ((x.n + z.s) - z.o) = expanding
2551
# x.n - x.n - z.s + z.o = cancelling
2552
# - z.s + z.o = by #2
2553
# z.d
2554
#
2555
# So diff = z.d.
2556
#
2557
# If [5] is true now, diff = 0, so z.d = 0 too, and we have the standard-time
2558
# spelling we wanted in the endcase described above. We're done. Contrarily,
2559
# if z.d = 0, then we have a UTC equivalent, and are also done.
2560
#
2561
# If [5] is not true now, diff = z.d != 0, and z.d is the offset we need to
2562
# add to z (in effect, z is in tz's standard time, and we need to shift the
2563
# local clock into tz's daylight time).
2564
#
2565
# Let
2566
#
2567
# z' = z + z.d = z + diff [7]
2568
#
2569
# and we can again ask whether
2570
#
2571
# z'.n - z'.o = x.n [8]
2572
#
2573
# If so, we're done. If not, the tzinfo class is insane, according to the
2574
# assumptions we've made. This also requires a bit of proof. As before, let's
2575
# compute the difference between the LHS and RHS of [8] (and skipping some of
2576
# the justifications for the kinds of substitutions we've done several times
2577
# already):
2578
#
2579
# diff' = x.n - (z'.n - z'.o) = replacing z'.n via [7]
2580
# x.n - (z.n + diff - z'.o) = replacing diff via [6]
2581
# x.n - (z.n + x.n - (z.n - z.o) - z'.o) =
2582
# x.n - z.n - x.n + z.n - z.o + z'.o = cancel x.n
2583
# - z.n + z.n - z.o + z'.o = cancel z.n
2584
# - z.o + z'.o = #1 twice
2585
# -z.s - z.d + z'.s + z'.d = z and z' have same tzinfo
2586
# z'.d - z.d
2587
#
2588
# So z' is UTC-equivalent to x iff z'.d = z.d at this point. If they are equal,
2589
# we've found the UTC-equivalent so are done. In fact, we stop with [7] and
2590
# return z', not bothering to compute z'.d.
2591
#
2592
# How could z.d and z'd differ? z' = z + z.d [7], so merely moving z' by
2593
# a dst() offset, and starting *from* a time already in DST (we know z.d != 0),
2594
# would have to change the result dst() returns: we start in DST, and moving
2595
# a little further into it takes us out of DST.
2596
#
2597
# There isn't a sane case where this can happen. The closest it gets is at
2598
# the end of DST, where there's an hour in UTC with no spelling in a hybrid
2599
# tzinfo class. In US Eastern, that's 5:MM UTC = 0:MM EST = 1:MM EDT. During
2600
# that hour, on an Eastern clock 1:MM is taken as being in standard time (6:MM
2601
# UTC) because the docs insist on that, but 0:MM is taken as being in daylight
2602
# time (4:MM UTC). There is no local time mapping to 5:MM UTC. The local
2603
# clock jumps from 1:59 back to 1:00 again, and repeats the 1:MM hour in
2604
# standard time. Since that's what the local clock *does*, we want to map both
2605
# UTC hours 5:MM and 6:MM to 1:MM Eastern. The result is ambiguous
2606
# in local time, but so it goes -- it's the way the local clock works.
2607
#
2608
# When x = 5:MM UTC is the input to this algorithm, x.o=0, y.o=-5 and y.d=0,
2609
# so z=0:MM. z.d=60 (minutes) then, so [5] doesn't hold and we keep going.
2610
# z' = z + z.d = 1:MM then, and z'.d=0, and z'.d - z.d = -60 != 0 so [8]
2611
# (correctly) concludes that z' is not UTC-equivalent to x.
2612
#
2613
# Because we know z.d said z was in daylight time (else [5] would have held and
2614
# we would have stopped then), and we know z.d != z'.d (else [8] would have held
2615
# and we have stopped then), and there are only 2 possible values dst() can
2616
# return in Eastern, it follows that z'.d must be 0 (which it is in the example,
2617
# but the reasoning doesn't depend on the example -- it depends on there being
2618
# two possible dst() outcomes, one zero and the other non-zero). Therefore
2619
# z' must be in standard time, and is the spelling we want in this case.
2620
#
2621
# Note again that z' is not UTC-equivalent as far as the hybrid tzinfo class is
2622
# concerned (because it takes z' as being in standard time rather than the
2623
# daylight time we intend here), but returning it gives the real-life "local
2624
# clock repeats an hour" behavior when mapping the "unspellable" UTC hour into
2625
# tz.
2626
#
2627
# When the input is 6:MM, z=1:MM and z.d=0, and we stop at once, again with
2628
# the 1:MM standard time spelling we want.
2629
#
2630
# So how can this break? One of the assumptions must be violated. Two
2631
# possibilities:
2632
#
2633
# 1) [2] effectively says that y.s is invariant across all y belong to a given
2634
# time zone. This isn't true if, for political reasons or continental drift,
2635
# a region decides to change its base offset from UTC.
2636
#
2637
# 2) There may be versions of "double daylight" time where the tail end of
2638
# the analysis gives up a step too early. I haven't thought about that
2639
# enough to say.
2640
#
2641
# In any case, it's clear that the default fromutc() is strong enough to handle
2642
# "almost all" time zones: so long as the standard offset is invariant, it
2643
# doesn't matter if daylight time transition points change from year to year, or
2644
# if daylight time is skipped in some years; it doesn't matter how large or
2645
# small dst() may get within its bounds; and it doesn't even matter if some
2646
# perverse time zone returns a negative dst()). So a breaking case must be
2647
# pretty bizarre, and a tzinfo subclass can override fromutc() if it is.
2648
2649