dycw-utilities 0.131.18__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,230 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import datetime as dt
4
- from contextlib import suppress
5
- from dataclasses import dataclass
6
- from typing import TYPE_CHECKING, override
7
-
8
- from whenever import DateTimeDelta, TimeZoneNotFoundError, ZonedDateTime
9
-
10
- from utilities.datetime import (
11
- _MICROSECONDS_PER_DAY,
12
- _MICROSECONDS_PER_SECOND,
13
- ZERO_TIME,
14
- datetime_duration_to_microseconds,
15
- )
16
- from utilities.math import ParseNumberError, parse_number
17
- from utilities.re import (
18
- ExtractGroupError,
19
- ExtractGroupsError,
20
- extract_group,
21
- extract_groups,
22
- )
23
- from utilities.zoneinfo import UTC, ensure_time_zone, get_time_zone_name
24
-
25
- if TYPE_CHECKING:
26
- from utilities.types import Duration
27
-
28
- MAX_SERIALIZABLE_TIMEDELTA = dt.timedelta(days=3652060, microseconds=-1)
29
- MIN_SERIALIZABLE_TIMEDELTA = -MAX_SERIALIZABLE_TIMEDELTA
30
-
31
-
32
- ##
33
-
34
-
35
- def check_valid_zoned_datetime(datetime: dt.datetime, /) -> None:
36
- """Check if a zoned datetime is valid."""
37
- time_zone = ensure_time_zone(datetime) # skipif-ci-and-windows
38
- datetime2 = datetime.replace(tzinfo=time_zone) # skipif-ci-and-windows
39
- try: # skipif-ci-and-windows
40
- result = (
41
- ZonedDateTime.from_py_datetime(datetime2)
42
- .to_tz(get_time_zone_name(UTC))
43
- .to_tz(get_time_zone_name(time_zone))
44
- .py_datetime()
45
- )
46
- except TimeZoneNotFoundError: # pragma: no cover
47
- raise _CheckValidZonedDateTimeInvalidTimeZoneError(datetime=datetime) from None
48
- if result != datetime2: # skipif-ci-and-windows
49
- raise _CheckValidZonedDateTimeUnequalError(datetime=datetime, result=result)
50
-
51
-
52
- @dataclass(kw_only=True, slots=True)
53
- class CheckValidZonedDateTimeError(Exception):
54
- datetime: dt.datetime
55
-
56
-
57
- @dataclass(kw_only=True, slots=True)
58
- class _CheckValidZonedDateTimeInvalidTimeZoneError(CheckValidZonedDateTimeError):
59
- @override
60
- def __str__(self) -> str:
61
- return f"Invalid timezone; got {self.datetime.tzinfo}" # pragma: no cover
62
-
63
-
64
- @dataclass(kw_only=True, slots=True)
65
- class _CheckValidZonedDateTimeUnequalError(CheckValidZonedDateTimeError):
66
- result: dt.datetime
67
-
68
- @override
69
- def __str__(self) -> str:
70
- return f"Zoned datetime must be valid; got {self.datetime} != {self.result}" # skipif-ci-and-windows
71
-
72
-
73
- ##
74
-
75
-
76
- def parse_duration(duration: str, /) -> Duration:
77
- """Parse a string into a Duration."""
78
- with suppress(ParseNumberError):
79
- return parse_number(duration)
80
- try:
81
- return parse_timedelta(duration)
82
- except ParseTimedeltaError:
83
- raise ParseDurationError(duration=duration) from None
84
-
85
-
86
- @dataclass(kw_only=True, slots=True)
87
- class ParseDurationError(Exception):
88
- duration: str
89
-
90
- @override
91
- def __str__(self) -> str:
92
- return f"Unable to parse duration; got {self.duration!r}"
93
-
94
-
95
- ##
96
-
97
-
98
- def parse_timedelta(timedelta: str, /) -> dt.timedelta:
99
- """Parse a string into a timedelta."""
100
- with suppress(ExtractGroupError):
101
- rest = extract_group(r"^-([\w\.]+)$", timedelta)
102
- return -parse_timedelta(rest)
103
- try:
104
- days_str, time_str = extract_groups(r"^P(?:(\d+)D)?(?:T([\w\.]*))?$", timedelta)
105
- except ExtractGroupsError:
106
- raise _ParseTimedeltaParseError(timedelta=timedelta) from None
107
- days = ZERO_TIME if days_str == "" else dt.timedelta(days=int(days_str))
108
- if time_str == "":
109
- time = ZERO_TIME
110
- else:
111
- time_part = DateTimeDelta.parse_common_iso(f"PT{time_str}").time_part()
112
- _, nanoseconds = divmod(time_part.in_nanoseconds(), 1000)
113
- if nanoseconds != 0:
114
- raise _ParseTimedeltaNanosecondError(
115
- timedelta=timedelta, nanoseconds=nanoseconds
116
- )
117
- time = dt.timedelta(microseconds=int(time_part.in_microseconds()))
118
- return days + time
119
-
120
-
121
- @dataclass(kw_only=True, slots=True)
122
- class ParseTimedeltaError(Exception):
123
- timedelta: str
124
-
125
-
126
- @dataclass(kw_only=True, slots=True)
127
- class _ParseTimedeltaParseError(ParseTimedeltaError):
128
- @override
129
- def __str__(self) -> str:
130
- return f"Unable to parse timedelta; got {self.timedelta!r}"
131
-
132
-
133
- @dataclass(kw_only=True, slots=True)
134
- class _ParseTimedeltaNanosecondError(ParseTimedeltaError):
135
- nanoseconds: int
136
-
137
- @override
138
- def __str__(self) -> str:
139
- return f"Unable to parse timedelta; got {self.nanoseconds} nanoseconds"
140
-
141
-
142
- ##
143
-
144
-
145
- def serialize_duration(duration: Duration, /) -> str:
146
- """Serialize a duration."""
147
- if isinstance(duration, int | float):
148
- return str(duration)
149
- try:
150
- return serialize_timedelta(duration)
151
- except SerializeTimeDeltaError as error:
152
- raise SerializeDurationError(duration=error.timedelta) from None
153
-
154
-
155
- @dataclass(kw_only=True, slots=True)
156
- class SerializeDurationError(Exception):
157
- duration: Duration
158
-
159
- @override
160
- def __str__(self) -> str:
161
- return f"Unable to serialize duration; got {self.duration}"
162
-
163
-
164
- ##
165
-
166
-
167
- def serialize_timedelta(timedelta: dt.timedelta, /) -> str:
168
- """Serialize a timedelta."""
169
- try:
170
- dtd = _to_datetime_delta(timedelta)
171
- except _ToDateTimeDeltaError as error:
172
- raise SerializeTimeDeltaError(timedelta=error.timedelta) from None
173
- return dtd.format_common_iso()
174
-
175
-
176
- @dataclass(kw_only=True, slots=True)
177
- class SerializeTimeDeltaError(Exception):
178
- timedelta: dt.timedelta
179
-
180
- @override
181
- def __str__(self) -> str:
182
- return f"Unable to serialize timedelta; got {self.timedelta}"
183
-
184
-
185
- ##
186
-
187
-
188
- def _to_datetime_delta(timedelta: dt.timedelta, /) -> DateTimeDelta:
189
- """Serialize a timedelta."""
190
- total_microseconds = datetime_duration_to_microseconds(timedelta)
191
- if total_microseconds == 0:
192
- return DateTimeDelta()
193
- if total_microseconds >= 1:
194
- days, remainder = divmod(total_microseconds, _MICROSECONDS_PER_DAY)
195
- seconds, microseconds = divmod(remainder, _MICROSECONDS_PER_SECOND)
196
- try:
197
- dtd = DateTimeDelta(days=days, seconds=seconds, microseconds=microseconds)
198
- except (OverflowError, ValueError):
199
- raise _ToDateTimeDeltaError(timedelta=timedelta) from None
200
- months, days, seconds, nanoseconds = dtd.in_months_days_secs_nanos()
201
- return DateTimeDelta(
202
- months=months, days=days, seconds=seconds, nanoseconds=nanoseconds
203
- )
204
- return -_to_datetime_delta(-timedelta)
205
-
206
-
207
- @dataclass(kw_only=True, slots=True)
208
- class _ToDateTimeDeltaError(Exception):
209
- timedelta: dt.timedelta
210
-
211
- @override
212
- def __str__(self) -> str:
213
- return f"Unable to create DateTimeDelta; got {self.timedelta}"
214
-
215
-
216
- __all__ = [
217
- "MAX_SERIALIZABLE_TIMEDELTA",
218
- "MIN_SERIALIZABLE_TIMEDELTA",
219
- "CheckValidZonedDateTimeError",
220
- "ParseDurationError",
221
- "ParseTimedeltaError",
222
- "SerializeDurationError",
223
- "SerializeTimeDeltaError",
224
- "check_valid_zoned_datetime",
225
- "check_valid_zoned_datetime",
226
- "parse_duration",
227
- "parse_timedelta",
228
- "serialize_duration",
229
- "serialize_timedelta",
230
- ]