dycw-utilities 0.131.16__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.
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)