dycw-utilities 0.131.17__py3-none-any.whl → 0.131.18__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.131.17
3
+ Version: 0.131.18
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=VaWub7ik43R7i6IUMJibr6EG62sAZ7zSqzl9IaPeNEs,61
1
+ utilities/__init__.py,sha256=653kI0IdCJCyIvZc8ekfN8j8dOnjrRtGJKRx9UDXVvM,61
2
2
  utilities/aiolimiter.py,sha256=mD0wEiqMgwpty4XTbawFpnkkmJS6R4JRsVXFUaoitSU,628
3
3
  utilities/altair.py,sha256=HeZBVUocjkrTNwwKrClppsIqgNFF-ykv05HfZSoHYno,9104
4
4
  utilities/asyncio.py,sha256=mHnlSA4KPeDaBRts8Rn4sNA_4urodj7gQzs-_8Z-F7A,37587
@@ -12,7 +12,7 @@ utilities/contextvars.py,sha256=RsSGGrbQqqZ67rOydnM7WWIsM2lIE31UHJLejnHJPWY,505
12
12
  utilities/cryptography.py,sha256=_CiK_K6c_-uQuUhsUNjNjTL-nqxAh4_1zTfS11Xe120,972
13
13
  utilities/cvxpy.py,sha256=Rv1-fD-XYerosCavRF8Pohop2DBkU3AlFaGTfD8AEAA,13776
14
14
  utilities/dataclasses.py,sha256=iiC1wpGXWhaocIikzwBt8bbLWyImoUlOlcDZJGejaIg,33011
15
- utilities/datetime.py,sha256=uPQdUgJJ9KuF-pogjYRbI9lOK-i5UBzPHexWe4pOVEo,38713
15
+ utilities/datetime.py,sha256=bt9tgxHfjSbl0g5H7W2YSwNm-1HsbKjX5AsYcyEO1mU,30361
16
16
  utilities/enum.py,sha256=HoRwVCWzsnH0vpO9ZEcAAIZLMv0Sn2vJxxA4sYMQgDs,5793
17
17
  utilities/errors.py,sha256=nC7ZYtxxDBMfrTHtT_MByBfup_wfGQFRo3eDt-0ZPe8,1045
18
18
  utilities/eventkit.py,sha256=6M5Xu1SzN-juk9PqBHwy5dS-ta7T0qA6SMpDsakOJ0E,13039
@@ -28,7 +28,7 @@ utilities/hypothesis.py,sha256=fcklSUIagoDZOTn8ITbkIU7BOZMV4g1Owj0khiz9yBY,43401
28
28
  utilities/importlib.py,sha256=mV1xT_O_zt_GnZZ36tl3xOmMaN_3jErDWY54fX39F6Y,429
29
29
  utilities/inflect.py,sha256=DbqB5Q9FbRGJ1NbvEiZBirRMxCxgrz91zy5jCO9ZIs0,347
30
30
  utilities/ipython.py,sha256=V2oMYHvEKvlNBzxDXdLvKi48oUq2SclRg5xasjaXStw,763
31
- utilities/iterables.py,sha256=mDqw2_0MUVp-P8FklgcaVTi2TXduH0MxbhTDzzhSBho,44915
31
+ utilities/iterables.py,sha256=cuebB4ivKlZuKm8S3PQIfjavn9h-5mBGmvYq4FpSxFg,43812
32
32
  utilities/jupyter.py,sha256=ft5JA7fBxXKzP-L9W8f2-wbF0QeYc_2uLQNFDVk4Z-M,2917
33
33
  utilities/libcst.py,sha256=Jto5ppzRzsxn4AD32IS8n0lbgLYXwsVJB6EY8giNZyY,4974
34
34
  utilities/lightweight_charts.py,sha256=JrkrAZMo6JID2Eoc9QCc05Y_pK4l2zsApIhmii1z2Ig,2764
@@ -39,11 +39,11 @@ utilities/memory_profiler.py,sha256=tf2C51P2lCujPGvRt2Rfc7VEw5LDXmVPCG3z_AvBmbU,
39
39
  utilities/modules.py,sha256=iuvLluJya-hvl1Q25-Jk3dLgx2Es3ck4SjJiEkAlVTs,3195
40
40
  utilities/more_itertools.py,sha256=tBbjjKx8_Uv_TCjxhPwrGfAx_jRHtvLIZqXVWAsjzqA,8842
41
41
  utilities/numpy.py,sha256=Xn23sA2ZbVNqwUYEgNJD3XBYH6IbCri_WkHSNhg3NkY,26122
42
- utilities/operator.py,sha256=0M2yZJ0PODH47ogFEnkGMBe_cfxwZR02T_92LZVZvHo,3715
42
+ utilities/operator.py,sha256=DuiWdkmK0D-ddvFqOayDkazTQE1Ysvtl6-UiBN5gns8,3857
43
43
  utilities/optuna.py,sha256=loyJGWTzljgdJaoLhP09PT8Jz6o_pwBOwehY33lHkhw,1923
44
- utilities/orjson.py,sha256=lhBSO-QCm2g-uhUE0hKe-PVhAt1TKRRx0iZLSFPmew4,36970
44
+ utilities/orjson.py,sha256=7CraMEtaJtKTDTTVS4fQU6PeuFcG0V8SGwD3sqZ8kSI,36475
45
45
  utilities/os.py,sha256=D_FyyT-6TtqiN9KSS7c9g1fnUtgxmyMtzAjmYLkk46A,3587
46
- utilities/parse.py,sha256=vsZ2jf_ceSI_Kta9titixufysJaVXh0Whjz1T4awJZw,18938
46
+ utilities/parse.py,sha256=YE2VWYKDm9WYf5wcOWrOxVfM6UWvhkSxSkwdxRQNsA0,17633
47
47
  utilities/pathlib.py,sha256=PK41rf1c9Wqv7h8f5R7H3_Lhq_gQZTUJD5tu3gMHVaU,3247
48
48
  utilities/period.py,sha256=opqpBevBGSGXbA7NYfRJjtthi1JPxdMaZ7QV3xosnTc,4774
49
49
  utilities/pickle.py,sha256=MBT2xZCsv0pH868IXLGKnlcqNx2IRVKYNpRcqiQQqxw,653
@@ -87,11 +87,11 @@ utilities/tzlocal.py,sha256=xbBBzVIUKMk8AkhuIp1qxGRNBioIa5I09dpeoBnIOOU,662
87
87
  utilities/uuid.py,sha256=jJTFxz-CWgltqNuzmythB7iEQ-Q1mCwPevUfKthZT3c,611
88
88
  utilities/version.py,sha256=ufhJMmI6KPs1-3wBI71aj5wCukd3sP_m11usLe88DNA,5117
89
89
  utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
90
- utilities/whenever.py,sha256=oO0sgIIv4tvhIYmlZ4RFFfY6P54CPeyJCY9A4XHuyqo,11916
90
+ utilities/whenever.py,sha256=tUNfGB_72YSCxbDKiQ7ulWtISC3AgVBrW1s3XlFPkuw,6883
91
91
  utilities/whenever2.py,sha256=Jd_fEavXCWTdMj29L7j-HbeMvrl8e3Ah_nxcLEFAwOU,12026
92
92
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
93
93
  utilities/zoneinfo.py,sha256=gJPr9l7V8s3Y7TXpCGYEM1S81Rplb9e4MoV9Nvy2VU8,1852
94
- dycw_utilities-0.131.17.dist-info/METADATA,sha256=H8TXOjXCrBtmH2V30ZUSMr1mz56J-27FJYHgaoof7Oc,1585
95
- dycw_utilities-0.131.17.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
96
- dycw_utilities-0.131.17.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
97
- dycw_utilities-0.131.17.dist-info/RECORD,,
94
+ dycw_utilities-0.131.18.dist-info/METADATA,sha256=nfjJB5vwmQGGegP4Ma-fxV0cKOE67JbPGxKLmZ0Rd78,1585
95
+ dycw_utilities-0.131.18.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
96
+ dycw_utilities-0.131.18.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
97
+ dycw_utilities-0.131.18.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.131.17"
3
+ __version__ = "0.131.18"
utilities/datetime.py CHANGED
@@ -11,7 +11,6 @@ from typing import (
11
11
  Literal,
12
12
  Self,
13
13
  SupportsFloat,
14
- TypeGuard,
15
14
  assert_never,
16
15
  overload,
17
16
  override,
@@ -23,7 +22,7 @@ from utilities.platform import SYSTEM
23
22
  from utilities.sentinel import Sentinel, sentinel
24
23
  from utilities.types import MaybeCallablePyDate, MaybeCallablePyDateTime, MaybeStr
25
24
  from utilities.typing import is_instance_gen
26
- from utilities.zoneinfo import UTC, ensure_time_zone, get_time_zone_name
25
+ from utilities.zoneinfo import UTC, ensure_time_zone
27
26
 
28
27
  if TYPE_CHECKING:
29
28
  from collections.abc import Iterator
@@ -114,50 +113,6 @@ class AddWeekdaysError(Exception): ...
114
113
  ##
115
114
 
116
115
 
117
- def are_equal_date_durations(x: Duration, y: Duration, /) -> bool:
118
- """Check if x == y for durations."""
119
- x_timedelta = date_duration_to_timedelta(x)
120
- y_timedelta = date_duration_to_timedelta(y)
121
- return x_timedelta == y_timedelta
122
-
123
-
124
- ##
125
-
126
-
127
- def are_equal_dates_or_datetimes(
128
- x: DateOrDateTime, y: DateOrDateTime, /, *, strict: bool = False
129
- ) -> bool:
130
- """Check if x == y for dates/datetimes."""
131
- if is_instance_gen(x, dt.date) and is_instance_gen(y, dt.date):
132
- return x == y
133
- if is_instance_gen(x, dt.datetime) and is_instance_gen(y, dt.datetime):
134
- return are_equal_datetimes(x, y, strict=strict)
135
- raise AreEqualDatesOrDateTimesError(x=x, y=y)
136
-
137
-
138
- @dataclass(kw_only=True, slots=True)
139
- class AreEqualDatesOrDateTimesError(Exception):
140
- x: DateOrDateTime
141
- y: DateOrDateTime
142
-
143
- @override
144
- def __str__(self) -> str:
145
- return f"Cannot compare date and datetime ({self.x}, {self.y})"
146
-
147
-
148
- ##
149
-
150
-
151
- def are_equal_datetime_durations(x: Duration, y: Duration, /) -> bool:
152
- """Check if x == y for durations."""
153
- x_timedelta = datetime_duration_to_timedelta(x)
154
- y_timedelta = datetime_duration_to_timedelta(y)
155
- return x_timedelta == y_timedelta
156
-
157
-
158
- ##
159
-
160
-
161
116
  def are_equal_datetimes(
162
117
  x: dt.datetime, y: dt.datetime, /, *, strict: bool = False
163
118
  ) -> bool:
@@ -186,16 +141,6 @@ class AreEqualDateTimesError(Exception):
186
141
  ##
187
142
 
188
143
 
189
- def are_equal_months(x: DateOrMonth, y: DateOrMonth, /) -> bool:
190
- """Check if x == y as months."""
191
- x_month = Month.from_date(x) if isinstance(x, dt.date) else x
192
- y_month = Month.from_date(y) if isinstance(y, dt.date) else y
193
- return x_month == y_month
194
-
195
-
196
- ##
197
-
198
-
199
144
  def check_date_not_datetime(date: dt.date, /) -> None:
200
145
  """Check if a date is not a datetime."""
201
146
  if not is_instance_gen(date, dt.date):
@@ -214,19 +159,6 @@ class CheckDateNotDateTimeError(Exception):
214
159
  ##
215
160
 
216
161
 
217
- def date_to_datetime(
218
- date: dt.date, /, *, time: dt.time | None = None, time_zone: TimeZoneLike = UTC
219
- ) -> dt.datetime:
220
- """Expand a date into a datetime."""
221
- check_date_not_datetime(date)
222
- time_use = dt.time(0) if time is None else time
223
- time_zone_use = ensure_time_zone(time_zone)
224
- return dt.datetime.combine(date, time_use, tzinfo=time_zone_use)
225
-
226
-
227
- ##
228
-
229
-
230
162
  def date_to_month(date: dt.date, /) -> Month:
231
163
  """Collapse a date into a month."""
232
164
  check_date_not_datetime(date)
@@ -337,32 +269,6 @@ def datetime_duration_to_microseconds(duration: Duration, /) -> int:
337
269
  )
338
270
 
339
271
 
340
- @overload
341
- def datetime_duration_to_milliseconds(
342
- duration: Duration, /, *, strict: Literal[True]
343
- ) -> int: ...
344
- @overload
345
- def datetime_duration_to_milliseconds(
346
- duration: Duration, /, *, strict: bool = False
347
- ) -> float: ...
348
- def datetime_duration_to_milliseconds(
349
- duration: Duration, /, *, strict: bool = False
350
- ) -> int | float:
351
- """Compute the number of milliseconds in a datetime duration."""
352
- timedelta = datetime_duration_to_timedelta(duration)
353
- microseconds = datetime_duration_to_microseconds(timedelta)
354
- milliseconds, remainder = divmod(microseconds, _MICROSECONDS_PER_MILLISECOND)
355
- match remainder, strict:
356
- case 0, _:
357
- return milliseconds
358
- case _, True:
359
- raise TimedeltaToMillisecondsError(duration=duration, remainder=remainder)
360
- case _, False:
361
- return milliseconds + remainder / _MICROSECONDS_PER_MILLISECOND
362
- case _ as never:
363
- assert_never(never)
364
-
365
-
366
272
  @dataclass(kw_only=True, slots=True)
367
273
  class TimedeltaToMillisecondsError(Exception):
368
274
  duration: Duration
@@ -370,7 +276,7 @@ class TimedeltaToMillisecondsError(Exception):
370
276
 
371
277
  @override
372
278
  def __str__(self) -> str:
373
- return f"Unable to convert {self.duration} to milliseconds; got {self.remainder} microsecond(s)"
279
+ return f"Unable to convert {self.duration} to milliseconds; got {self.remainder} microsecond(s)" # pragma: no cover
374
280
 
375
281
 
376
282
  def datetime_duration_to_timedelta(duration: Duration, /) -> dt.timedelta:
@@ -413,20 +319,6 @@ def datetime_utc(
413
319
  ##
414
320
 
415
321
 
416
- def days_since_epoch(date: dt.date, /) -> int:
417
- """Compute the number of days since the epoch."""
418
- check_date_not_datetime(date)
419
- return timedelta_since_epoch(date).days
420
-
421
-
422
- def days_since_epoch_to_date(days: int, /) -> dt.date:
423
- """Convert a number of days since the epoch to a date."""
424
- return EPOCH_DATE + days * DAY
425
-
426
-
427
- ##
428
-
429
-
430
322
  def ensure_month(month: MonthLike, /) -> Month:
431
323
  """Ensure the object is a month."""
432
324
  if isinstance(month, Month):
@@ -449,25 +341,6 @@ class EnsureMonthError(Exception):
449
341
  ##
450
342
 
451
343
 
452
- def format_datetime_local_and_utc(datetime: dt.datetime, /) -> str:
453
- """Format a plain datetime locally & in UTC."""
454
- time_zone = ensure_time_zone(datetime)
455
- if time_zone is UTC:
456
- return datetime.strftime("%Y-%m-%d %H:%M:%S (%a, UTC)")
457
- as_utc = datetime.astimezone(UTC)
458
- local = get_time_zone_name(time_zone)
459
- if datetime.year != as_utc.year:
460
- return f"{datetime:%Y-%m-%d %H:%M:%S (%a}, {local}, {as_utc:%Y-%m-%d %H:%M:%S} UTC)"
461
- if (datetime.month != as_utc.month) or (datetime.day != as_utc.day):
462
- return (
463
- f"{datetime:%Y-%m-%d %H:%M:%S (%a}, {local}, {as_utc:%m-%d %H:%M:%S} UTC)"
464
- )
465
- return f"{datetime:%Y-%m-%d %H:%M:%S (%a}, {local}, {as_utc:%H:%M:%S} UTC)"
466
-
467
-
468
- ##
469
-
470
-
471
344
  @overload
472
345
  def get_date(*, date: MaybeCallablePyDate) -> dt.date: ...
473
346
  @overload
@@ -691,14 +564,6 @@ def is_integral_timedelta(duration: Duration, /) -> bool:
691
564
  ##
692
565
 
693
566
 
694
- def is_plain_datetime(obj: Any, /) -> TypeGuard[dt.datetime]:
695
- """Check if an object is a plain datetime."""
696
- return isinstance(obj, dt.datetime) and (obj.tzinfo is None)
697
-
698
-
699
- ##
700
-
701
-
702
567
  _FRIDAY = 5
703
568
 
704
569
 
@@ -719,14 +584,6 @@ def is_zero_time(duration: Duration, /) -> bool:
719
584
  ##
720
585
 
721
586
 
722
- def is_zoned_datetime(obj: Any, /) -> TypeGuard[dt.datetime]:
723
- """Check if an object is a zoned datetime."""
724
- return isinstance(obj, dt.datetime) and (obj.tzinfo is not None)
725
-
726
-
727
- ##
728
-
729
-
730
587
  def maybe_sub_pct_y(text: str, /) -> str:
731
588
  """Substitute the `%Y' token with '%4Y' if necessary."""
732
589
  match SYSTEM:
@@ -839,54 +696,6 @@ def microseconds_since_epoch_to_datetime(
839
696
  ##
840
697
 
841
698
 
842
- @overload
843
- def milliseconds_since_epoch(
844
- datetime: dt.datetime, /, *, strict: Literal[True]
845
- ) -> int: ...
846
- @overload
847
- def milliseconds_since_epoch(
848
- datetime: dt.datetime, /, *, strict: bool = False
849
- ) -> float: ...
850
- def milliseconds_since_epoch(
851
- datetime: dt.datetime, /, *, strict: bool = False
852
- ) -> float:
853
- """Compute the number of milliseconds since the epoch."""
854
- microseconds = microseconds_since_epoch(datetime)
855
- milliseconds, remainder = divmod(microseconds, _MICROSECONDS_PER_MILLISECOND)
856
- if strict:
857
- if remainder == 0:
858
- return milliseconds
859
- raise MillisecondsSinceEpochError(datetime=datetime, remainder=remainder)
860
- return milliseconds + remainder / _MICROSECONDS_PER_MILLISECOND
861
-
862
-
863
- @dataclass(kw_only=True, slots=True)
864
- class MillisecondsSinceEpochError(Exception):
865
- datetime: dt.datetime
866
- remainder: int
867
-
868
- @override
869
- def __str__(self) -> str:
870
- return f"Unable to convert {self.datetime} to milliseconds since epoch; got {self.remainder} microsecond(s)"
871
-
872
-
873
- def milliseconds_since_epoch_to_datetime(
874
- milliseconds: int, /, *, time_zone: dt.tzinfo | None = None
875
- ) -> dt.datetime:
876
- """Convert a number of milliseconds since the epoch to a datetime."""
877
- epoch = EPOCH_NAIVE if time_zone is None else EPOCH_UTC
878
- timedelta = milliseconds_to_timedelta(milliseconds)
879
- return epoch + timedelta
880
-
881
-
882
- def milliseconds_to_timedelta(milliseconds: int, /) -> dt.timedelta:
883
- """Compute a timedelta given a number of milliseconds."""
884
- return microseconds_to_timedelta(_MICROSECONDS_PER_MILLISECOND * milliseconds)
885
-
886
-
887
- ##
888
-
889
-
890
699
  @dataclass(order=True, unsafe_hash=True, slots=True)
891
700
  class Month:
892
701
  """Represents a month in time."""
@@ -1070,70 +879,6 @@ def _round_to_weekday(
1070
879
  ##
1071
880
 
1072
881
 
1073
- def serialize_compact(date_or_datetime: DateOrDateTime, /) -> str:
1074
- """Serialize a date/datetime using a compact format."""
1075
- match date_or_datetime:
1076
- case dt.datetime() as datetime:
1077
- if datetime.tzinfo is None:
1078
- raise SerializeCompactError(datetime=datetime)
1079
- format_ = "%Y%m%dT%H%M%S"
1080
- case dt.date():
1081
- format_ = "%Y%m%d"
1082
- case _ as never:
1083
- assert_never(never)
1084
- return date_or_datetime.strftime(maybe_sub_pct_y(format_))
1085
-
1086
-
1087
- @dataclass(kw_only=True, slots=True)
1088
- class SerializeCompactError(Exception):
1089
- datetime: dt.datetime
1090
-
1091
- @override
1092
- def __str__(self) -> str:
1093
- return f"Unable to serialize plain datetime {self.datetime}"
1094
-
1095
-
1096
- def parse_date_compact(text: str, /) -> dt.date:
1097
- """Parse a compact string into a date."""
1098
- try:
1099
- datetime = dt.datetime.strptime(text, "%Y%m%d").replace(tzinfo=UTC)
1100
- except ValueError:
1101
- raise ParseDateCompactError(text=text) from None
1102
- return datetime.date()
1103
-
1104
-
1105
- @dataclass(kw_only=True, slots=True)
1106
- class ParseDateCompactError(Exception):
1107
- text: str
1108
-
1109
- @override
1110
- def __str__(self) -> str:
1111
- return f"Unable to parse {self.text!r} into a date"
1112
-
1113
-
1114
- def parse_datetime_compact(
1115
- text: str, /, *, time_zone: TimeZoneLike = UTC
1116
- ) -> dt.datetime:
1117
- """Parse a compact string into a datetime."""
1118
- time_zone = ensure_time_zone(time_zone)
1119
- try:
1120
- return dt.datetime.strptime(text, "%Y%m%dT%H%M%S").replace(tzinfo=time_zone)
1121
- except ValueError:
1122
- raise ParseDateTimeCompactError(text=text) from None
1123
-
1124
-
1125
- @dataclass(kw_only=True, slots=True)
1126
- class ParseDateTimeCompactError(Exception):
1127
- text: str
1128
-
1129
- @override
1130
- def __str__(self) -> str:
1131
- return f"Unable to parse {self.text!r} into a datetime"
1132
-
1133
-
1134
- ##
1135
-
1136
-
1137
882
  def serialize_month(month: Month, /) -> str:
1138
883
  """Serialize a month."""
1139
884
  return f"{month.year:04}-{month.month:02}"
@@ -1323,46 +1068,32 @@ __all__ = [
1323
1068
  "AddDurationError",
1324
1069
  "AddWeekdaysError",
1325
1070
  "AreEqualDateTimesError",
1326
- "AreEqualDatesOrDateTimesError",
1327
1071
  "CheckDateNotDateTimeError",
1328
1072
  "DateOrMonth",
1329
1073
  "EnsureMonthError",
1330
1074
  "GetMinMaxDateError",
1331
1075
  "MeanDateTimeError",
1332
1076
  "MeanTimeDeltaError",
1333
- "MillisecondsSinceEpochError",
1334
1077
  "Month",
1335
1078
  "MonthError",
1336
1079
  "MonthLike",
1337
- "ParseDateCompactError",
1338
- "ParseDateTimeCompactError",
1339
1080
  "ParseMonthError",
1340
- "SerializeCompactError",
1341
1081
  "SubDurationError",
1342
1082
  "TimedeltaToMillisecondsError",
1343
1083
  "YieldDaysError",
1344
1084
  "YieldWeekdaysError",
1345
1085
  "add_duration",
1346
1086
  "add_weekdays",
1347
- "are_equal_date_durations",
1348
- "are_equal_dates_or_datetimes",
1349
- "are_equal_datetime_durations",
1350
1087
  "are_equal_datetimes",
1351
- "are_equal_months",
1352
1088
  "check_date_not_datetime",
1353
1089
  "date_duration_to_int",
1354
1090
  "date_duration_to_timedelta",
1355
- "date_to_datetime",
1356
1091
  "date_to_month",
1357
1092
  "datetime_duration_to_float",
1358
1093
  "datetime_duration_to_microseconds",
1359
- "datetime_duration_to_milliseconds",
1360
1094
  "datetime_duration_to_timedelta",
1361
1095
  "datetime_utc",
1362
- "days_since_epoch",
1363
- "days_since_epoch_to_date",
1364
1096
  "ensure_month",
1365
- "format_datetime_local_and_utc",
1366
1097
  "get_date",
1367
1098
  "get_datetime",
1368
1099
  "get_half_years",
@@ -1373,27 +1104,19 @@ __all__ = [
1373
1104
  "get_today",
1374
1105
  "get_years",
1375
1106
  "is_integral_timedelta",
1376
- "is_plain_datetime",
1377
1107
  "is_weekday",
1378
1108
  "is_zero_time",
1379
- "is_zoned_datetime",
1380
1109
  "maybe_sub_pct_y",
1381
1110
  "mean_datetime",
1382
1111
  "mean_timedelta",
1383
1112
  "microseconds_since_epoch",
1384
1113
  "microseconds_since_epoch_to_datetime",
1385
1114
  "microseconds_to_timedelta",
1386
- "milliseconds_since_epoch",
1387
- "milliseconds_since_epoch_to_datetime",
1388
- "milliseconds_to_timedelta",
1389
- "parse_date_compact",
1390
- "parse_datetime_compact",
1391
1115
  "parse_month",
1392
1116
  "parse_two_digit_year",
1393
1117
  "round_datetime",
1394
1118
  "round_to_next_weekday",
1395
1119
  "round_to_prev_weekday",
1396
- "serialize_compact",
1397
1120
  "serialize_month",
1398
1121
  "sub_duration",
1399
1122
  "timedelta_since_epoch",
utilities/iterables.py CHANGED
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import builtins
4
- import datetime as dt
5
4
  from collections import Counter
6
5
  from collections.abc import (
7
6
  Callable,
@@ -34,7 +33,7 @@ from typing import (
34
33
  )
35
34
 
36
35
  from utilities.errors import ImpossibleCaseError
37
- from utilities.functions import ensure_hashable, ensure_not_none, ensure_str
36
+ from utilities.functions import ensure_hashable, ensure_str
38
37
  from utilities.math import (
39
38
  _CheckIntegerEqualError,
40
39
  _CheckIntegerEqualOrApproxError,
@@ -45,7 +44,6 @@ from utilities.math import (
45
44
  from utilities.reprlib import get_repr
46
45
  from utilities.sentinel import Sentinel, sentinel
47
46
  from utilities.types import Sign, THashable, TSupportsAdd, TSupportsLT
48
- from utilities.zoneinfo import UTC
49
47
 
50
48
  if TYPE_CHECKING:
51
49
  from types import NoneType
@@ -1326,9 +1324,6 @@ def _sort_iterable_cmp(x: Any, y: Any, /) -> Sign:
1326
1324
  if x is None:
1327
1325
  y = cast("NoneType", y)
1328
1326
  return 0
1329
- if isinstance(x, dt.datetime):
1330
- y = cast("dt.datetime", y)
1331
- return _sort_iterable_cmp_datetimes(x, y)
1332
1327
  if isinstance(x, float):
1333
1328
  y = cast("float", y)
1334
1329
  return _sort_iterable_cmp_floats(x, y)
@@ -1371,30 +1366,6 @@ class SortIterableError(Exception):
1371
1366
  return f"Unable to sort {get_repr(self.x)} and {get_repr(self.y)}"
1372
1367
 
1373
1368
 
1374
- def _sort_iterable_cmp_datetimes(x: dt.datetime, y: dt.datetime, /) -> Sign:
1375
- """Compare two datetimes."""
1376
- match x.tzinfo, y.tzinfo:
1377
- case None, None:
1378
- return cast("Sign", (x > y) - (x < y))
1379
- case dt.tzinfo(), None:
1380
- return 1
1381
- case None, dt.tzinfo():
1382
- return -1
1383
- case dt.tzinfo(), dt.tzinfo():
1384
- x_utc = x.astimezone(tz=UTC)
1385
- y_utc = y.astimezone(tz=UTC)
1386
- result = cast("Sign", (x_utc > y_utc) - (x_utc < y_utc))
1387
- if result != 0:
1388
- return result
1389
- x_time_zone = ensure_not_none(ensure_not_none(x.tzinfo).tzname(x))
1390
- y_time_zone = ensure_not_none(ensure_not_none(y.tzinfo).tzname(y))
1391
- return cast(
1392
- "Sign", (x_time_zone > y_time_zone) - (x_time_zone < y_time_zone)
1393
- )
1394
- case _ as never:
1395
- assert_never(never)
1396
-
1397
-
1398
1369
  def _sort_iterable_cmp_floats(x: float, y: float, /) -> Sign:
1399
1370
  """Compare two floats."""
1400
1371
  x_nan, y_nan = map(isnan, [x, y])
utilities/operator.py CHANGED
@@ -1,23 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
- import datetime as dt
4
3
  from collections.abc import Callable, Mapping, Sequence
5
4
  from collections.abc import Set as AbstractSet
6
5
  from dataclasses import asdict, dataclass
7
6
  from typing import TYPE_CHECKING, Any, TypeVar, cast, override
8
7
 
9
8
  import utilities.math
10
- from utilities.datetime import (
11
- AreEqualDatesOrDateTimesError,
12
- AreEqualDateTimesError,
13
- are_equal_dates_or_datetimes,
14
- )
15
9
  from utilities.functions import is_dataclass_instance
16
10
  from utilities.iterables import SortIterableError, sort_iterable
17
11
  from utilities.reprlib import get_repr
18
12
 
19
13
  if TYPE_CHECKING:
20
- from utilities.types import Dataclass, DateOrDateTime, Number
14
+ from utilities.types import Dataclass, Number
21
15
 
22
16
  _T = TypeVar("_T")
23
17
 
@@ -51,12 +45,6 @@ def is_equal(
51
45
  if isinstance(x, str): # else Sequence
52
46
  y = cast("str", y)
53
47
  return x == y
54
- if isinstance(x, dt.date | dt.datetime):
55
- y = cast("DateOrDateTime", y)
56
- try:
57
- return are_equal_dates_or_datetimes(x, y)
58
- except (AreEqualDateTimesError, AreEqualDatesOrDateTimesError):
59
- return False
60
48
  if is_dataclass_instance(x):
61
49
  y = cast("Dataclass", y)
62
50
  x_values = asdict(x)
@@ -80,8 +68,26 @@ def is_equal(
80
68
  try:
81
69
  x_sorted = sort_iterable(x)
82
70
  y_sorted = sort_iterable(y)
83
- except SortIterableError as error:
84
- raise IsEqualError(x=error.x, y=error.y) from None
71
+ except SortIterableError:
72
+ x_in_y = all(
73
+ any(
74
+ is_equal(
75
+ x_i, y_i, rel_tol=rel_tol, abs_tol=abs_tol, extra=extra
76
+ )
77
+ for y_i in y
78
+ )
79
+ for x_i in x
80
+ )
81
+ y_in_x = all(
82
+ any(
83
+ is_equal(
84
+ x_i, y_i, rel_tol=rel_tol, abs_tol=abs_tol, extra=extra
85
+ )
86
+ for x_i in x
87
+ )
88
+ for y_i in y
89
+ )
90
+ return x_in_y and y_in_x
85
91
  return is_equal(x_sorted, y_sorted, rel_tol=rel_tol, abs_tol=abs_tol)
86
92
  if isinstance(x, Sequence):
87
93
  y = cast("Sequence[Any]", y)
utilities/orjson.py CHANGED
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import datetime as dt
4
3
  import re
5
4
  from collections.abc import Callable, Iterable, Mapping, Sequence
6
5
  from contextlib import suppress
@@ -23,7 +22,15 @@ from orjson import (
23
22
  dumps,
24
23
  loads,
25
24
  )
26
- from whenever import ZonedDateTime
25
+ from whenever import (
26
+ Date,
27
+ DateDelta,
28
+ DateTimeDelta,
29
+ PlainDateTime,
30
+ Time,
31
+ TimeDelta,
32
+ ZonedDateTime,
33
+ )
27
34
 
28
35
  from utilities.concurrent import concurrent_map
29
36
  from utilities.dataclasses import dataclass_to_dict
@@ -39,27 +46,13 @@ from utilities.logging import get_logging_level_number
39
46
  from utilities.math import MAX_INT64, MIN_INT64
40
47
  from utilities.types import Dataclass, LogLevel, MaybeIterable, PathLike, StrMapping
41
48
  from utilities.tzlocal import LOCAL_TIME_ZONE
42
- from utilities.uuid import UUID_PATTERN
43
49
  from utilities.version import Version, parse_version
44
- from utilities.whenever import (
45
- parse_date,
46
- parse_plain_datetime,
47
- parse_time,
48
- parse_timedelta,
49
- parse_zoned_datetime,
50
- serialize_date,
51
- serialize_datetime,
52
- serialize_time,
53
- serialize_timedelta,
54
- )
55
50
  from utilities.whenever2 import from_timestamp
56
51
 
57
52
  if TYPE_CHECKING:
58
53
  from collections.abc import Set as AbstractSet
59
54
  from logging import _FormatStyle
60
55
 
61
- from whenever import Date
62
-
63
56
  from utilities.types import Parallelism
64
57
 
65
58
 
@@ -70,26 +63,25 @@ if TYPE_CHECKING:
70
63
  class _Prefixes(Enum):
71
64
  dataclass = "dc"
72
65
  date = "d"
73
- datetime = "dt"
66
+ date_delta = "dd"
67
+ date_time_delta = "D"
74
68
  enum = "e"
75
- exception_class = "exc"
76
- exception_instance = "exi"
69
+ exception_class = "Ex"
70
+ exception_instance = "ex"
77
71
  float_ = "fl"
78
72
  frozenset_ = "fr"
79
73
  list_ = "l"
80
- nan = "nan"
81
74
  none = "none"
82
75
  path = "p"
83
- pos_inf = "pos_inf"
84
- neg_inf = "neg_inf"
76
+ plain_date_time = "pd"
85
77
  set_ = "s"
86
- timedelta = "td"
87
- time = "tm"
78
+ time = "ti"
79
+ time_delta = "td"
88
80
  tuple_ = "tu"
89
81
  unserializable = "un"
90
82
  uuid = "uu"
91
83
  version = "v"
92
- zoned_datetime = "zd"
84
+ zoned_date_time = "zd"
93
85
 
94
86
 
95
87
  type _DataclassHook = Callable[[type[Dataclass], StrMapping], StrMapping]
@@ -160,14 +152,12 @@ def _pre_process(
160
152
  # singletons
161
153
  case None:
162
154
  return f"[{_Prefixes.none.value}]"
163
- case dt.datetime() as datetime:
164
- return f"[{_Prefixes.datetime.value}]{serialize_datetime(datetime)}"
165
- case dt.date() as date: # after datetime
166
- return f"[{_Prefixes.date.value}]{serialize_date(date)}"
167
- case dt.time() as time:
168
- return f"[{_Prefixes.time.value}]{serialize_time(time)}"
169
- case dt.timedelta() as timedelta:
170
- return f"[{_Prefixes.timedelta.value}]{serialize_timedelta(timedelta)}"
155
+ case Date() as date:
156
+ return f"[{_Prefixes.date.value}]{date}"
157
+ case DateDelta() as date:
158
+ return f"[{_Prefixes.date_delta.value}]{date}"
159
+ case DateTimeDelta() as date:
160
+ return f"[{_Prefixes.date_time_delta.value}]{date}"
171
161
  case Exception() as error_:
172
162
  return {
173
163
  f"[{_Prefixes.exception_instance.value}|{type(error_).__qualname__}]": pre(
@@ -182,18 +172,24 @@ def _pre_process(
182
172
  if MIN_INT64 <= int_ <= MAX_INT64:
183
173
  return int_
184
174
  raise _SerializeIntegerError(obj=int_)
185
- case UUID() as uuid:
186
- return f"[{_Prefixes.uuid.value}]{uuid}"
187
175
  case Path() as path:
188
176
  return f"[{_Prefixes.path.value}]{path!s}"
177
+ case PlainDateTime() as datetime:
178
+ return f"[{_Prefixes.plain_date_time.value}]{datetime}"
189
179
  case str() as str_:
190
180
  return str_
181
+ case Time() as time:
182
+ return f"[{_Prefixes.time.value}]{time}"
183
+ case TimeDelta() as time_delta:
184
+ return f"[{_Prefixes.time_delta.value}]{time_delta}"
191
185
  case type() as error_cls if issubclass(error_cls, Exception):
192
186
  return f"[{_Prefixes.exception_class.value}|{error_cls.__qualname__}]"
187
+ case UUID() as uuid:
188
+ return f"[{_Prefixes.uuid.value}]{uuid}"
193
189
  case Version() as version:
194
190
  return f"[{_Prefixes.version.value}]{version!s}"
195
191
  case ZonedDateTime() as datetime:
196
- return f"[{_Prefixes.zoned_datetime.value}]{datetime}"
192
+ return f"[{_Prefixes.zoned_date_time.value}]{datetime}"
197
193
  # contains
198
194
  case Dataclass() as dataclass:
199
195
  asdict = dataclass_to_dict(
@@ -338,51 +334,36 @@ def deserialize(
338
334
  )
339
335
 
340
336
 
341
- _NONE_PATTERN = re.compile(r"^\[" + _Prefixes.none.value + r"\]$")
342
- _LOCAL_DATETIME_PATTERN = re.compile(
343
- r"^\["
344
- + _Prefixes.datetime.value
345
- + r"\](\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?)$"
346
- )
347
- _UUID_PATTERN = re.compile(r"^\[" + _Prefixes.uuid.value + r"\](" + UUID_PATTERN + ")$")
348
- _ZONED_DATETIME_PATTERN = re.compile(
349
- r"^\["
350
- + _Prefixes.datetime.value
351
- + r"\](\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?[\+\-]\d{2}:\d{2}(?::\d{2})?\[(?!(?:dt\.)).+?\])$"
352
- )
353
- _ZONED_DATETIME_PATTERN2 = re.compile(
354
- r"^\["
355
- + _Prefixes.zoned_datetime.value
356
- + r"\](\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?[\+\-]\d{2}:\d{2}(?::\d{2})?\[(?!(?:dt\.)).+?\])$"
357
- )
358
-
359
-
360
- def _make_unit_pattern(prefix: _Prefixes, /) -> Pattern[str]:
361
- return re.compile(r"^\[" + prefix.value + r"\](.+)$")
362
-
363
-
364
337
  (
365
338
  _DATE_PATTERN,
339
+ _DATE_DELTA_PATTERN,
340
+ _DATE_TIME_DELTA_PATTERN,
366
341
  _FLOAT_PATTERN,
342
+ _NONE_PATTERN,
367
343
  _PATH_PATTERN,
344
+ _PLAIN_DATE_TIME_PATTERN,
368
345
  _TIME_PATTERN,
369
- _TIMEDELTA_PATTERN,
346
+ _TIME_DELTA_PATTERN,
347
+ _UUID_PATTERN,
370
348
  _VERSION_PATTERN,
371
- ) = map(
372
- _make_unit_pattern,
373
- [
349
+ _ZONED_DATE_TIME_PATTERN,
350
+ ) = [
351
+ re.compile(r"^\[" + p.value + r"\](" + ".*" + ")$")
352
+ for p in [
374
353
  _Prefixes.date,
354
+ _Prefixes.date_delta,
355
+ _Prefixes.date_time_delta,
375
356
  _Prefixes.float_,
357
+ _Prefixes.none,
376
358
  _Prefixes.path,
359
+ _Prefixes.plain_date_time,
377
360
  _Prefixes.time,
378
- _Prefixes.timedelta,
361
+ _Prefixes.time_delta,
362
+ _Prefixes.uuid,
379
363
  _Prefixes.version,
380
- ],
381
- )
382
-
383
-
384
- def _make_container_pattern(prefix: _Prefixes, /) -> Pattern[str]:
385
- return re.compile(r"^\[" + prefix.value + r"(?:\|(.+))?\]$")
364
+ _Prefixes.zoned_date_time,
365
+ ]
366
+ ]
386
367
 
387
368
 
388
369
  (
@@ -394,9 +375,9 @@ def _make_container_pattern(prefix: _Prefixes, /) -> Pattern[str]:
394
375
  _LIST_PATTERN,
395
376
  _SET_PATTERN,
396
377
  _TUPLE_PATTERN,
397
- ) = map(
398
- _make_container_pattern,
399
- [
378
+ ) = [
379
+ re.compile(r"^\[" + p.value + r"(?:\|(.+))?\]$")
380
+ for p in [
400
381
  _Prefixes.dataclass,
401
382
  _Prefixes.enum,
402
383
  _Prefixes.exception_class,
@@ -405,8 +386,8 @@ def _make_container_pattern(prefix: _Prefixes, /) -> Pattern[str]:
405
386
  _Prefixes.list_,
406
387
  _Prefixes.set_,
407
388
  _Prefixes.tuple_,
408
- ],
409
- )
389
+ ]
390
+ ]
410
391
 
411
392
 
412
393
  def _object_hook(
@@ -425,24 +406,26 @@ def _object_hook(
425
406
  if match := _NONE_PATTERN.search(text):
426
407
  return None
427
408
  if match := _DATE_PATTERN.search(text):
428
- return parse_date(match.group(1))
409
+ return Date.parse_common_iso(match.group(1))
410
+ if match := _DATE_DELTA_PATTERN.search(text):
411
+ return DateDelta.parse_common_iso(match.group(1))
412
+ if match := _DATE_TIME_DELTA_PATTERN.search(text):
413
+ return DateTimeDelta.parse_common_iso(match.group(1))
429
414
  if match := _FLOAT_PATTERN.search(text):
430
415
  return float(match.group(1))
431
- if match := _LOCAL_DATETIME_PATTERN.search(text):
432
- return parse_plain_datetime(match.group(1))
433
416
  if match := _PATH_PATTERN.search(text):
434
417
  return Path(match.group(1))
418
+ if match := _PLAIN_DATE_TIME_PATTERN.search(text):
419
+ return PlainDateTime.parse_common_iso(match.group(1))
435
420
  if match := _TIME_PATTERN.search(text):
436
- return parse_time(match.group(1))
437
- if match := _TIMEDELTA_PATTERN.search(text):
438
- return parse_timedelta(match.group(1))
421
+ return Time.parse_common_iso(match.group(1))
422
+ if match := _TIME_DELTA_PATTERN.search(text):
423
+ return TimeDelta.parse_common_iso(match.group(1))
439
424
  if match := _UUID_PATTERN.search(text):
440
425
  return UUID(match.group(1))
441
426
  if match := _VERSION_PATTERN.search(text):
442
427
  return parse_version(match.group(1))
443
- if match := _ZONED_DATETIME_PATTERN.search(text):
444
- return parse_zoned_datetime(match.group(1))
445
- if match := _ZONED_DATETIME_PATTERN2.search(text):
428
+ if match := _ZONED_DATE_TIME_PATTERN.search(text):
446
429
  return ZonedDateTime.parse_common_iso(match.group(1))
447
430
  if (
448
431
  exc_class := _object_hook_exception_class(
utilities/parse.py CHANGED
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import datetime as dt
4
3
  from contextlib import suppress
5
4
  from dataclasses import dataclass
6
5
  from enum import Enum
@@ -9,6 +8,16 @@ from re import DOTALL
9
8
  from types import NoneType
10
9
  from typing import TYPE_CHECKING, Any, override
11
10
 
11
+ from whenever import (
12
+ Date,
13
+ DateDelta,
14
+ DateTimeDelta,
15
+ PlainDateTime,
16
+ Time,
17
+ TimeDelta,
18
+ ZonedDateTime,
19
+ )
20
+
12
21
  from utilities.enum import ParseEnumError, parse_enum
13
22
  from utilities.iterables import OneEmptyError, OneNonUniqueError, one, one_str
14
23
  from utilities.math import ParseNumberError, parse_number
@@ -26,7 +35,7 @@ from utilities.text import (
26
35
  split_key_value_pairs,
27
36
  split_str,
28
37
  )
29
- from utilities.types import Duration, Number, ParseObjectExtra, SerializeObjectExtra
38
+ from utilities.types import Number, ParseObjectExtra, SerializeObjectExtra
30
39
  from utilities.typing import (
31
40
  get_args,
32
41
  is_dict_type,
@@ -182,6 +191,14 @@ def _parse_object_type(
182
191
  return parse_enum(text, cls, case_sensitive=case_sensitive)
183
192
  except ParseEnumError:
184
193
  raise _ParseObjectParseError(type_=cls, text=text) from None
194
+ if issubclass(
195
+ cls,
196
+ (Date, DateDelta, DateTimeDelta, PlainDateTime, Time, TimeDelta, ZonedDateTime),
197
+ ):
198
+ try:
199
+ return cls.parse_common_iso(text)
200
+ except ValueError:
201
+ raise _ParseObjectParseError(type_=cls, text=text) from None
185
202
  if issubclass(cls, Path):
186
203
  return Path(text).expanduser()
187
204
  if issubclass(cls, Sentinel):
@@ -194,34 +211,6 @@ def _parse_object_type(
194
211
  return parse_version(text)
195
212
  except ParseVersionError:
196
213
  raise _ParseObjectParseError(type_=cls, text=text) from None
197
- if is_subclass_gen(cls, dt.date):
198
- from utilities.whenever import ParseDateError, parse_date
199
-
200
- try:
201
- return parse_date(text)
202
- except ParseDateError:
203
- raise _ParseObjectParseError(type_=cls, text=text) from None
204
- if is_subclass_gen(cls, dt.datetime):
205
- from utilities.whenever import ParseDateTimeError, parse_datetime
206
-
207
- try:
208
- return parse_datetime(text)
209
- except ParseDateTimeError:
210
- raise _ParseObjectParseError(type_=cls, text=text) from None
211
- if issubclass(cls, dt.time):
212
- from utilities.whenever import ParseTimeError, parse_time
213
-
214
- try:
215
- return parse_time(text)
216
- except ParseTimeError:
217
- raise _ParseObjectParseError(type_=cls, text=text) from None
218
- if issubclass(cls, dt.timedelta):
219
- from utilities.whenever import ParseTimedeltaError, parse_timedelta
220
-
221
- try:
222
- return parse_timedelta(text)
223
- except ParseTimedeltaError:
224
- raise _ParseObjectParseError(type_=cls, text=text) from None
225
214
  raise _ParseObjectParseError(type_=cls, text=text)
226
215
 
227
216
 
@@ -374,13 +363,6 @@ def _parse_object_union_type(type_: Any, text: str, /) -> Any:
374
363
  return parse_number(text)
375
364
  except ParseNumberError:
376
365
  raise _ParseObjectParseError(type_=type_, text=text) from None
377
- if type_ is Duration:
378
- from utilities.whenever import ParseDurationError, parse_duration
379
-
380
- try:
381
- return parse_duration(text)
382
- except ParseDurationError:
383
- raise _ParseObjectParseError(type_=type_, text=text) from None
384
366
  raise _ParseObjectParseError(type_=type_, text=text) from None
385
367
 
386
368
 
@@ -464,22 +446,11 @@ def serialize_object(
464
446
  obj, bool | int | float | str | Path | Sentinel | Version
465
447
  ):
466
448
  return str(obj)
467
- if is_instance_gen(obj, dt.date):
468
- from utilities.whenever import serialize_date
469
-
470
- return serialize_date(obj)
471
- if is_instance_gen(obj, dt.datetime):
472
- from utilities.whenever import serialize_datetime
473
-
474
- return serialize_datetime(obj)
475
- if isinstance(obj, dt.time):
476
- from utilities.whenever import serialize_time
477
-
478
- return serialize_time(obj)
479
- if isinstance(obj, dt.timedelta):
480
- from utilities.whenever import serialize_timedelta
481
-
482
- return serialize_timedelta(obj)
449
+ if isinstance(
450
+ obj,
451
+ (Date, DateDelta, DateTimeDelta, PlainDateTime, Time, TimeDelta, ZonedDateTime),
452
+ ):
453
+ return obj.format_common_iso()
483
454
  if isinstance(obj, Enum):
484
455
  return obj.name
485
456
  if isinstance(obj, dict):
utilities/whenever.py CHANGED
@@ -1,27 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import datetime as dt
4
- import re
5
4
  from contextlib import suppress
6
5
  from dataclasses import dataclass
7
6
  from typing import TYPE_CHECKING, override
8
7
 
9
- from whenever import (
10
- Date,
11
- DateTimeDelta,
12
- PlainDateTime,
13
- Time,
14
- TimeZoneNotFoundError,
15
- ZonedDateTime,
16
- )
8
+ from whenever import DateTimeDelta, TimeZoneNotFoundError, ZonedDateTime
17
9
 
18
10
  from utilities.datetime import (
19
11
  _MICROSECONDS_PER_DAY,
20
12
  _MICROSECONDS_PER_SECOND,
21
13
  ZERO_TIME,
22
- check_date_not_datetime,
23
14
  datetime_duration_to_microseconds,
24
- parse_two_digit_year,
25
15
  )
26
16
  from utilities.math import ParseNumberError, parse_number
27
17
  from utilities.re import (
@@ -35,7 +25,6 @@ from utilities.zoneinfo import UTC, ensure_time_zone, get_time_zone_name
35
25
  if TYPE_CHECKING:
36
26
  from utilities.types import Duration
37
27
 
38
-
39
28
  MAX_SERIALIZABLE_TIMEDELTA = dt.timedelta(days=3652060, microseconds=-1)
40
29
  MIN_SERIALIZABLE_TIMEDELTA = -MAX_SERIALIZABLE_TIMEDELTA
41
30
 
@@ -84,56 +73,6 @@ class _CheckValidZonedDateTimeUnequalError(CheckValidZonedDateTimeError):
84
73
  ##
85
74
 
86
75
 
87
- _PARSE_DATE_YYMMDD_REGEX = re.compile(r"^(\d{2})(\d{2})(\d{2})$")
88
-
89
-
90
- def parse_date(date: str, /) -> dt.date:
91
- """Parse a string into a date."""
92
- try:
93
- w_date = Date.parse_common_iso(date)
94
- except ValueError:
95
- try:
96
- ((year2, month, day),) = _PARSE_DATE_YYMMDD_REGEX.findall(date)
97
- except ValueError:
98
- raise ParseDateError(date=date) from None
99
- year = parse_two_digit_year(year2)
100
- return dt.date(year=int(year), month=int(month), day=int(day))
101
- return w_date.py_date()
102
-
103
-
104
- @dataclass(kw_only=True, slots=True)
105
- class ParseDateError(Exception):
106
- date: str
107
-
108
- @override
109
- def __str__(self) -> str:
110
- return f"Unable to parse date; got {self.date!r}"
111
-
112
-
113
- ##
114
-
115
-
116
- def parse_datetime(datetime: str, /) -> dt.datetime:
117
- """Parse a string into a datetime."""
118
- with suppress(ParsePlainDateTimeError):
119
- return parse_plain_datetime(datetime)
120
- with suppress(ParseZonedDateTimeError):
121
- return parse_zoned_datetime(datetime)
122
- raise ParseDateTimeError(datetime=datetime) from None
123
-
124
-
125
- @dataclass(kw_only=True, slots=True)
126
- class ParseDateTimeError(Exception):
127
- datetime: str
128
-
129
- @override
130
- def __str__(self) -> str:
131
- return f"Unable to parse datetime; got {self.datetime!r}"
132
-
133
-
134
- ##
135
-
136
-
137
76
  def parse_duration(duration: str, /) -> Duration:
138
77
  """Parse a string into a Duration."""
139
78
  with suppress(ParseNumberError):
@@ -156,48 +95,6 @@ class ParseDurationError(Exception):
156
95
  ##
157
96
 
158
97
 
159
- def parse_plain_datetime(datetime: str, /) -> dt.datetime:
160
- """Parse a string into a plain datetime."""
161
- try:
162
- ldt = PlainDateTime.parse_common_iso(datetime)
163
- except ValueError:
164
- raise ParsePlainDateTimeError(datetime=datetime) from None
165
- return ldt.py_datetime()
166
-
167
-
168
- @dataclass(kw_only=True, slots=True)
169
- class ParsePlainDateTimeError(Exception):
170
- datetime: str
171
-
172
- @override
173
- def __str__(self) -> str:
174
- return f"Unable to parse plain datetime; got {self.datetime!r}"
175
-
176
-
177
- ##
178
-
179
-
180
- def parse_time(time: str, /) -> dt.time:
181
- """Parse a string into a time."""
182
- try:
183
- w_time = Time.parse_common_iso(time)
184
- except ValueError:
185
- raise ParseTimeError(time=time) from None
186
- return w_time.py_time()
187
-
188
-
189
- @dataclass(kw_only=True, slots=True)
190
- class ParseTimeError(Exception):
191
- time: str
192
-
193
- @override
194
- def __str__(self) -> str:
195
- return f"Unable to parse time; got {self.time!r}"
196
-
197
-
198
- ##
199
-
200
-
201
98
  def parse_timedelta(timedelta: str, /) -> dt.timedelta:
202
99
  """Parse a string into a timedelta."""
203
100
  with suppress(ExtractGroupError):
@@ -245,47 +142,6 @@ class _ParseTimedeltaNanosecondError(ParseTimedeltaError):
245
142
  ##
246
143
 
247
144
 
248
- def parse_zoned_datetime(datetime: str, /) -> dt.datetime:
249
- """Parse a string into a zoned datetime."""
250
- try:
251
- zdt = ZonedDateTime.parse_common_iso(datetime)
252
- except ValueError:
253
- raise ParseZonedDateTimeError(datetime=datetime) from None
254
- return zdt.py_datetime()
255
-
256
-
257
- @dataclass(kw_only=True, slots=True)
258
- class ParseZonedDateTimeError(Exception):
259
- datetime: str
260
-
261
- @override
262
- def __str__(self) -> str:
263
- return f"Unable to parse zoned datetime; got {self.datetime!r}"
264
-
265
-
266
- ##
267
-
268
-
269
- def serialize_date(date: dt.date, /) -> str:
270
- """Serialize a date."""
271
- check_date_not_datetime(date)
272
- return Date.from_py_date(date).format_common_iso()
273
-
274
-
275
- ##
276
-
277
-
278
- def serialize_datetime(datetime: dt.datetime, /) -> str:
279
- """Serialize a datetime."""
280
- try:
281
- return serialize_plain_datetime(datetime)
282
- except SerializePlainDateTimeError:
283
- return serialize_zoned_datetime(datetime)
284
-
285
-
286
- ##
287
-
288
-
289
145
  def serialize_duration(duration: Duration, /) -> str:
290
146
  """Serialize a duration."""
291
147
  if isinstance(duration, int | float):
@@ -308,35 +164,6 @@ class SerializeDurationError(Exception):
308
164
  ##
309
165
 
310
166
 
311
- def serialize_plain_datetime(datetime: dt.datetime, /) -> str:
312
- """Serialize a plain datetime."""
313
- try:
314
- pdt = PlainDateTime.from_py_datetime(datetime)
315
- except ValueError:
316
- raise SerializePlainDateTimeError(datetime=datetime) from None
317
- return pdt.format_common_iso()
318
-
319
-
320
- @dataclass(kw_only=True, slots=True)
321
- class SerializePlainDateTimeError(Exception):
322
- datetime: dt.datetime
323
-
324
- @override
325
- def __str__(self) -> str:
326
- return f"Unable to serialize plain datetime; got {self.datetime}"
327
-
328
-
329
- ##
330
-
331
-
332
- def serialize_time(time: dt.time, /) -> str:
333
- """Serialize a time."""
334
- return Time.from_py_time(time).format_common_iso()
335
-
336
-
337
- ##
338
-
339
-
340
167
  def serialize_timedelta(timedelta: dt.timedelta, /) -> str:
341
168
  """Serialize a timedelta."""
342
169
  try:
@@ -358,31 +185,6 @@ class SerializeTimeDeltaError(Exception):
358
185
  ##
359
186
 
360
187
 
361
- def serialize_zoned_datetime(datetime: dt.datetime, /) -> str:
362
- """Serialize a zoned datetime."""
363
- if datetime.tzinfo is dt.UTC:
364
- return serialize_zoned_datetime( # skipif-ci-and-windows
365
- datetime.replace(tzinfo=UTC)
366
- )
367
- try:
368
- zdt = ZonedDateTime.from_py_datetime(datetime)
369
- except ValueError:
370
- raise SerializeZonedDateTimeError(datetime=datetime) from None
371
- return zdt.format_common_iso()
372
-
373
-
374
- @dataclass(kw_only=True, slots=True)
375
- class SerializeZonedDateTimeError(Exception):
376
- datetime: dt.datetime
377
-
378
- @override
379
- def __str__(self) -> str:
380
- return f"Unable to serialize zoned datetime; got {self.datetime}"
381
-
382
-
383
- ##
384
-
385
-
386
188
  def _to_datetime_delta(timedelta: dt.timedelta, /) -> DateTimeDelta:
387
189
  """Serialize a timedelta."""
388
190
  total_microseconds = datetime_duration_to_microseconds(timedelta)
@@ -415,30 +217,14 @@ __all__ = [
415
217
  "MAX_SERIALIZABLE_TIMEDELTA",
416
218
  "MIN_SERIALIZABLE_TIMEDELTA",
417
219
  "CheckValidZonedDateTimeError",
418
- "ParseDateError",
419
- "ParseDateTimeError",
420
220
  "ParseDurationError",
421
- "ParsePlainDateTimeError",
422
- "ParseTimeError",
423
221
  "ParseTimedeltaError",
424
- "ParseZonedDateTimeError",
425
222
  "SerializeDurationError",
426
- "SerializePlainDateTimeError",
427
223
  "SerializeTimeDeltaError",
428
- "SerializeZonedDateTimeError",
429
224
  "check_valid_zoned_datetime",
430
- "parse_date",
431
- "parse_datetime",
225
+ "check_valid_zoned_datetime",
432
226
  "parse_duration",
433
- "parse_plain_datetime",
434
- "parse_time",
435
227
  "parse_timedelta",
436
- "parse_zoned_datetime",
437
- "serialize_date",
438
- "serialize_datetime",
439
228
  "serialize_duration",
440
- "serialize_plain_datetime",
441
- "serialize_time",
442
229
  "serialize_timedelta",
443
- "serialize_zoned_datetime",
444
230
  ]