dycw-utilities 0.136.10__py3-none-any.whl → 0.138.0__py3-none-any.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.
- {dycw_utilities-0.136.10.dist-info → dycw_utilities-0.138.0.dist-info}/METADATA +1 -1
- {dycw_utilities-0.136.10.dist-info → dycw_utilities-0.138.0.dist-info}/RECORD +16 -16
- utilities/__init__.py +1 -1
- utilities/click.py +47 -12
- utilities/fpdf2.py +2 -2
- utilities/hypothesis.py +56 -30
- utilities/logging.py +8 -3
- utilities/orjson.py +17 -1
- utilities/parse.py +6 -3
- utilities/pyinstrument.py +2 -2
- utilities/traceback.py +4 -3
- utilities/typed_settings.py +5 -0
- utilities/types.py +6 -0
- utilities/whenever.py +14 -143
- {dycw_utilities-0.136.10.dist-info → dycw_utilities-0.138.0.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.136.10.dist-info → dycw_utilities-0.138.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,11 +1,11 @@
|
|
1
|
-
utilities/__init__.py,sha256
|
1
|
+
utilities/__init__.py,sha256=-LD1QyR_P_VpkzFmNAkfoqYhCifIwYOuBQGhhpTr7lI,60
|
2
2
|
utilities/aiolimiter.py,sha256=mD0wEiqMgwpty4XTbawFpnkkmJS6R4JRsVXFUaoitSU,628
|
3
3
|
utilities/altair.py,sha256=HeZBVUocjkrTNwwKrClppsIqgNFF-ykv05HfZSoHYno,9104
|
4
4
|
utilities/asyncio.py,sha256=dcGeKQzjLBXxKzZkVIk5oZsFXEcynVbRB9iNB5XEDZk,38526
|
5
5
|
utilities/atomicwrites.py,sha256=geFjn9Pwn-tTrtoGjDDxWli9NqbYfy3gGL6ZBctiqSo,5393
|
6
6
|
utilities/atools.py,sha256=9im2g8OCf-Iynqa8bAv8N0Ycj9QvrJmGO7yLCZEdgII,986
|
7
7
|
utilities/cachetools.py,sha256=v1-9sXHLdOLiwmkq6NB0OUbxeKBuVVN6wmAWefWoaHI,2744
|
8
|
-
utilities/click.py,sha256=
|
8
|
+
utilities/click.py,sha256=PToheYTcKmBwKOMHasZ36CKcMNmM005nKdvOCFUo5dw,16989
|
9
9
|
utilities/concurrent.py,sha256=ZdhcNeBl1-HaAPY3h7bZ5ccuYdfdq2ATHplvZdnzlhk,2858
|
10
10
|
utilities/contextlib.py,sha256=lpaLJBy3X0UGLWjM98jkQZZq8so4fRmoK-Bheq0uOW4,1027
|
11
11
|
utilities/contextvars.py,sha256=RsSGGrbQqqZ67rOydnM7WWIsM2lIE31UHJLejnHJPWY,505
|
@@ -16,14 +16,14 @@ utilities/enum.py,sha256=IEPMGiNJBcofGPuoGZErf4bMNSBE4SLs32nvm2r81h0,5791
|
|
16
16
|
utilities/errors.py,sha256=nC7ZYtxxDBMfrTHtT_MByBfup_wfGQFRo3eDt-0ZPe8,1045
|
17
17
|
utilities/eventkit.py,sha256=cV76NIHKDPyrhnZRuhjCXse5CQqgRGXZ7p7Gon4Mu2g,12646
|
18
18
|
utilities/fastapi.py,sha256=E8T2J1-N_RbpkN4czthU6NPIxAZDzxy-k_WGJaxeJ48,2671
|
19
|
-
utilities/fpdf2.py,sha256=
|
19
|
+
utilities/fpdf2.py,sha256=776PkEX5xEK-whFOzqaVaQVHPy1Xf01kCSyj7TEp80g,1886
|
20
20
|
utilities/functions.py,sha256=qefAfW0zz7OEiRuBtKF-3tI3NaufcwAULRIFv24gZ2Q,28533
|
21
21
|
utilities/functools.py,sha256=I00ru2gQPakZw2SHVeKIKXfTv741655s6HI0lUoE0D4,1552
|
22
22
|
utilities/getpass.py,sha256=DfN5UgMAtFCqS3dSfFHUfqIMZX2shXvwphOz_6J6f6A,103
|
23
23
|
utilities/git.py,sha256=oi7-_l5e9haSANSCvQw25ufYGoNahuUPHAZ6114s3JQ,1191
|
24
24
|
utilities/hashlib.py,sha256=SVTgtguur0P4elppvzOBbLEjVM3Pea0eWB61yg2ilxo,309
|
25
25
|
utilities/http.py,sha256=WcahTcKYRtZ04WXQoWt5EGCgFPcyHD3EJdlMfxvDt-0,946
|
26
|
-
utilities/hypothesis.py,sha256=
|
26
|
+
utilities/hypothesis.py,sha256=2VJy9Pxqjy4_ldAwZAR6XEGuG2ZmAN1ycpA_VIGnKew,38819
|
27
27
|
utilities/importlib.py,sha256=mV1xT_O_zt_GnZZ36tl3xOmMaN_3jErDWY54fX39F6Y,429
|
28
28
|
utilities/inflect.py,sha256=DbqB5Q9FbRGJ1NbvEiZBirRMxCxgrz91zy5jCO9ZIs0,347
|
29
29
|
utilities/ipython.py,sha256=V2oMYHvEKvlNBzxDXdLvKi48oUq2SclRg5xasjaXStw,763
|
@@ -31,7 +31,7 @@ utilities/iterables.py,sha256=wlcm0PS2fKG-H0r0k367NOLrryMbfXLlwzUeAmBSvv8,43392
|
|
31
31
|
utilities/jupyter.py,sha256=ft5JA7fBxXKzP-L9W8f2-wbF0QeYc_2uLQNFDVk4Z-M,2917
|
32
32
|
utilities/libcst.py,sha256=XTT8cCYAYfI9ZIdxjiTCqbF45cN-viDNDa7GE5mR2T4,5615
|
33
33
|
utilities/lightweight_charts.py,sha256=JrkrAZMo6JID2Eoc9QCc05Y_pK4l2zsApIhmii1z2Ig,2764
|
34
|
-
utilities/logging.py,sha256=
|
34
|
+
utilities/logging.py,sha256=O2IApkDJQ1EqZHCvxp0YHrML7a8UuORtb66Hou_vPfw,17923
|
35
35
|
utilities/luigi.py,sha256=wK7cB3y8NXeSa8d6r_yTKRmjMguNmIPmy52yg10vPaI,4774
|
36
36
|
utilities/math.py,sha256=_6vrDyjtaqE_OFE-F2DNWrDG_J_kMl3nFAJsok9v_bY,26862
|
37
37
|
utilities/memory_profiler.py,sha256=XzN56jDCa5aqXS_DxEjb_K4L6aIWh_5zyKi6OhcIxw0,853
|
@@ -40,9 +40,9 @@ utilities/more_itertools.py,sha256=NEGXDT8COaxziaJFTiXYKX0gffwfKhCAMrq-Kgh0w8c,1
|
|
40
40
|
utilities/numpy.py,sha256=Xn23sA2ZbVNqwUYEgNJD3XBYH6IbCri_WkHSNhg3NkY,26122
|
41
41
|
utilities/operator.py,sha256=fZM2DBZdjtU8Lh9Z-eHC4ktNr0vO3JfX8hXgIYwdvBI,3826
|
42
42
|
utilities/optuna.py,sha256=C-fhWYiXHVPo1l8QctYkFJ4DyhbSrGorzP1dJb_qvd8,1933
|
43
|
-
utilities/orjson.py,sha256=
|
43
|
+
utilities/orjson.py,sha256=WWV2QukCIuwT8OAOtmKhLhxezXPVbeA_fQCucmGmbRA,37106
|
44
44
|
utilities/os.py,sha256=yMNAKMyY8oFgQ1yN3TQYnwa5-A_FXz4tCDbhIctQHSs,3736
|
45
|
-
utilities/parse.py,sha256=
|
45
|
+
utilities/parse.py,sha256=JcJn5yXKhIWXBCwgBdPsyu7Hvcuw6kyEdqvaebCaI9k,17951
|
46
46
|
utilities/pathlib.py,sha256=jCFPZm4rBKylEva9wDVTwQlTTVKMepu92WrTpoGa438,3248
|
47
47
|
utilities/period.py,sha256=6jEff_qAiE7xdFaQ1DnKgNf10D2wHhzt7hQXCBoKlgc,6842
|
48
48
|
utilities/pickle.py,sha256=MBT2xZCsv0pH868IXLGKnlcqNx2IRVKYNpRcqiQQqxw,653
|
@@ -54,7 +54,7 @@ utilities/pqdm.py,sha256=BTsYPtbKQWwX-iXF4qCkfPG7DPxIB54J989n83bXrIo,3092
|
|
54
54
|
utilities/psutil.py,sha256=0j4YxtVb8VjaaKKiHg6UEK95SUPkEcENgPtLgPJsNv0,3760
|
55
55
|
utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
56
56
|
utilities/pydantic.py,sha256=CmxCi4sukeHM3JGjJ1Rbp8UAvcx4MZapLg10mFYJ-nk,1771
|
57
|
-
utilities/pyinstrument.py,sha256=
|
57
|
+
utilities/pyinstrument.py,sha256=HrTGJ2niUAHUFMSN3im9BeedC0faq2DqoFccDxPpsP8,884
|
58
58
|
utilities/pytest.py,sha256=vv5ZpePZS6pBxl5jY3XQR5SnAhgd4f0sSMyRsvHWg60,8086
|
59
59
|
utilities/pytest_regressions.py,sha256=YI55B7EtLjhz7zPJZ6NK9bWrxrKCKabWZJe1cwcbA5o,5082
|
60
60
|
utilities/python_dotenv.py,sha256=dYooRYwqrvhSoZWuiVbCiKUWiS-M5b5yv2zDWGYPEvI,3209
|
@@ -76,19 +76,19 @@ utilities/tempfile.py,sha256=VqmZJAhTJ1OaVywFzk5eqROV8iJbW9XQ_QYAV0bpdRo,1384
|
|
76
76
|
utilities/text.py,sha256=ymBFlP_cA8OgNnZRVNs7FAh7OG8HxE6YkiLEMZv5g_A,11297
|
77
77
|
utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
|
78
78
|
utilities/timer.py,sha256=oXfTii6ymu57niP0BDGZjFD55LEHi2a19kqZKiTgaFQ,2588
|
79
|
-
utilities/traceback.py,sha256=
|
80
|
-
utilities/typed_settings.py,sha256=
|
81
|
-
utilities/types.py,sha256
|
79
|
+
utilities/traceback.py,sha256=h9yt4C2XkGHXW9e9bz93IGluhIR_Qil8zu5dhC8DXY0,8884
|
80
|
+
utilities/typed_settings.py,sha256=C2i2VK62us_Z5jjca9NLmDFiT3_mkoSCwgU6FDtDhMI,4501
|
81
|
+
utilities/types.py,sha256=-_pXQvmpJhTgEbI13N_zZWINdt0ODBpZ3WdJQUML9GA,17397
|
82
82
|
utilities/typing.py,sha256=Z-_XDaWyT_6wIo3qfNK-hvRlzxP2Jxa9PgXzm5rDYRA,13790
|
83
83
|
utilities/tzdata.py,sha256=fgNVj66yUbCSI_-vrRVzSD3gtf-L_8IEJEPjP_Jel5Y,266
|
84
84
|
utilities/tzlocal.py,sha256=KyCXEgCTjqGFx-389JdTuhMRUaT06U1RCMdWoED-qro,728
|
85
85
|
utilities/uuid.py,sha256=32p7DGHGM2Btx6PcBvCZvERSWbpupMXqx6FppPoSoTU,612
|
86
86
|
utilities/version.py,sha256=ufhJMmI6KPs1-3wBI71aj5wCukd3sP_m11usLe88DNA,5117
|
87
87
|
utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
88
|
-
utilities/whenever.py,sha256=
|
88
|
+
utilities/whenever.py,sha256=4K_DfnPvQ2iddgKmTd3aa1RT3ZTh3bU4R0xe41l9goI,20099
|
89
89
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
90
90
|
utilities/zoneinfo.py,sha256=oEH-nL3t4h9uawyZqWDtNtDAl6M-CLpLYGI_nI6DulM,1971
|
91
|
-
dycw_utilities-0.
|
92
|
-
dycw_utilities-0.
|
93
|
-
dycw_utilities-0.
|
94
|
-
dycw_utilities-0.
|
91
|
+
dycw_utilities-0.138.0.dist-info/METADATA,sha256=nExJGsgLn_Rn-_aeZ2risr4iR1Xuout7dLx9QEb8qbI,1637
|
92
|
+
dycw_utilities-0.138.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
93
|
+
dycw_utilities-0.138.0.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
94
|
+
dycw_utilities-0.138.0.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/click.py
CHANGED
@@ -16,7 +16,7 @@ from utilities.functions import EnsureStrError, ensure_str, get_class_name
|
|
16
16
|
from utilities.iterables import is_iterable_not_str
|
17
17
|
from utilities.parse import ParseObjectError, parse_object
|
18
18
|
from utilities.text import split_str
|
19
|
-
from utilities.whenever import FreqLike, _FreqParseError
|
19
|
+
from utilities.whenever import FreqLike, _FreqParseError
|
20
20
|
|
21
21
|
if TYPE_CHECKING:
|
22
22
|
from collections.abc import Iterable, Sequence
|
@@ -29,12 +29,13 @@ if TYPE_CHECKING:
|
|
29
29
|
IPv4AddressLike,
|
30
30
|
IPv6AddressLike,
|
31
31
|
MaybeStr,
|
32
|
+
MonthDayLike,
|
32
33
|
PlainDateTimeLike,
|
33
34
|
TimeDeltaLike,
|
34
35
|
TimeLike,
|
36
|
+
YearMonthLike,
|
35
37
|
ZonedDateTimeLike,
|
36
38
|
)
|
37
|
-
from utilities.whenever import MonthLike
|
38
39
|
|
39
40
|
|
40
41
|
FilePath = click.Path(file_okay=True, dir_okay=False, path_type=pathlib.Path)
|
@@ -252,10 +253,10 @@ class IPv6Address(ParamType):
|
|
252
253
|
assert_never(never)
|
253
254
|
|
254
255
|
|
255
|
-
class
|
256
|
-
"""A month-
|
256
|
+
class MonthDay(ParamType):
|
257
|
+
"""A month-day parameter."""
|
257
258
|
|
258
|
-
name = "month"
|
259
|
+
name = "month-day"
|
259
260
|
|
260
261
|
@override
|
261
262
|
def __repr__(self) -> str:
|
@@ -263,13 +264,19 @@ class Month(ParamType):
|
|
263
264
|
|
264
265
|
@override
|
265
266
|
def convert(
|
266
|
-
self, value:
|
267
|
-
) ->
|
268
|
-
"""Convert a value into the `
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
267
|
+
self, value: MonthDayLike, param: Parameter | None, ctx: Context | None
|
268
|
+
) -> whenever.MonthDay:
|
269
|
+
"""Convert a value into the `MonthDay` type."""
|
270
|
+
match value:
|
271
|
+
case whenever.MonthDay():
|
272
|
+
return value
|
273
|
+
case str():
|
274
|
+
try:
|
275
|
+
return whenever.MonthDay.parse_common_iso(value)
|
276
|
+
except ValueError as error:
|
277
|
+
self.fail(str(error), param, ctx)
|
278
|
+
case _ as never:
|
279
|
+
assert_never(never)
|
273
280
|
|
274
281
|
|
275
282
|
class PlainDateTime(ParamType):
|
@@ -350,6 +357,32 @@ class TimeDelta(ParamType):
|
|
350
357
|
assert_never(never)
|
351
358
|
|
352
359
|
|
360
|
+
class YearMonth(ParamType):
|
361
|
+
"""A year-month parameter."""
|
362
|
+
|
363
|
+
name = "year-month"
|
364
|
+
|
365
|
+
@override
|
366
|
+
def __repr__(self) -> str:
|
367
|
+
return self.name.upper()
|
368
|
+
|
369
|
+
@override
|
370
|
+
def convert(
|
371
|
+
self, value: YearMonthLike, param: Parameter | None, ctx: Context | None
|
372
|
+
) -> whenever.YearMonth:
|
373
|
+
"""Convert a value into the `YearMonth` type."""
|
374
|
+
match value:
|
375
|
+
case whenever.YearMonth():
|
376
|
+
return value
|
377
|
+
case str():
|
378
|
+
try:
|
379
|
+
return whenever.YearMonth.parse_common_iso(value)
|
380
|
+
except ValueError as error:
|
381
|
+
self.fail(str(error), param, ctx)
|
382
|
+
case _ as never:
|
383
|
+
assert_never(never)
|
384
|
+
|
385
|
+
|
353
386
|
class ZonedDateTime(ParamType):
|
354
387
|
"""A zoned-datetime-valued parameter."""
|
355
388
|
|
@@ -559,8 +592,10 @@ __all__ = [
|
|
559
592
|
"ListEnums",
|
560
593
|
"ListParameter",
|
561
594
|
"ListStrs",
|
595
|
+
"MonthDay",
|
562
596
|
"PlainDateTime",
|
563
597
|
"Time",
|
564
598
|
"TimeDelta",
|
599
|
+
"YearMonth",
|
565
600
|
"ZonedDateTime",
|
566
601
|
]
|
utilities/fpdf2.py
CHANGED
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, override
|
|
6
6
|
from fpdf import FPDF
|
7
7
|
from fpdf.enums import XPos, YPos
|
8
8
|
|
9
|
-
from utilities.whenever import format_compact,
|
9
|
+
from utilities.whenever import format_compact, get_now, to_local_plain
|
10
10
|
|
11
11
|
if TYPE_CHECKING:
|
12
12
|
from collections.abc import Iterator
|
@@ -47,7 +47,7 @@ def yield_pdf(*, header: str | None = None) -> Iterator[_BasePDF]:
|
|
47
47
|
def footer(self) -> None:
|
48
48
|
self.set_y(-15)
|
49
49
|
self.set_font(family="Helvetica", style="I", size=8)
|
50
|
-
page_no, now = (self.page_no(), format_compact(
|
50
|
+
page_no, now = (self.page_no(), format_compact(to_local_plain(get_now())))
|
51
51
|
text = f"page {page_no}/{{}}; {now}"
|
52
52
|
_ = self.cell(
|
53
53
|
w=0,
|
utilities/hypothesis.py
CHANGED
@@ -38,12 +38,14 @@ from whenever import (
|
|
38
38
|
Date,
|
39
39
|
DateDelta,
|
40
40
|
DateTimeDelta,
|
41
|
+
MonthDay,
|
41
42
|
PlainDateTime,
|
42
43
|
RepeatedTime,
|
43
44
|
SkippedTime,
|
44
45
|
Time,
|
45
46
|
TimeDelta,
|
46
47
|
TimeZoneNotFoundError,
|
48
|
+
YearMonth,
|
47
49
|
ZonedDateTime,
|
48
50
|
)
|
49
51
|
|
@@ -76,8 +78,6 @@ from utilities.whenever import (
|
|
76
78
|
DATE_DELTA_MIN,
|
77
79
|
DATE_DELTA_PARSABLE_MAX,
|
78
80
|
DATE_DELTA_PARSABLE_MIN,
|
79
|
-
DATE_MAX,
|
80
|
-
DATE_MIN,
|
81
81
|
DATE_TIME_DELTA_MAX,
|
82
82
|
DATE_TIME_DELTA_MIN,
|
83
83
|
DATE_TIME_DELTA_PARSABLE_MAX,
|
@@ -85,16 +85,9 @@ from utilities.whenever import (
|
|
85
85
|
DATE_TWO_DIGIT_YEAR_MAX,
|
86
86
|
DATE_TWO_DIGIT_YEAR_MIN,
|
87
87
|
DAY,
|
88
|
-
MONTH_MAX,
|
89
|
-
MONTH_MIN,
|
90
|
-
PLAIN_DATE_TIME_MAX,
|
91
|
-
PLAIN_DATE_TIME_MIN,
|
92
88
|
TIME_DELTA_MAX,
|
93
89
|
TIME_DELTA_MIN,
|
94
|
-
TIME_MAX,
|
95
|
-
TIME_MIN,
|
96
90
|
Freq,
|
97
|
-
Month,
|
98
91
|
to_date_time_delta,
|
99
92
|
to_days,
|
100
93
|
to_nanos,
|
@@ -255,14 +248,14 @@ def dates(
|
|
255
248
|
min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
|
256
249
|
match min_value_:
|
257
250
|
case None:
|
258
|
-
min_value_ =
|
251
|
+
min_value_ = Date.MIN
|
259
252
|
case Date():
|
260
253
|
...
|
261
254
|
case _ as never:
|
262
255
|
assert_never(never)
|
263
256
|
match max_value_:
|
264
257
|
case None:
|
265
|
-
max_value_ =
|
258
|
+
max_value_ = Date.MAX
|
266
259
|
case Date():
|
267
260
|
...
|
268
261
|
case _ as never:
|
@@ -695,33 +688,32 @@ def lists_fixed_length[T](
|
|
695
688
|
|
696
689
|
|
697
690
|
@composite
|
698
|
-
def
|
691
|
+
def month_days(
|
699
692
|
draw: DrawFn,
|
700
693
|
/,
|
701
694
|
*,
|
702
|
-
min_value: MaybeSearchStrategy[
|
703
|
-
max_value: MaybeSearchStrategy[
|
704
|
-
|
705
|
-
|
706
|
-
"""Strategy for generating months."""
|
695
|
+
min_value: MaybeSearchStrategy[MonthDay | None] = None,
|
696
|
+
max_value: MaybeSearchStrategy[MonthDay | None] = None,
|
697
|
+
) -> MonthDay:
|
698
|
+
"""Strategy for generating month-days."""
|
707
699
|
min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
|
708
700
|
match min_value_:
|
709
701
|
case None:
|
710
|
-
min_value_ =
|
711
|
-
case
|
702
|
+
min_value_ = MonthDay.MIN
|
703
|
+
case MonthDay():
|
712
704
|
...
|
713
705
|
case _ as never:
|
714
706
|
assert_never(never)
|
715
707
|
match max_value_:
|
716
708
|
case None:
|
717
|
-
max_value_ =
|
718
|
-
case
|
709
|
+
max_value_ = MonthDay.MAX
|
710
|
+
case MonthDay():
|
719
711
|
...
|
720
712
|
case _ as never:
|
721
713
|
assert_never(never)
|
722
|
-
min_date, max_date = [m.
|
723
|
-
date = draw(dates(min_value=min_date, max_value=max_date
|
724
|
-
return
|
714
|
+
min_date, max_date = [m.in_year(2000) for m in [min_value_, max_value_]]
|
715
|
+
date = draw(dates(min_value=min_date, max_value=max_date))
|
716
|
+
return date.month_day()
|
725
717
|
|
726
718
|
|
727
719
|
##
|
@@ -843,14 +835,14 @@ def plain_datetimes(
|
|
843
835
|
min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
|
844
836
|
match min_value_:
|
845
837
|
case None:
|
846
|
-
min_value_ =
|
838
|
+
min_value_ = PlainDateTime.MIN
|
847
839
|
case PlainDateTime():
|
848
840
|
...
|
849
841
|
case _ as never:
|
850
842
|
assert_never(never)
|
851
843
|
match max_value_:
|
852
844
|
case None:
|
853
|
-
max_value_ =
|
845
|
+
max_value_ = PlainDateTime.MAX
|
854
846
|
case PlainDateTime():
|
855
847
|
...
|
856
848
|
case _ as never:
|
@@ -1219,14 +1211,14 @@ def times(
|
|
1219
1211
|
min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
|
1220
1212
|
match min_value_:
|
1221
1213
|
case None:
|
1222
|
-
min_value_ =
|
1214
|
+
min_value_ = Time()
|
1223
1215
|
case Time():
|
1224
1216
|
...
|
1225
1217
|
case _ as never:
|
1226
1218
|
assert_never(never)
|
1227
1219
|
match max_value_:
|
1228
1220
|
case None:
|
1229
|
-
max_value_ =
|
1221
|
+
max_value_ = Time.MAX
|
1230
1222
|
case Time():
|
1231
1223
|
...
|
1232
1224
|
case _ as never:
|
@@ -1308,6 +1300,39 @@ def versions(draw: DrawFn, /, *, suffix: MaybeSearchStrategy[bool] = False) -> V
|
|
1308
1300
|
##
|
1309
1301
|
|
1310
1302
|
|
1303
|
+
@composite
|
1304
|
+
def year_months(
|
1305
|
+
draw: DrawFn,
|
1306
|
+
/,
|
1307
|
+
*,
|
1308
|
+
min_value: MaybeSearchStrategy[YearMonth | None] = None,
|
1309
|
+
max_value: MaybeSearchStrategy[YearMonth | None] = None,
|
1310
|
+
two_digit: MaybeSearchStrategy[bool] = False,
|
1311
|
+
) -> YearMonth:
|
1312
|
+
"""Strategy for generating months."""
|
1313
|
+
min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
|
1314
|
+
match min_value_:
|
1315
|
+
case None:
|
1316
|
+
min_value_ = YearMonth.MIN
|
1317
|
+
case YearMonth():
|
1318
|
+
...
|
1319
|
+
case _ as never:
|
1320
|
+
assert_never(never)
|
1321
|
+
match max_value_:
|
1322
|
+
case None:
|
1323
|
+
max_value_ = YearMonth.MAX
|
1324
|
+
case YearMonth():
|
1325
|
+
...
|
1326
|
+
case _ as never:
|
1327
|
+
assert_never(never)
|
1328
|
+
min_date, max_date = [m.on_day(1) for m in [min_value_, max_value_]]
|
1329
|
+
date = draw(dates(min_value=min_date, max_value=max_date, two_digit=two_digit))
|
1330
|
+
return date.year_month()
|
1331
|
+
|
1332
|
+
|
1333
|
+
##
|
1334
|
+
|
1335
|
+
|
1311
1336
|
@composite
|
1312
1337
|
def zoned_datetimes(
|
1313
1338
|
draw: DrawFn,
|
@@ -1345,7 +1370,7 @@ def zoned_datetimes(
|
|
1345
1370
|
):
|
1346
1371
|
zoned = plain.assume_tz(time_zone_.key, disambiguate="raise")
|
1347
1372
|
with assume_does_not_raise(OverflowError, match="date value out of range"):
|
1348
|
-
if not ((
|
1373
|
+
if not ((Date.MIN + DAY) <= zoned.date() <= (Date.MAX - DAY)):
|
1349
1374
|
_ = zoned.py_datetime()
|
1350
1375
|
return zoned
|
1351
1376
|
|
@@ -1373,7 +1398,7 @@ __all__ = [
|
|
1373
1398
|
"int64s",
|
1374
1399
|
"int_arrays",
|
1375
1400
|
"lists_fixed_length",
|
1376
|
-
"
|
1401
|
+
"month_days",
|
1377
1402
|
"namespace_mixins",
|
1378
1403
|
"numbers",
|
1379
1404
|
"pairs",
|
@@ -1399,5 +1424,6 @@ __all__ = [
|
|
1399
1424
|
"uint32s",
|
1400
1425
|
"uint64s",
|
1401
1426
|
"versions",
|
1427
|
+
"year_months",
|
1402
1428
|
"zoned_datetimes",
|
1403
1429
|
]
|
utilities/logging.py
CHANGED
@@ -45,7 +45,12 @@ from utilities.re import (
|
|
45
45
|
)
|
46
46
|
from utilities.sentinel import Sentinel, sentinel
|
47
47
|
from utilities.tzlocal import LOCAL_TIME_ZONE_NAME
|
48
|
-
from utilities.whenever import
|
48
|
+
from utilities.whenever import (
|
49
|
+
WheneverLogRecord,
|
50
|
+
format_compact,
|
51
|
+
get_now_local,
|
52
|
+
to_local_plain,
|
53
|
+
)
|
49
54
|
|
50
55
|
if TYPE_CHECKING:
|
51
56
|
from collections.abc import Callable, Iterable, Mapping
|
@@ -533,9 +538,9 @@ class _RotatingLogFile:
|
|
533
538
|
case int() as index, None, None:
|
534
539
|
tail = str(index)
|
535
540
|
case int() as index, None, ZonedDateTime() as end:
|
536
|
-
tail = f"{index}__{format_compact(end
|
541
|
+
tail = f"{index}__{format_compact(to_local_plain(end))}"
|
537
542
|
case int() as index, ZonedDateTime() as start, ZonedDateTime() as end:
|
538
|
-
tail = f"{index}__{format_compact(start
|
543
|
+
tail = f"{index}__{format_compact(to_local_plain(start))}__{format_compact(to_local_plain(end))}"
|
539
544
|
case _: # pragma: no cover
|
540
545
|
raise ImpossibleCaseError(
|
541
546
|
case=[f"{self.index=}", f"{self.start=}", f"{self.end=}"]
|
utilities/orjson.py
CHANGED
@@ -26,9 +26,11 @@ from whenever import (
|
|
26
26
|
Date,
|
27
27
|
DateDelta,
|
28
28
|
DateTimeDelta,
|
29
|
+
MonthDay,
|
29
30
|
PlainDateTime,
|
30
31
|
Time,
|
31
32
|
TimeDelta,
|
33
|
+
YearMonth,
|
32
34
|
ZonedDateTime,
|
33
35
|
)
|
34
36
|
|
@@ -71,6 +73,7 @@ class _Prefixes(Enum):
|
|
71
73
|
float_ = "fl"
|
72
74
|
frozenset_ = "fr"
|
73
75
|
list_ = "l"
|
76
|
+
month_day = "md"
|
74
77
|
none = "none"
|
75
78
|
path = "p"
|
76
79
|
plain_date_time = "pd"
|
@@ -81,6 +84,7 @@ class _Prefixes(Enum):
|
|
81
84
|
unserializable = "un"
|
82
85
|
uuid = "uu"
|
83
86
|
version = "v"
|
87
|
+
year_month = "ym"
|
84
88
|
zoned_date_time = "zd"
|
85
89
|
|
86
90
|
|
@@ -172,6 +176,8 @@ def _pre_process(
|
|
172
176
|
if MIN_INT64 <= int_ <= MAX_INT64:
|
173
177
|
return int_
|
174
178
|
raise _SerializeIntegerError(obj=int_)
|
179
|
+
case MonthDay() as month_day:
|
180
|
+
return f"[{_Prefixes.month_day.value}]{month_day!s}"
|
175
181
|
case Path() as path:
|
176
182
|
return f"[{_Prefixes.path.value}]{path!s}"
|
177
183
|
case PlainDateTime() as datetime:
|
@@ -187,7 +193,9 @@ def _pre_process(
|
|
187
193
|
case UUID() as uuid:
|
188
194
|
return f"[{_Prefixes.uuid.value}]{uuid}"
|
189
195
|
case Version() as version:
|
190
|
-
return f"[{_Prefixes.version.value}]{version
|
196
|
+
return f"[{_Prefixes.version.value}]{version}"
|
197
|
+
case YearMonth() as year_month:
|
198
|
+
return f"[{_Prefixes.year_month.value}]{year_month}"
|
191
199
|
case ZonedDateTime() as datetime:
|
192
200
|
return f"[{_Prefixes.zoned_date_time.value}]{datetime}"
|
193
201
|
# contains
|
@@ -339,6 +347,7 @@ def deserialize(
|
|
339
347
|
_DATE_DELTA_PATTERN,
|
340
348
|
_DATE_TIME_DELTA_PATTERN,
|
341
349
|
_FLOAT_PATTERN,
|
350
|
+
_MONTH_DAY_PATTERN,
|
342
351
|
_NONE_PATTERN,
|
343
352
|
_PATH_PATTERN,
|
344
353
|
_PLAIN_DATE_TIME_PATTERN,
|
@@ -346,6 +355,7 @@ def deserialize(
|
|
346
355
|
_TIME_DELTA_PATTERN,
|
347
356
|
_UUID_PATTERN,
|
348
357
|
_VERSION_PATTERN,
|
358
|
+
_YEAR_MONTH_PATTERN,
|
349
359
|
_ZONED_DATE_TIME_PATTERN,
|
350
360
|
) = [
|
351
361
|
re.compile(r"^\[" + p.value + r"\](" + ".*" + ")$")
|
@@ -354,6 +364,7 @@ def deserialize(
|
|
354
364
|
_Prefixes.date_delta,
|
355
365
|
_Prefixes.date_time_delta,
|
356
366
|
_Prefixes.float_,
|
367
|
+
_Prefixes.month_day,
|
357
368
|
_Prefixes.none,
|
358
369
|
_Prefixes.path,
|
359
370
|
_Prefixes.plain_date_time,
|
@@ -361,6 +372,7 @@ def deserialize(
|
|
361
372
|
_Prefixes.time_delta,
|
362
373
|
_Prefixes.uuid,
|
363
374
|
_Prefixes.version,
|
375
|
+
_Prefixes.year_month,
|
364
376
|
_Prefixes.zoned_date_time,
|
365
377
|
]
|
366
378
|
]
|
@@ -413,6 +425,8 @@ def _object_hook(
|
|
413
425
|
return DateTimeDelta.parse_common_iso(match.group(1))
|
414
426
|
if match := _FLOAT_PATTERN.search(text):
|
415
427
|
return float(match.group(1))
|
428
|
+
if match := _MONTH_DAY_PATTERN.search(text):
|
429
|
+
return MonthDay.parse_common_iso(match.group(1))
|
416
430
|
if match := _PATH_PATTERN.search(text):
|
417
431
|
return Path(match.group(1))
|
418
432
|
if match := _PLAIN_DATE_TIME_PATTERN.search(text):
|
@@ -425,6 +439,8 @@ def _object_hook(
|
|
425
439
|
return UUID(match.group(1))
|
426
440
|
if match := _VERSION_PATTERN.search(text):
|
427
441
|
return parse_version(match.group(1))
|
442
|
+
if match := _YEAR_MONTH_PATTERN.search(text):
|
443
|
+
return YearMonth.parse_common_iso(match.group(1))
|
428
444
|
if match := _ZONED_DATE_TIME_PATTERN.search(text):
|
429
445
|
return ZonedDateTime.parse_common_iso(match.group(1))
|
430
446
|
if (
|
utilities/parse.py
CHANGED
@@ -13,9 +13,11 @@ from whenever import (
|
|
13
13
|
Date,
|
14
14
|
DateDelta,
|
15
15
|
DateTimeDelta,
|
16
|
+
MonthDay,
|
16
17
|
PlainDateTime,
|
17
18
|
Time,
|
18
19
|
TimeDelta,
|
20
|
+
YearMonth,
|
19
21
|
ZonedDateTime,
|
20
22
|
)
|
21
23
|
|
@@ -51,7 +53,6 @@ from utilities.typing import (
|
|
51
53
|
is_union_type,
|
52
54
|
)
|
53
55
|
from utilities.version import ParseVersionError, Version, parse_version
|
54
|
-
from utilities.whenever import Month
|
55
56
|
|
56
57
|
if TYPE_CHECKING:
|
57
58
|
from collections.abc import Iterable, Mapping, Sequence
|
@@ -194,10 +195,11 @@ def _parse_object_type(
|
|
194
195
|
Date,
|
195
196
|
DateDelta,
|
196
197
|
DateTimeDelta,
|
197
|
-
|
198
|
+
MonthDay,
|
198
199
|
PlainDateTime,
|
199
200
|
Time,
|
200
201
|
TimeDelta,
|
202
|
+
YearMonth,
|
201
203
|
ZonedDateTime,
|
202
204
|
),
|
203
205
|
):
|
@@ -467,10 +469,11 @@ def serialize_object(
|
|
467
469
|
Date,
|
468
470
|
DateDelta,
|
469
471
|
DateTimeDelta,
|
470
|
-
|
472
|
+
MonthDay,
|
471
473
|
PlainDateTime,
|
472
474
|
Time,
|
473
475
|
TimeDelta,
|
476
|
+
YearMonth,
|
474
477
|
ZonedDateTime,
|
475
478
|
),
|
476
479
|
):
|
utilities/pyinstrument.py
CHANGED
@@ -8,7 +8,7 @@ from pyinstrument.profiler import Profiler
|
|
8
8
|
|
9
9
|
from utilities.atomicwrites import writer
|
10
10
|
from utilities.pathlib import get_path
|
11
|
-
from utilities.whenever import format_compact,
|
11
|
+
from utilities.whenever import format_compact, get_now, to_local_plain
|
12
12
|
|
13
13
|
if TYPE_CHECKING:
|
14
14
|
from collections.abc import Iterator
|
@@ -22,7 +22,7 @@ def profile(*, path: MaybeCallablePathLike | None = Path.cwd) -> Iterator[None]:
|
|
22
22
|
with Profiler() as profiler:
|
23
23
|
yield
|
24
24
|
filename = get_path(path=path).joinpath(
|
25
|
-
f"profile__{format_compact(
|
25
|
+
f"profile__{format_compact(to_local_plain(get_now()))}.html"
|
26
26
|
)
|
27
27
|
with writer(filename) as temp, temp.open(mode="w") as fh:
|
28
28
|
_ = fh.write(profiler.output_html())
|
utilities/traceback.py
CHANGED
@@ -31,6 +31,7 @@ from utilities.whenever import (
|
|
31
31
|
format_compact,
|
32
32
|
get_now,
|
33
33
|
get_now_local,
|
34
|
+
to_local_plain,
|
34
35
|
to_zoned_date_time,
|
35
36
|
)
|
36
37
|
|
@@ -96,8 +97,8 @@ def _yield_header_lines(
|
|
96
97
|
"""Yield the header lines."""
|
97
98
|
now = get_now_local()
|
98
99
|
start_use = to_zoned_date_time(date_time=start)
|
99
|
-
yield f"Date/time | {format_compact(now
|
100
|
-
start_str = "" if start_use is None else format_compact(start_use
|
100
|
+
yield f"Date/time | {format_compact(now)}"
|
101
|
+
start_str = "" if start_use is None else format_compact(start_use)
|
101
102
|
yield f"Started | {start_str}"
|
102
103
|
delta = None if start_use is None else (now - start_use)
|
103
104
|
delta_str = "" if delta is None else delta.format_common_iso()
|
@@ -256,7 +257,7 @@ def _make_except_hook_inner(
|
|
256
257
|
if path is not None:
|
257
258
|
path = (
|
258
259
|
get_path(path=path)
|
259
|
-
.joinpath(format_compact(
|
260
|
+
.joinpath(format_compact(to_local_plain(get_now())))
|
260
261
|
.with_suffix(".txt")
|
261
262
|
)
|
262
263
|
full = format_exception_stack(
|
utilities/typed_settings.py
CHANGED
@@ -16,9 +16,11 @@ from whenever import (
|
|
16
16
|
Date,
|
17
17
|
DateDelta,
|
18
18
|
DateTimeDelta,
|
19
|
+
MonthDay,
|
19
20
|
PlainDateTime,
|
20
21
|
Time,
|
21
22
|
TimeDelta,
|
23
|
+
YearMonth,
|
22
24
|
ZonedDateTime,
|
23
25
|
)
|
24
26
|
|
@@ -57,10 +59,12 @@ class ExtendedTSConverter(TSConverter):
|
|
57
59
|
(Freq, Freq.parse),
|
58
60
|
(IPv4Address, IPv4Address),
|
59
61
|
(IPv6Address, IPv6Address),
|
62
|
+
(MonthDay, MonthDay.parse_common_iso),
|
60
63
|
(Path, partial(_parse_path, resolve=resolve_paths, pwd=Path.cwd())),
|
61
64
|
(PlainDateTime, PlainDateTime.parse_common_iso),
|
62
65
|
(Time, Time.parse_common_iso),
|
63
66
|
(TimeDelta, TimeDelta.parse_common_iso),
|
67
|
+
(YearMonth, YearMonth.parse_common_iso),
|
64
68
|
(ZonedDateTime, ZonedDateTime.parse_common_iso),
|
65
69
|
]
|
66
70
|
extras = {cls: _make_converter(cls, func) for cls, func in cases}
|
@@ -96,6 +100,7 @@ def _parse_path(
|
|
96
100
|
|
97
101
|
##
|
98
102
|
|
103
|
+
|
99
104
|
_BASE_DIR: Path = Path()
|
100
105
|
|
101
106
|
|
utilities/types.py
CHANGED
@@ -25,9 +25,11 @@ from whenever import (
|
|
25
25
|
Date,
|
26
26
|
DateDelta,
|
27
27
|
DateTimeDelta,
|
28
|
+
MonthDay,
|
28
29
|
PlainDateTime,
|
29
30
|
Time,
|
30
31
|
TimeDelta,
|
32
|
+
YearMonth,
|
31
33
|
ZonedDateTime,
|
32
34
|
)
|
33
35
|
|
@@ -214,9 +216,11 @@ type DateLike = MaybeStr[Date]
|
|
214
216
|
type DateTimeDeltaLike = MaybeStr[DateTimeDelta]
|
215
217
|
type MaybeCallableDate = MaybeCallable[Date]
|
216
218
|
type MaybeCallableZonedDateTime = MaybeCallable[ZonedDateTime]
|
219
|
+
type MonthDayLike = MaybeStr[MonthDay]
|
217
220
|
type PlainDateTimeLike = MaybeStr[PlainDateTime]
|
218
221
|
type TimeDeltaLike = MaybeStr[TimeDelta]
|
219
222
|
type TimeLike = MaybeStr[Time]
|
223
|
+
type YearMonthLike = MaybeStr[YearMonth]
|
220
224
|
type ZonedDateTimeLike = MaybeStr[ZonedDateTime]
|
221
225
|
type DateTimeRoundUnit = Literal[
|
222
226
|
"day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"
|
@@ -265,6 +269,7 @@ __all__ = [
|
|
265
269
|
"MaybeIterableHashable",
|
266
270
|
"MaybeStr",
|
267
271
|
"MaybeType",
|
272
|
+
"MonthDayLike",
|
268
273
|
"Number",
|
269
274
|
"OpenMode",
|
270
275
|
"OptExcInfo",
|
@@ -296,5 +301,6 @@ __all__ = [
|
|
296
301
|
"TupleOrStrMapping",
|
297
302
|
"TypeLike",
|
298
303
|
"WeekDay",
|
304
|
+
"YearMonthLike",
|
299
305
|
"ZonedDateTimeLike",
|
300
306
|
]
|
utilities/whenever.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
from collections.abc import Callable, Iterable, Mapping
|
4
|
-
from dataclasses import dataclass
|
4
|
+
from dataclasses import dataclass
|
5
5
|
from functools import cache
|
6
6
|
from logging import LogRecord
|
7
7
|
from statistics import fmean
|
@@ -33,7 +33,7 @@ from utilities.platform import get_strftime
|
|
33
33
|
from utilities.re import ExtractGroupsError, extract_groups
|
34
34
|
from utilities.sentinel import Sentinel, sentinel
|
35
35
|
from utilities.types import DateTimeRoundUnit, MaybeStr
|
36
|
-
from utilities.tzlocal import LOCAL_TIME_ZONE
|
36
|
+
from utilities.tzlocal import LOCAL_TIME_ZONE, LOCAL_TIME_ZONE_NAME
|
37
37
|
from utilities.zoneinfo import UTC, get_time_zone_name
|
38
38
|
|
39
39
|
if TYPE_CHECKING:
|
@@ -49,16 +49,8 @@ if TYPE_CHECKING:
|
|
49
49
|
## bounds
|
50
50
|
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
9999, 12, 31, hour=23, minute=59, second=59, nanosecond=999999999
|
55
|
-
)
|
56
|
-
DATE_MIN = PLAIN_DATE_TIME_MIN.date()
|
57
|
-
DATE_MAX = PLAIN_DATE_TIME_MAX.date()
|
58
|
-
TIME_MIN = PLAIN_DATE_TIME_MIN.time()
|
59
|
-
TIME_MAX = PLAIN_DATE_TIME_MIN.time()
|
60
|
-
ZONED_DATE_TIME_MIN = PLAIN_DATE_TIME_MIN.assume_tz(UTC.key)
|
61
|
-
ZONED_DATE_TIME_MAX = PLAIN_DATE_TIME_MAX.assume_tz(UTC.key)
|
52
|
+
ZONED_DATE_TIME_MIN = PlainDateTime.MIN.assume_tz(UTC.key)
|
53
|
+
ZONED_DATE_TIME_MAX = PlainDateTime.MAX.assume_tz(UTC.key)
|
62
54
|
|
63
55
|
|
64
56
|
DATE_TIME_DELTA_MIN = DateTimeDelta(
|
@@ -170,7 +162,7 @@ def format_compact(
|
|
170
162
|
obj_use = date.py_date()
|
171
163
|
fmt_use = "%Y%m%d" if fmt is None else fmt
|
172
164
|
case Time() as time:
|
173
|
-
obj_use = time.py_time()
|
165
|
+
obj_use = time.round().py_time()
|
174
166
|
fmt_use = "%H%M%S" if fmt is None else fmt
|
175
167
|
case PlainDateTime() as datetime:
|
176
168
|
obj_use = datetime.round().py_datetime()
|
@@ -465,124 +457,6 @@ class _MinMaxDatePeriodError(MinMaxDateError):
|
|
465
457
|
##
|
466
458
|
|
467
459
|
|
468
|
-
@dataclass(order=True, unsafe_hash=True, slots=True)
|
469
|
-
class Month:
|
470
|
-
"""Represents a month in time."""
|
471
|
-
|
472
|
-
year: int
|
473
|
-
month: int
|
474
|
-
|
475
|
-
def __post_init__(self) -> None:
|
476
|
-
try:
|
477
|
-
_ = Date(self.year, self.month, 1)
|
478
|
-
except ValueError:
|
479
|
-
raise _MonthInvalidError(year=self.year, month=self.month) from None
|
480
|
-
|
481
|
-
@override
|
482
|
-
def __repr__(self) -> str:
|
483
|
-
return self.format_common_iso()
|
484
|
-
|
485
|
-
@override
|
486
|
-
def __str__(self) -> str:
|
487
|
-
return repr(self)
|
488
|
-
|
489
|
-
def __add__(self, other: Any, /) -> Self:
|
490
|
-
if not isinstance(other, int): # pragma: no cover
|
491
|
-
return NotImplemented
|
492
|
-
years, month = divmod(self.month + other - 1, 12)
|
493
|
-
month += 1
|
494
|
-
year = self.year + years
|
495
|
-
return replace(self, year=year, month=month)
|
496
|
-
|
497
|
-
@overload
|
498
|
-
def __sub__(self, other: Self, /) -> int: ...
|
499
|
-
@overload
|
500
|
-
def __sub__(self, other: int, /) -> Self: ...
|
501
|
-
def __sub__(self, other: Self | int, /) -> Self | int:
|
502
|
-
if isinstance(other, int): # pragma: no cover
|
503
|
-
return self + (-other)
|
504
|
-
if isinstance(other, type(self)):
|
505
|
-
self_as_int = 12 * self.year + self.month
|
506
|
-
other_as_int = 12 * other.year + other.month
|
507
|
-
return self_as_int - other_as_int
|
508
|
-
return NotImplemented # pragma: no cover
|
509
|
-
|
510
|
-
@classmethod
|
511
|
-
def ensure(cls, obj: MonthLike, /) -> Month:
|
512
|
-
"""Ensure the object is a month."""
|
513
|
-
match obj:
|
514
|
-
case Month() as month:
|
515
|
-
return month
|
516
|
-
case str() as text:
|
517
|
-
return cls.parse_common_iso(text)
|
518
|
-
case _ as never:
|
519
|
-
assert_never(never)
|
520
|
-
|
521
|
-
def format_common_iso(self) -> str:
|
522
|
-
return f"{self.year:04}-{self.month:02}"
|
523
|
-
|
524
|
-
@classmethod
|
525
|
-
def from_date(cls, date: Date, /) -> Self:
|
526
|
-
return cls(year=date.year, month=date.month)
|
527
|
-
|
528
|
-
@classmethod
|
529
|
-
def parse_common_iso(cls, text: str, /) -> Self:
|
530
|
-
try:
|
531
|
-
year, month = extract_groups(r"^(\d{2,4})[\-\. ]?(\d{2})$", text)
|
532
|
-
except ExtractGroupsError:
|
533
|
-
raise _MonthParseCommonISOError(text=text) from None
|
534
|
-
return cls(year=cls._parse_year(year), month=int(month))
|
535
|
-
|
536
|
-
def to_date(self, /, *, day: int = 1) -> Date:
|
537
|
-
return Date(self.year, self.month, day)
|
538
|
-
|
539
|
-
@classmethod
|
540
|
-
def _parse_year(cls, year: str, /) -> int:
|
541
|
-
match len(year):
|
542
|
-
case 4:
|
543
|
-
return int(year)
|
544
|
-
case 2:
|
545
|
-
min_year = DATE_TWO_DIGIT_YEAR_MIN.year
|
546
|
-
max_year = DATE_TWO_DIGIT_YEAR_MAX.year
|
547
|
-
years = range(min_year, max_year + 1)
|
548
|
-
(result,) = (y for y in years if y % 100 == int(year))
|
549
|
-
return result
|
550
|
-
case _:
|
551
|
-
raise _MonthParseCommonISOError(text=year) from None
|
552
|
-
|
553
|
-
|
554
|
-
@dataclass(kw_only=True, slots=True)
|
555
|
-
class MonthError(Exception): ...
|
556
|
-
|
557
|
-
|
558
|
-
@dataclass(kw_only=True, slots=True)
|
559
|
-
class _MonthInvalidError(MonthError):
|
560
|
-
year: int
|
561
|
-
month: int
|
562
|
-
|
563
|
-
@override
|
564
|
-
def __str__(self) -> str:
|
565
|
-
return f"Invalid year and month: {self.year}, {self.month}"
|
566
|
-
|
567
|
-
|
568
|
-
@dataclass(kw_only=True, slots=True)
|
569
|
-
class _MonthParseCommonISOError(MonthError):
|
570
|
-
text: str
|
571
|
-
|
572
|
-
@override
|
573
|
-
def __str__(self) -> str:
|
574
|
-
return f"Unable to parse month; got {self.text!r}"
|
575
|
-
|
576
|
-
|
577
|
-
type DateOrMonth = Date | Month
|
578
|
-
type MonthLike = MaybeStr[Month]
|
579
|
-
MONTH_MIN = Month.from_date(DATE_MIN)
|
580
|
-
MONTH_MAX = Month.from_date(DATE_MAX)
|
581
|
-
|
582
|
-
|
583
|
-
##
|
584
|
-
|
585
|
-
|
586
460
|
@overload
|
587
461
|
def to_date(*, date: MaybeCallableDate) -> Date: ...
|
588
462
|
@overload
|
@@ -668,6 +542,14 @@ def to_date_time_delta(nanos: int, /) -> DateTimeDelta:
|
|
668
542
|
##
|
669
543
|
|
670
544
|
|
545
|
+
def to_local_plain(date_time: ZonedDateTime, /) -> PlainDateTime:
|
546
|
+
"""Convert a datetime to its local/plain variant."""
|
547
|
+
return date_time.to_tz(LOCAL_TIME_ZONE_NAME).to_plain()
|
548
|
+
|
549
|
+
|
550
|
+
##
|
551
|
+
|
552
|
+
|
671
553
|
def to_nanos(delta: DateTimeDelta, /) -> int:
|
672
554
|
"""Compute the number of nanoseconds in a date-time delta."""
|
673
555
|
months, days, _, _ = delta.in_months_days_secs_nanos()
|
@@ -843,8 +725,6 @@ __all__ = [
|
|
843
725
|
"DATE_DELTA_MIN",
|
844
726
|
"DATE_DELTA_PARSABLE_MAX",
|
845
727
|
"DATE_DELTA_PARSABLE_MIN",
|
846
|
-
"DATE_MAX",
|
847
|
-
"DATE_MIN",
|
848
728
|
"DATE_TIME_DELTA_MAX",
|
849
729
|
"DATE_TIME_DELTA_MIN",
|
850
730
|
"DATE_TIME_DELTA_PARSABLE_MAX",
|
@@ -857,16 +737,10 @@ __all__ = [
|
|
857
737
|
"MILLISECOND",
|
858
738
|
"MINUTE",
|
859
739
|
"MONTH",
|
860
|
-
"MONTH_MAX",
|
861
|
-
"MONTH_MIN",
|
862
740
|
"NOW_LOCAL",
|
863
|
-
"PLAIN_DATE_TIME_MAX",
|
864
|
-
"PLAIN_DATE_TIME_MIN",
|
865
741
|
"SECOND",
|
866
742
|
"TIME_DELTA_MAX",
|
867
743
|
"TIME_DELTA_MIN",
|
868
|
-
"TIME_MAX",
|
869
|
-
"TIME_MIN",
|
870
744
|
"TODAY_LOCAL",
|
871
745
|
"TODAY_UTC",
|
872
746
|
"WEEK",
|
@@ -875,15 +749,11 @@ __all__ = [
|
|
875
749
|
"ZERO_TIME",
|
876
750
|
"ZONED_DATE_TIME_MAX",
|
877
751
|
"ZONED_DATE_TIME_MIN",
|
878
|
-
"DateOrMonth",
|
879
752
|
"Freq",
|
880
753
|
"FreqError",
|
881
754
|
"FreqLike",
|
882
755
|
"MeanDateTimeError",
|
883
756
|
"MinMaxDateError",
|
884
|
-
"Month",
|
885
|
-
"MonthError",
|
886
|
-
"MonthLike",
|
887
757
|
"ToDaysError",
|
888
758
|
"ToNanosError",
|
889
759
|
"WheneverLogRecord",
|
@@ -901,6 +771,7 @@ __all__ = [
|
|
901
771
|
"to_date",
|
902
772
|
"to_date_time_delta",
|
903
773
|
"to_days",
|
774
|
+
"to_local_plain",
|
904
775
|
"to_nanos",
|
905
776
|
"to_zoned_date_time",
|
906
777
|
]
|
File without changes
|
File without changes
|