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