dycw-utilities 0.131.17__py3-none-any.whl → 0.131.19__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/whenever.py DELETED
@@ -1,444 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import datetime as dt
4
- import re
5
- from contextlib import suppress
6
- from dataclasses import dataclass
7
- from typing import TYPE_CHECKING, override
8
-
9
- from whenever import (
10
- Date,
11
- DateTimeDelta,
12
- PlainDateTime,
13
- Time,
14
- TimeZoneNotFoundError,
15
- ZonedDateTime,
16
- )
17
-
18
- from utilities.datetime import (
19
- _MICROSECONDS_PER_DAY,
20
- _MICROSECONDS_PER_SECOND,
21
- ZERO_TIME,
22
- check_date_not_datetime,
23
- datetime_duration_to_microseconds,
24
- parse_two_digit_year,
25
- )
26
- from utilities.math import ParseNumberError, parse_number
27
- from utilities.re import (
28
- ExtractGroupError,
29
- ExtractGroupsError,
30
- extract_group,
31
- extract_groups,
32
- )
33
- from utilities.zoneinfo import UTC, ensure_time_zone, get_time_zone_name
34
-
35
- if TYPE_CHECKING:
36
- from utilities.types import Duration
37
-
38
-
39
- MAX_SERIALIZABLE_TIMEDELTA = dt.timedelta(days=3652060, microseconds=-1)
40
- MIN_SERIALIZABLE_TIMEDELTA = -MAX_SERIALIZABLE_TIMEDELTA
41
-
42
-
43
- ##
44
-
45
-
46
- def check_valid_zoned_datetime(datetime: dt.datetime, /) -> None:
47
- """Check if a zoned datetime is valid."""
48
- time_zone = ensure_time_zone(datetime) # skipif-ci-and-windows
49
- datetime2 = datetime.replace(tzinfo=time_zone) # skipif-ci-and-windows
50
- try: # skipif-ci-and-windows
51
- result = (
52
- ZonedDateTime.from_py_datetime(datetime2)
53
- .to_tz(get_time_zone_name(UTC))
54
- .to_tz(get_time_zone_name(time_zone))
55
- .py_datetime()
56
- )
57
- except TimeZoneNotFoundError: # pragma: no cover
58
- raise _CheckValidZonedDateTimeInvalidTimeZoneError(datetime=datetime) from None
59
- if result != datetime2: # skipif-ci-and-windows
60
- raise _CheckValidZonedDateTimeUnequalError(datetime=datetime, result=result)
61
-
62
-
63
- @dataclass(kw_only=True, slots=True)
64
- class CheckValidZonedDateTimeError(Exception):
65
- datetime: dt.datetime
66
-
67
-
68
- @dataclass(kw_only=True, slots=True)
69
- class _CheckValidZonedDateTimeInvalidTimeZoneError(CheckValidZonedDateTimeError):
70
- @override
71
- def __str__(self) -> str:
72
- return f"Invalid timezone; got {self.datetime.tzinfo}" # pragma: no cover
73
-
74
-
75
- @dataclass(kw_only=True, slots=True)
76
- class _CheckValidZonedDateTimeUnequalError(CheckValidZonedDateTimeError):
77
- result: dt.datetime
78
-
79
- @override
80
- def __str__(self) -> str:
81
- return f"Zoned datetime must be valid; got {self.datetime} != {self.result}" # skipif-ci-and-windows
82
-
83
-
84
- ##
85
-
86
-
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
- def parse_duration(duration: str, /) -> Duration:
138
- """Parse a string into a Duration."""
139
- with suppress(ParseNumberError):
140
- return parse_number(duration)
141
- try:
142
- return parse_timedelta(duration)
143
- except ParseTimedeltaError:
144
- raise ParseDurationError(duration=duration) from None
145
-
146
-
147
- @dataclass(kw_only=True, slots=True)
148
- class ParseDurationError(Exception):
149
- duration: str
150
-
151
- @override
152
- def __str__(self) -> str:
153
- return f"Unable to parse duration; got {self.duration!r}"
154
-
155
-
156
- ##
157
-
158
-
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
- def parse_timedelta(timedelta: str, /) -> dt.timedelta:
202
- """Parse a string into a timedelta."""
203
- with suppress(ExtractGroupError):
204
- rest = extract_group(r"^-([\w\.]+)$", timedelta)
205
- return -parse_timedelta(rest)
206
- try:
207
- days_str, time_str = extract_groups(r"^P(?:(\d+)D)?(?:T([\w\.]*))?$", timedelta)
208
- except ExtractGroupsError:
209
- raise _ParseTimedeltaParseError(timedelta=timedelta) from None
210
- days = ZERO_TIME if days_str == "" else dt.timedelta(days=int(days_str))
211
- if time_str == "":
212
- time = ZERO_TIME
213
- else:
214
- time_part = DateTimeDelta.parse_common_iso(f"PT{time_str}").time_part()
215
- _, nanoseconds = divmod(time_part.in_nanoseconds(), 1000)
216
- if nanoseconds != 0:
217
- raise _ParseTimedeltaNanosecondError(
218
- timedelta=timedelta, nanoseconds=nanoseconds
219
- )
220
- time = dt.timedelta(microseconds=int(time_part.in_microseconds()))
221
- return days + time
222
-
223
-
224
- @dataclass(kw_only=True, slots=True)
225
- class ParseTimedeltaError(Exception):
226
- timedelta: str
227
-
228
-
229
- @dataclass(kw_only=True, slots=True)
230
- class _ParseTimedeltaParseError(ParseTimedeltaError):
231
- @override
232
- def __str__(self) -> str:
233
- return f"Unable to parse timedelta; got {self.timedelta!r}"
234
-
235
-
236
- @dataclass(kw_only=True, slots=True)
237
- class _ParseTimedeltaNanosecondError(ParseTimedeltaError):
238
- nanoseconds: int
239
-
240
- @override
241
- def __str__(self) -> str:
242
- return f"Unable to parse timedelta; got {self.nanoseconds} nanoseconds"
243
-
244
-
245
- ##
246
-
247
-
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
- def serialize_duration(duration: Duration, /) -> str:
290
- """Serialize a duration."""
291
- if isinstance(duration, int | float):
292
- return str(duration)
293
- try:
294
- return serialize_timedelta(duration)
295
- except SerializeTimeDeltaError as error:
296
- raise SerializeDurationError(duration=error.timedelta) from None
297
-
298
-
299
- @dataclass(kw_only=True, slots=True)
300
- class SerializeDurationError(Exception):
301
- duration: Duration
302
-
303
- @override
304
- def __str__(self) -> str:
305
- return f"Unable to serialize duration; got {self.duration}"
306
-
307
-
308
- ##
309
-
310
-
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
- def serialize_timedelta(timedelta: dt.timedelta, /) -> str:
341
- """Serialize a timedelta."""
342
- try:
343
- dtd = _to_datetime_delta(timedelta)
344
- except _ToDateTimeDeltaError as error:
345
- raise SerializeTimeDeltaError(timedelta=error.timedelta) from None
346
- return dtd.format_common_iso()
347
-
348
-
349
- @dataclass(kw_only=True, slots=True)
350
- class SerializeTimeDeltaError(Exception):
351
- timedelta: dt.timedelta
352
-
353
- @override
354
- def __str__(self) -> str:
355
- return f"Unable to serialize timedelta; got {self.timedelta}"
356
-
357
-
358
- ##
359
-
360
-
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
- def _to_datetime_delta(timedelta: dt.timedelta, /) -> DateTimeDelta:
387
- """Serialize a timedelta."""
388
- total_microseconds = datetime_duration_to_microseconds(timedelta)
389
- if total_microseconds == 0:
390
- return DateTimeDelta()
391
- if total_microseconds >= 1:
392
- days, remainder = divmod(total_microseconds, _MICROSECONDS_PER_DAY)
393
- seconds, microseconds = divmod(remainder, _MICROSECONDS_PER_SECOND)
394
- try:
395
- dtd = DateTimeDelta(days=days, seconds=seconds, microseconds=microseconds)
396
- except (OverflowError, ValueError):
397
- raise _ToDateTimeDeltaError(timedelta=timedelta) from None
398
- months, days, seconds, nanoseconds = dtd.in_months_days_secs_nanos()
399
- return DateTimeDelta(
400
- months=months, days=days, seconds=seconds, nanoseconds=nanoseconds
401
- )
402
- return -_to_datetime_delta(-timedelta)
403
-
404
-
405
- @dataclass(kw_only=True, slots=True)
406
- class _ToDateTimeDeltaError(Exception):
407
- timedelta: dt.timedelta
408
-
409
- @override
410
- def __str__(self) -> str:
411
- return f"Unable to create DateTimeDelta; got {self.timedelta}"
412
-
413
-
414
- __all__ = [
415
- "MAX_SERIALIZABLE_TIMEDELTA",
416
- "MIN_SERIALIZABLE_TIMEDELTA",
417
- "CheckValidZonedDateTimeError",
418
- "ParseDateError",
419
- "ParseDateTimeError",
420
- "ParseDurationError",
421
- "ParsePlainDateTimeError",
422
- "ParseTimeError",
423
- "ParseTimedeltaError",
424
- "ParseZonedDateTimeError",
425
- "SerializeDurationError",
426
- "SerializePlainDateTimeError",
427
- "SerializeTimeDeltaError",
428
- "SerializeZonedDateTimeError",
429
- "check_valid_zoned_datetime",
430
- "parse_date",
431
- "parse_datetime",
432
- "parse_duration",
433
- "parse_plain_datetime",
434
- "parse_time",
435
- "parse_timedelta",
436
- "parse_zoned_datetime",
437
- "serialize_date",
438
- "serialize_datetime",
439
- "serialize_duration",
440
- "serialize_plain_datetime",
441
- "serialize_time",
442
- "serialize_timedelta",
443
- "serialize_zoned_datetime",
444
- ]