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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.136.10
3
+ Version: 0.138.0
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -1,11 +1,11 @@
1
- utilities/__init__.py,sha256=8QgSKySzcmMe0_2EO7a511YvtC4NfQTMgUzRj45icM0,61
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=rXCbuBi6xqRK2-2e0hXQ2vYBWJzT9vPY6KkRIu73_uI,16049
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=ztFZeigIS6sMZj7xeC_sIHdd7szrub4V7EBHxH7j09E,1877
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=Ru3ZBhtGiuj-1vJjGuclymlYDAV_NC0WoUg9LY873d8,38044
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=trxiM6pLmYVyHs6ELlkVDZrlYgdMKfwH5WQnbNp93aY,17871
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=y5ynSGhQjX7vikfvsHh_AklLnH7gjvsSkIC3h5tnx98,36474
43
+ utilities/orjson.py,sha256=WWV2QukCIuwT8OAOtmKhLhxezXPVbeA_fQCucmGmbRA,37106
44
44
  utilities/os.py,sha256=yMNAKMyY8oFgQ1yN3TQYnwa5-A_FXz4tCDbhIctQHSs,3736
45
- utilities/parse.py,sha256=bCZW1bBfXM2j-yLGq0TGUshOnbL8V-U8amGu_OZTE_I,17907
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=xN4BaS9AjLJ5YT4tCmEkCGo2PDkpCvngtA0dQ1INvSo,875
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=13cKPGaJAQyK7yzxU2qsiXWzvHm3qPyEBzeizL6r7AM,8887
80
- utilities/typed_settings.py,sha256=cZqA2IjG1erF1SUZFSsjIHmZX7BP5iIfPd6CixHllA0,4367
81
- utilities/types.py,sha256=98pwEfkBikg6UUrshNJZgc8l97FGBbi0twIsqVt-KNA,17247
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=hDk60UneSU29rpu7Q-UCM61ptcNqkhso5hoBFAvUk6M,23860
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.136.10.dist-info/METADATA,sha256=hm82pnqhQ2zMisMtl9dRwyCvpNuS-DNp9Zwflhfu2Ok,1638
92
- dycw_utilities-0.136.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
- dycw_utilities-0.136.10.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
94
- dycw_utilities-0.136.10.dist-info/RECORD,,
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
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.136.10"
3
+ __version__ = "0.138.0"
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, _MonthParseCommonISOError
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 Month(ParamType):
256
- """A month-valued parameter."""
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: MonthLike, param: Parameter | None, ctx: Context | None
267
- ) -> utilities.whenever.Month:
268
- """Convert a value into the `Month` type."""
269
- try:
270
- return utilities.whenever.Month.ensure(value)
271
- except _MonthParseCommonISOError as error:
272
- self.fail(str(error), param, ctx)
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, get_now_local
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(get_now_local().to_plain()))
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_ = DATE_MIN
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_ = DATE_MAX
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 months(
691
+ def month_days(
699
692
  draw: DrawFn,
700
693
  /,
701
694
  *,
702
- min_value: MaybeSearchStrategy[Month | None] = None,
703
- max_value: MaybeSearchStrategy[Month | None] = None,
704
- two_digit: MaybeSearchStrategy[bool] = False,
705
- ) -> Month:
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_ = MONTH_MIN
711
- case Month():
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_ = MONTH_MAX
718
- case Month():
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.to_date() for m in [min_value_, max_value_]]
723
- date = draw(dates(min_value=min_date, max_value=max_date, two_digit=two_digit))
724
- return Month.from_date(date)
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_ = PLAIN_DATE_TIME_MIN
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_ = PLAIN_DATE_TIME_MAX
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_ = TIME_MIN
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_ = TIME_MAX
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 ((DATE_MIN + DAY) <= zoned.date() <= (DATE_MAX - DAY)):
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
- "months",
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 WheneverLogRecord, format_compact, get_now_local
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.to_plain())}"
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.to_plain())}__{format_compact(end.to_plain())}"
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!s}"
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
- Month,
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
- Month,
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, get_now_local
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(get_now_local().to_plain())}.html"
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.to_plain())}"
100
- start_str = "" if start_use is None else format_compact(start_use.to_plain())
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(get_now_local().to_plain()))
260
+ .joinpath(format_compact(to_local_plain(get_now())))
260
261
  .with_suffix(".txt")
261
262
  )
262
263
  full = format_exception_stack(
@@ -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, replace
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
- PLAIN_DATE_TIME_MIN = PlainDateTime(1, 1, 1)
53
- PLAIN_DATE_TIME_MAX = PlainDateTime(
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
  ]