none-shall-parse 0.4.5__py3-none-any.whl → 0.6.3__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.
- none_shall_parse/__init__.py +66 -27
- none_shall_parse/dates.py +140 -73
- none_shall_parse/imeis.py +0 -1
- none_shall_parse/lists.py +6 -7
- {none_shall_parse-0.4.5.dist-info → none_shall_parse-0.6.3.dist-info}/METADATA +1 -1
- none_shall_parse-0.6.3.dist-info/RECORD +10 -0
- {none_shall_parse-0.4.5.dist-info → none_shall_parse-0.6.3.dist-info}/WHEEL +1 -1
- none_shall_parse-0.4.5.dist-info/RECORD +0 -10
none_shall_parse/__init__.py
CHANGED
|
@@ -10,28 +10,48 @@ translates to "none". Combined this with our parsing intentions
|
|
|
10
10
|
to create a name which nods to the Black Knight in Monty Python's Holy Grail.
|
|
11
11
|
https://www.youtube.com/watch?v=zKhEw7nD9C4
|
|
12
12
|
"""
|
|
13
|
+
|
|
13
14
|
from __future__ import annotations
|
|
14
15
|
from .dates import (
|
|
15
|
-
DateUtilsError,
|
|
16
|
+
DateUtilsError,
|
|
17
|
+
assert_week_start_date_is_valid,
|
|
16
18
|
assert_month_start_date_is_valid,
|
|
17
|
-
get_datetime_now,
|
|
19
|
+
get_datetime_now,
|
|
20
|
+
za_now,
|
|
21
|
+
utc_now,
|
|
18
22
|
za_ordinal_year_day_now,
|
|
19
|
-
za_ordinal_year_day_tomorrow,
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
za_ordinal_year_day_tomorrow,
|
|
24
|
+
utc_epoch_start,
|
|
25
|
+
now_offset_n_minutes,
|
|
26
|
+
now_offset_n_hours,
|
|
27
|
+
now_offset_n_days,
|
|
28
|
+
now_offset_n_months,
|
|
29
|
+
get_datetime_tomorrow,
|
|
22
30
|
get_datetime_yesterday,
|
|
23
31
|
get_utc_datetime_offset_n_days,
|
|
24
|
-
epoch_to_datetime,
|
|
32
|
+
epoch_to_datetime,
|
|
33
|
+
epoch_to_utc_datetime,
|
|
25
34
|
is_office_hours_in_timezone,
|
|
26
35
|
get_datetime_from_ordinal_and_sentinel,
|
|
27
|
-
day_span,
|
|
28
|
-
|
|
36
|
+
day_span,
|
|
37
|
+
week_span,
|
|
38
|
+
month_span,
|
|
39
|
+
arb_span,
|
|
40
|
+
calendar_month_start_end,
|
|
41
|
+
unix_timestamp,
|
|
29
42
|
sentinel_date_and_ordinal_to_date,
|
|
30
|
-
seconds_to_end_of_month,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
43
|
+
seconds_to_end_of_month,
|
|
44
|
+
standard_tz_timestring,
|
|
45
|
+
get_notice_end_date,
|
|
46
|
+
dt_to_za_time_string,
|
|
47
|
+
months_ago_selection,
|
|
48
|
+
is_aware,
|
|
49
|
+
make_aware,
|
|
50
|
+
unaware_to_utc_aware,
|
|
51
|
+
timer_decorator,
|
|
52
|
+
ZA_TZ,
|
|
53
|
+
UTC_TZ,
|
|
54
|
+
keys_for_span_func,
|
|
35
55
|
)
|
|
36
56
|
from .imeis import (
|
|
37
57
|
get_luhn_digit,
|
|
@@ -83,26 +103,45 @@ __email__ = "andries.niemandt@trintel.co.za, jan@trintel.co.za"
|
|
|
83
103
|
__license__ = "MIT"
|
|
84
104
|
|
|
85
105
|
__all__ = (
|
|
86
|
-
"DateUtilsError",
|
|
106
|
+
"DateUtilsError",
|
|
107
|
+
"assert_week_start_date_is_valid",
|
|
87
108
|
"assert_month_start_date_is_valid",
|
|
88
|
-
"get_datetime_now",
|
|
109
|
+
"get_datetime_now",
|
|
110
|
+
"za_now",
|
|
111
|
+
"utc_now",
|
|
89
112
|
"za_ordinal_year_day_now",
|
|
90
|
-
"za_ordinal_year_day_tomorrow",
|
|
91
|
-
"
|
|
92
|
-
"
|
|
113
|
+
"za_ordinal_year_day_tomorrow",
|
|
114
|
+
"utc_epoch_start",
|
|
115
|
+
"now_offset_n_minutes",
|
|
116
|
+
"now_offset_n_hours",
|
|
117
|
+
"now_offset_n_days",
|
|
118
|
+
"now_offset_n_months",
|
|
119
|
+
"get_datetime_tomorrow",
|
|
93
120
|
"get_datetime_yesterday",
|
|
94
121
|
"get_utc_datetime_offset_n_days",
|
|
95
|
-
"epoch_to_datetime",
|
|
122
|
+
"epoch_to_datetime",
|
|
123
|
+
"epoch_to_utc_datetime",
|
|
96
124
|
"is_office_hours_in_timezone",
|
|
97
125
|
"get_datetime_from_ordinal_and_sentinel",
|
|
98
|
-
"day_span",
|
|
99
|
-
"
|
|
126
|
+
"day_span",
|
|
127
|
+
"week_span",
|
|
128
|
+
"month_span",
|
|
129
|
+
"arb_span",
|
|
130
|
+
"calendar_month_start_end",
|
|
131
|
+
"unix_timestamp",
|
|
100
132
|
"sentinel_date_and_ordinal_to_date",
|
|
101
|
-
"seconds_to_end_of_month",
|
|
102
|
-
"
|
|
103
|
-
"
|
|
104
|
-
"
|
|
105
|
-
"
|
|
133
|
+
"seconds_to_end_of_month",
|
|
134
|
+
"standard_tz_timestring",
|
|
135
|
+
"get_notice_end_date",
|
|
136
|
+
"dt_to_za_time_string",
|
|
137
|
+
"months_ago_selection",
|
|
138
|
+
"is_aware",
|
|
139
|
+
"make_aware",
|
|
140
|
+
"unaware_to_utc_aware",
|
|
141
|
+
"timer_decorator",
|
|
142
|
+
"ZA_TZ",
|
|
143
|
+
"UTC_TZ",
|
|
144
|
+
"keys_for_span_func",
|
|
106
145
|
"get_luhn_digit",
|
|
107
146
|
"is_valid_luhn",
|
|
108
147
|
"is_valid_imei",
|
|
@@ -136,4 +175,4 @@ __all__ = (
|
|
|
136
175
|
"DateTimeLike",
|
|
137
176
|
"DateLike",
|
|
138
177
|
"DateTimeOrDateLike",
|
|
139
|
-
)
|
|
178
|
+
)
|
none_shall_parse/dates.py
CHANGED
|
@@ -3,15 +3,15 @@ import logging
|
|
|
3
3
|
import time
|
|
4
4
|
from datetime import date, datetime
|
|
5
5
|
from typing import Callable, Any, Tuple, Sequence, List
|
|
6
|
-
from .types import DateTimeLike
|
|
6
|
+
from .types import DateTimeLike, DateTimeOrDateLike
|
|
7
7
|
|
|
8
8
|
import pendulum
|
|
9
9
|
from pendulum import DateTime, Date
|
|
10
10
|
from pendulum import local_timezone
|
|
11
11
|
from pendulum.tz.exceptions import InvalidTimezone
|
|
12
12
|
|
|
13
|
-
ZA_TZ =
|
|
14
|
-
UTC_TZ =
|
|
13
|
+
ZA_TZ = "Africa/Johannesburg"
|
|
14
|
+
UTC_TZ = "UTC"
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class DateUtilsError(Exception):
|
|
@@ -48,13 +48,17 @@ def get_datetime_now(naive: bool = False, tz: str | None = None) -> DateTime:
|
|
|
48
48
|
naive (bool): If True, returns a naive DateTime object without timezone information.
|
|
49
49
|
Defaults to True.
|
|
50
50
|
tz (Optional[str]): The timezone to use if a timezone-aware DateTime is requested.
|
|
51
|
-
If not provided and naive is False,
|
|
52
|
-
is used.
|
|
51
|
+
If not provided and naive is False, defaults to ZA_TZ.
|
|
53
52
|
|
|
54
53
|
Returns:
|
|
55
54
|
pendulum.DateTime: The current date and time based on the specified parameters.
|
|
56
55
|
"""
|
|
57
|
-
|
|
56
|
+
if naive:
|
|
57
|
+
return pendulum.now().naive()
|
|
58
|
+
else:
|
|
59
|
+
# Default to ZA_TZ if no timezone is specified for aware datetime
|
|
60
|
+
timezone = tz if tz is not None else ZA_TZ
|
|
61
|
+
return pendulum.now(tz=timezone)
|
|
58
62
|
|
|
59
63
|
|
|
60
64
|
def za_now() -> DateTime:
|
|
@@ -71,6 +75,15 @@ def za_now() -> DateTime:
|
|
|
71
75
|
return get_datetime_now(naive=False, tz=ZA_TZ)
|
|
72
76
|
|
|
73
77
|
|
|
78
|
+
def utc_now() -> DateTime:
|
|
79
|
+
"""
|
|
80
|
+
Shorthand for the current date and time in the UTC timezone.
|
|
81
|
+
Returns:
|
|
82
|
+
pendulum.DateTime: The current date and time in the UTC timezone.
|
|
83
|
+
"""
|
|
84
|
+
return get_datetime_now(naive=False, tz=UTC_TZ)
|
|
85
|
+
|
|
86
|
+
|
|
74
87
|
def za_ordinal_year_day_now() -> int:
|
|
75
88
|
"""
|
|
76
89
|
Returns the current ordinal day of the year for the ZA_TZ timezone.
|
|
@@ -111,8 +124,9 @@ def utc_epoch_start() -> DateTime:
|
|
|
111
124
|
return pendulum.from_timestamp(0)
|
|
112
125
|
|
|
113
126
|
|
|
114
|
-
def _now_offset_n_units(
|
|
115
|
-
|
|
127
|
+
def _now_offset_n_units(
|
|
128
|
+
n: int, units: str, naive: bool = False, tz: str | None = None
|
|
129
|
+
) -> DateTime:
|
|
116
130
|
"""
|
|
117
131
|
Calculate a DateTime object offset by a specified number of time units.
|
|
118
132
|
|
|
@@ -132,33 +146,37 @@ def _now_offset_n_units(n: int, units: str, naive: bool = False,
|
|
|
132
146
|
DateTime: The calculated DateTime object, optionally timezone-aware or naive.
|
|
133
147
|
"""
|
|
134
148
|
kwargs = {units: n}
|
|
135
|
-
return
|
|
136
|
-
**kwargs)
|
|
149
|
+
return (
|
|
150
|
+
pendulum.now().add(**kwargs).naive()
|
|
151
|
+
if naive
|
|
152
|
+
else pendulum.now(tz).add(**kwargs)
|
|
153
|
+
)
|
|
137
154
|
|
|
138
155
|
|
|
139
|
-
def now_offset_n_minutes(
|
|
140
|
-
|
|
156
|
+
def now_offset_n_minutes(
|
|
157
|
+
n: int, naive: bool = False, tz: str | None = None
|
|
158
|
+
) -> DateTime:
|
|
141
159
|
return _now_offset_n_units(n, units="minutes", naive=naive, tz=tz)
|
|
142
160
|
|
|
143
161
|
|
|
144
|
-
def now_offset_n_hours(n: int, naive: bool = False,
|
|
145
|
-
tz: str | None = None) -> DateTime:
|
|
162
|
+
def now_offset_n_hours(n: int, naive: bool = False, tz: str | None = None) -> DateTime:
|
|
146
163
|
return _now_offset_n_units(n, units="hours", naive=naive, tz=tz)
|
|
147
164
|
|
|
148
165
|
|
|
149
|
-
def now_offset_n_days(n: int, naive: bool = False,
|
|
150
|
-
tz: str | None = None) -> DateTime:
|
|
166
|
+
def now_offset_n_days(n: int, naive: bool = False, tz: str | None = None) -> DateTime:
|
|
151
167
|
return _now_offset_n_units(n, units="days", naive=naive, tz=tz)
|
|
152
168
|
|
|
153
169
|
|
|
154
|
-
def
|
|
155
|
-
|
|
170
|
+
def now_offset_n_months(n: int, naive: bool = False, tz: str | None = None) -> DateTime:
|
|
171
|
+
return _now_offset_n_units(n, units="months", naive=naive, tz=tz)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def get_datetime_tomorrow(naive: bool = False, tz: str | None = None) -> DateTime:
|
|
156
175
|
"""Get tomorrow's DateTime"""
|
|
157
176
|
return now_offset_n_days(1, naive=naive, tz=tz)
|
|
158
177
|
|
|
159
178
|
|
|
160
|
-
def get_datetime_yesterday(naive: bool = False,
|
|
161
|
-
tz: str | None = None) -> DateTime:
|
|
179
|
+
def get_datetime_yesterday(naive: bool = False, tz: str | None = None) -> DateTime:
|
|
162
180
|
"""Get yesterday's DateTime"""
|
|
163
181
|
return now_offset_n_days(-1, naive=naive, tz=tz)
|
|
164
182
|
|
|
@@ -168,8 +186,9 @@ def get_utc_datetime_offset_n_days(n: int = 0) -> DateTime:
|
|
|
168
186
|
return pendulum.now(UTC_TZ).add(days=n)
|
|
169
187
|
|
|
170
188
|
|
|
171
|
-
def epoch_to_datetime(
|
|
172
|
-
|
|
189
|
+
def epoch_to_datetime(
|
|
190
|
+
epoch_int: int | float, naive: bool = False, tz: str | None = None
|
|
191
|
+
) -> DateTime:
|
|
173
192
|
"""
|
|
174
193
|
Converts an epoch timestamp to a DateTime object.
|
|
175
194
|
|
|
@@ -218,7 +237,9 @@ def epoch_to_utc_datetime(epoch_int: int | float | str) -> DateTime:
|
|
|
218
237
|
return pendulum.from_timestamp(int(epoch_int), tz=UTC_TZ)
|
|
219
238
|
|
|
220
239
|
|
|
221
|
-
def is_office_hours_in_timezone(
|
|
240
|
+
def is_office_hours_in_timezone(
|
|
241
|
+
epoch_int: int | float | str, tz: str | None = None
|
|
242
|
+
) -> bool:
|
|
222
243
|
"""
|
|
223
244
|
Determines if a given epoch timestamp falls within office hours for a
|
|
224
245
|
specific timezone.
|
|
@@ -250,7 +271,8 @@ def is_office_hours_in_timezone(epoch_int: int | float | str, tz: str | None = N
|
|
|
250
271
|
|
|
251
272
|
|
|
252
273
|
def get_datetime_from_ordinal_and_sentinel(
|
|
253
|
-
|
|
274
|
+
sentinel: DateTimeLike | None = None,
|
|
275
|
+
) -> Callable[[int], DateTime]:
|
|
254
276
|
"""
|
|
255
277
|
Given an ordinal year day, and a sentinel datetime, get the closest past
|
|
256
278
|
datetime to the sentinel that had the given ordinal year day.
|
|
@@ -311,8 +333,8 @@ def day_span(pts: DateTimeLike) -> Tuple[DateTime, DateTime]:
|
|
|
311
333
|
pdt = pendulum.instance(pts)
|
|
312
334
|
|
|
313
335
|
# Use Pendulum's clean API
|
|
314
|
-
begin = pdt.start_of(
|
|
315
|
-
end = pdt.add(days=1).start_of(
|
|
336
|
+
begin = pdt.start_of("day")
|
|
337
|
+
end = pdt.add(days=1).start_of("day")
|
|
316
338
|
|
|
317
339
|
if naive:
|
|
318
340
|
return begin.naive(), end.naive()
|
|
@@ -349,7 +371,7 @@ def week_span(wso: int) -> Callable[[DateTimeLike], Tuple[DateTime, DateTime]]:
|
|
|
349
371
|
current_weekday = pdt.weekday() + 1 # Pendulum uses 0-6, we want 1-7
|
|
350
372
|
days_back = (current_weekday - wso) % 7
|
|
351
373
|
|
|
352
|
-
begin = pdt.start_of(
|
|
374
|
+
begin = pdt.start_of("day").subtract(days=days_back)
|
|
353
375
|
end = begin.add(days=7)
|
|
354
376
|
|
|
355
377
|
if naive:
|
|
@@ -388,10 +410,10 @@ def month_span(mso: int) -> Callable[[DateTimeLike], Tuple[DateTime, DateTime]]:
|
|
|
388
410
|
|
|
389
411
|
if current_day >= mso:
|
|
390
412
|
# We're in the current month period
|
|
391
|
-
begin = pdt.start_of(
|
|
413
|
+
begin = pdt.start_of("day").replace(day=mso)
|
|
392
414
|
else:
|
|
393
415
|
# We're in the previous month period
|
|
394
|
-
begin = pdt.start_of(
|
|
416
|
+
begin = pdt.start_of("day").subtract(months=1).replace(day=mso)
|
|
395
417
|
|
|
396
418
|
# End is mso of next month from `begin`
|
|
397
419
|
end = begin.add(months=1)
|
|
@@ -404,8 +426,9 @@ def month_span(mso: int) -> Callable[[DateTimeLike], Tuple[DateTime, DateTime]]:
|
|
|
404
426
|
return find_dates
|
|
405
427
|
|
|
406
428
|
|
|
407
|
-
def arb_span(
|
|
408
|
-
[
|
|
429
|
+
def arb_span(
|
|
430
|
+
dates: Sequence[str | DateTimeOrDateLike], naive: bool = False
|
|
431
|
+
) -> Callable[[Any], Tuple[DateTime, DateTime]]:
|
|
409
432
|
"""
|
|
410
433
|
Parses two given dates and returns a callable function that provides the date range
|
|
411
434
|
as a tuple of datetime objects. The function ensures the date range is valid and
|
|
@@ -434,22 +457,33 @@ def arb_span(dates: Sequence[str | DateTimeLike], naive: bool = False) -> Callab
|
|
|
434
457
|
parsed = pendulum.parse(date)
|
|
435
458
|
|
|
436
459
|
# If it's a date-only string (no time/timezone info), treat as naive
|
|
437
|
-
if
|
|
460
|
+
if "T" not in date and " " not in date:
|
|
438
461
|
parsed = parsed.naive()
|
|
439
462
|
|
|
440
|
-
parsed_dates.append(parsed.start_of(
|
|
463
|
+
parsed_dates.append(parsed.start_of("day"))
|
|
441
464
|
else:
|
|
442
|
-
# It's already a datetime
|
|
443
|
-
if date
|
|
444
|
-
#
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
465
|
+
# It's already a datetime-like object
|
|
466
|
+
if isinstance(date, datetime):
|
|
467
|
+
# datetime objects have tzinfo, hour, minute, etc.
|
|
468
|
+
if date.tzinfo is None:
|
|
469
|
+
# Input is naive, keep it naive using pendulum.naive()
|
|
470
|
+
parsed = pendulum.naive(
|
|
471
|
+
date.year,
|
|
472
|
+
date.month,
|
|
473
|
+
date.day,
|
|
474
|
+
date.hour,
|
|
475
|
+
date.minute,
|
|
476
|
+
date.second,
|
|
477
|
+
date.microsecond,
|
|
478
|
+
)
|
|
479
|
+
else:
|
|
480
|
+
# Input is timezone-aware, preserve it
|
|
481
|
+
parsed = pendulum.instance(date)
|
|
448
482
|
else:
|
|
449
|
-
#
|
|
450
|
-
parsed = pendulum.
|
|
483
|
+
# date objects (no time component, no tzinfo) - treat as naive
|
|
484
|
+
parsed = pendulum.naive(date.year, date.month, date.day)
|
|
451
485
|
|
|
452
|
-
parsed_dates.append(parsed.start_of(
|
|
486
|
+
parsed_dates.append(parsed.start_of("day"))
|
|
453
487
|
|
|
454
488
|
a, b = parsed_dates
|
|
455
489
|
|
|
@@ -479,8 +513,8 @@ def arb_span(dates: Sequence[str | DateTimeLike], naive: bool = False) -> Callab
|
|
|
479
513
|
|
|
480
514
|
|
|
481
515
|
def unroll_span_func(
|
|
482
|
-
|
|
483
|
-
|
|
516
|
+
f: Callable[[DateTimeLike], Tuple[DateTime, DateTime]],
|
|
517
|
+
cover: DateTimeLike | None = None,
|
|
484
518
|
) -> Tuple[List[DateTime], List[int], List[str], DateTime, DateTime]:
|
|
485
519
|
"""
|
|
486
520
|
Generate keys for a date range based on a provided function.
|
|
@@ -505,13 +539,21 @@ def unroll_span_func(
|
|
|
505
539
|
DateUtilsError: If the date range cannot be processed due to invalid dates or formatting.
|
|
506
540
|
"""
|
|
507
541
|
cover = pendulum.now() if cover is None else cover
|
|
508
|
-
|
|
542
|
+
|
|
509
543
|
try:
|
|
510
544
|
start, end = f(cover)
|
|
511
545
|
start = pendulum.instance(start)
|
|
512
|
-
start = start.naive() if naive else start
|
|
513
546
|
end = pendulum.instance(end)
|
|
514
|
-
|
|
547
|
+
|
|
548
|
+
# Determine naiveness from the dates returned by f, not from cover
|
|
549
|
+
naive = start.tzinfo is None
|
|
550
|
+
if naive:
|
|
551
|
+
start = start.naive()
|
|
552
|
+
end = end.naive()
|
|
553
|
+
|
|
554
|
+
# Get actual current time for filtering future dates (always "now", not cover)
|
|
555
|
+
current_date_sentinel = pendulum.now().naive() if naive else pendulum.now()
|
|
556
|
+
|
|
515
557
|
except Exception as e:
|
|
516
558
|
raise DateUtilsError(f"Function f failed to compute date range: {str(e)}")
|
|
517
559
|
|
|
@@ -524,9 +566,11 @@ def unroll_span_func(
|
|
|
524
566
|
ord_days = []
|
|
525
567
|
iso_date_strings = []
|
|
526
568
|
for dt in interval.range(unit="days"):
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
569
|
+
# Filter: only include dates up to today (not future dates)
|
|
570
|
+
if dt <= current_date_sentinel and dt < end:
|
|
571
|
+
date_range.append(dt)
|
|
572
|
+
ord_days.append(dt.day_of_year)
|
|
573
|
+
iso_date_strings.append(dt.format("YYYY-MM-DD"))
|
|
530
574
|
|
|
531
575
|
return date_range, ord_days, iso_date_strings, start, end
|
|
532
576
|
|
|
@@ -535,10 +579,10 @@ def unroll_span_func(
|
|
|
535
579
|
|
|
536
580
|
|
|
537
581
|
def keys_for_span_func(
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
582
|
+
f: Callable[[DateTimeLike], Tuple[DateTime, DateTime]],
|
|
583
|
+
cover: DateTimeLike | None = None,
|
|
584
|
+
key_in_format: str = "ODIN_{}",
|
|
585
|
+
key_out_format: str = "ODOUT_{}",
|
|
542
586
|
):
|
|
543
587
|
"""
|
|
544
588
|
Generate keys for a date range based on a provided function.
|
|
@@ -558,14 +602,17 @@ def keys_for_span_func(
|
|
|
558
602
|
Raises:
|
|
559
603
|
DateUtilsError: If the date range cannot be processed.
|
|
560
604
|
"""
|
|
561
|
-
date_range, ord_days, iso_date_strings, start, end = unroll_span_func(
|
|
605
|
+
date_range, ord_days, iso_date_strings, start, end = unroll_span_func(
|
|
606
|
+
f=f, cover=cover
|
|
607
|
+
)
|
|
562
608
|
keys_in = [key_in_format.format(d) for d in ord_days]
|
|
563
609
|
keys_out = [key_out_format.format(d) for d in ord_days]
|
|
564
610
|
return keys_in, keys_out, start, end
|
|
565
611
|
|
|
566
612
|
|
|
567
|
-
def calendar_month_start_end(
|
|
568
|
-
|
|
613
|
+
def calendar_month_start_end(
|
|
614
|
+
date_in_month: DateTimeLike | None = None,
|
|
615
|
+
) -> Tuple[DateTime, DateTime]:
|
|
569
616
|
naive = date_in_month.tzinfo is None
|
|
570
617
|
|
|
571
618
|
if date_in_month is None:
|
|
@@ -573,7 +620,7 @@ def calendar_month_start_end(date_in_month: DateTimeLike | None = None) -> Tuple
|
|
|
573
620
|
|
|
574
621
|
pdt = pendulum.instance(date_in_month)
|
|
575
622
|
|
|
576
|
-
start = pdt.start_of(
|
|
623
|
+
start = pdt.start_of("month")
|
|
577
624
|
end = start.add(months=1)
|
|
578
625
|
|
|
579
626
|
if naive:
|
|
@@ -592,8 +639,9 @@ def unix_timestamp() -> int:
|
|
|
592
639
|
return round(time.time())
|
|
593
640
|
|
|
594
641
|
|
|
595
|
-
def sentinel_date_and_ordinal_to_date(
|
|
596
|
-
|
|
642
|
+
def sentinel_date_and_ordinal_to_date(
|
|
643
|
+
sentinel_date: DateTimeLike | date, ordinal: int | float | str
|
|
644
|
+
) -> date:
|
|
597
645
|
"""Convert sentinel date and ordinal day to actual date"""
|
|
598
646
|
year = sentinel_date.year
|
|
599
647
|
int_ordinal = int(ordinal)
|
|
@@ -610,7 +658,7 @@ def sentinel_date_and_ordinal_to_date(sentinel_date: DateTimeLike | date,
|
|
|
610
658
|
def seconds_to_end_of_month() -> int:
|
|
611
659
|
"""Calculate seconds remaining until the end of the current month"""
|
|
612
660
|
now = pendulum.now(UTC_TZ)
|
|
613
|
-
end_of_month = now.end_of(
|
|
661
|
+
end_of_month = now.end_of("month")
|
|
614
662
|
return int((end_of_month - now).total_seconds())
|
|
615
663
|
|
|
616
664
|
|
|
@@ -639,24 +687,42 @@ def get_notice_end_date(given_date: DateTimeLike | date | Date | None = None) ->
|
|
|
639
687
|
given_date = given_date.date()
|
|
640
688
|
elif not isinstance(given_date, date):
|
|
641
689
|
raise ValueError(
|
|
642
|
-
"Given date must be a datetime.date or datetime.datetime object"
|
|
690
|
+
"Given date must be a datetime.date or datetime.datetime object"
|
|
691
|
+
)
|
|
643
692
|
|
|
644
693
|
pdt = pendulum.instance(given_date)
|
|
645
694
|
if given_date.day <= 15:
|
|
646
695
|
# End of current month
|
|
647
|
-
end_date = pdt.add(months=1).start_of(
|
|
696
|
+
end_date = pdt.add(months=1).start_of("month")
|
|
648
697
|
else:
|
|
649
698
|
# End of next month
|
|
650
|
-
end_date = pdt.add(months=2).start_of(
|
|
699
|
+
end_date = pdt.add(months=2).start_of("month")
|
|
651
700
|
|
|
652
701
|
if isinstance(end_date, DateTime):
|
|
653
702
|
return end_date.date()
|
|
654
703
|
return end_date
|
|
655
704
|
|
|
656
705
|
|
|
657
|
-
def dt_to_za_time_string(v:
|
|
658
|
-
"""Convert DateTime to South Africa time string
|
|
659
|
-
|
|
706
|
+
def dt_to_za_time_string(v: DateTimeOrDateLike | float | None) -> str | None:
|
|
707
|
+
"""Convert DateTime or Date to South Africa time string.
|
|
708
|
+
|
|
709
|
+
For datetime objects: returns "YYYY-MM-DD HH:MM:SS" in ZA timezone.
|
|
710
|
+
For date objects: returns "YYYY-MM-DD" (no time component).
|
|
711
|
+
For None: returns None.
|
|
712
|
+
For float values (NaN): returns None.
|
|
713
|
+
"""
|
|
714
|
+
if v is None:
|
|
715
|
+
return None
|
|
716
|
+
|
|
717
|
+
# Handle float values (NaN from pandas NULL database values)
|
|
718
|
+
if isinstance(v, float):
|
|
719
|
+
return None
|
|
720
|
+
|
|
721
|
+
# Handle date objects (no time component)
|
|
722
|
+
if isinstance(v, date) and not isinstance(v, datetime):
|
|
723
|
+
return v.strftime("%Y-%m-%d")
|
|
724
|
+
|
|
725
|
+
# Convert datetime to Pendulum
|
|
660
726
|
naive = v.tzinfo is None
|
|
661
727
|
if naive:
|
|
662
728
|
pdt = pendulum.instance(v, tz=ZA_TZ)
|
|
@@ -669,10 +735,7 @@ def months_ago_selection() -> List[Tuple[int, str]]:
|
|
|
669
735
|
"""Generate list of (index, "Month-Year") tuples for last 12 months"""
|
|
670
736
|
today = pendulum.today()
|
|
671
737
|
|
|
672
|
-
return [
|
|
673
|
-
(i, today.subtract(months=i).strftime("%B-%Y"))
|
|
674
|
-
for i in range(12)
|
|
675
|
-
]
|
|
738
|
+
return [(i, today.subtract(months=i).strftime("%B-%Y")) for i in range(12)]
|
|
676
739
|
|
|
677
740
|
|
|
678
741
|
def is_aware(dt: DateTimeLike | Date | date) -> bool:
|
|
@@ -680,7 +743,9 @@ def is_aware(dt: DateTimeLike | Date | date) -> bool:
|
|
|
680
743
|
return dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None
|
|
681
744
|
|
|
682
745
|
|
|
683
|
-
def make_aware(
|
|
746
|
+
def make_aware(
|
|
747
|
+
dt: DateTimeLike | date | Date | None, tz: str = None
|
|
748
|
+
) -> DateTime | Date | None:
|
|
684
749
|
"""
|
|
685
750
|
Convert a naive DateTime to a timezone-aware DateTime using Pendulum.
|
|
686
751
|
|
|
@@ -715,7 +780,9 @@ def make_aware(dt: DateTimeLike | date | Date | None, tz: str = None) -> DateTim
|
|
|
715
780
|
raise DateUtilsError(f"Invalid timezone: {tz}") from e
|
|
716
781
|
|
|
717
782
|
|
|
718
|
-
def unaware_to_utc_aware(
|
|
783
|
+
def unaware_to_utc_aware(
|
|
784
|
+
dt: DateTimeLike | date | Date | None,
|
|
785
|
+
) -> DateTime | Date | None:
|
|
719
786
|
"""Convert naive DateTime to UTC-aware DateTime using Pendulum."""
|
|
720
787
|
if not isinstance(dt, (datetime, type(None))):
|
|
721
788
|
raise TypeError(f"Expected datetime or None, got {type(dt)}")
|
none_shall_parse/imeis.py
CHANGED
none_shall_parse/lists.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import collections
|
|
2
1
|
from typing import Iterable, Generator, Any, List
|
|
3
2
|
|
|
4
3
|
from .types import T
|
|
@@ -29,20 +28,20 @@ def flatten(some_list: Iterable) -> Generator[Any, None, None]:
|
|
|
29
28
|
def safe_list_get(lst: List[T], idx: int, default: T = None) -> T:
|
|
30
29
|
"""
|
|
31
30
|
Retrieve an element from a list by its index or return a default value if the index
|
|
32
|
-
is out of range
|
|
33
|
-
|
|
31
|
+
is out of range or the input is not subscriptable. This function ensures safe retrieval
|
|
32
|
+
by providing a fallback value when access fails.
|
|
34
33
|
|
|
35
|
-
:param lst: The list from which the element is to be retrieved.
|
|
34
|
+
:param lst: The list from which the element is to be retrieved (may be None).
|
|
36
35
|
:type lst: List[T]
|
|
37
36
|
:param idx: The index of the element to retrieve from the list.
|
|
38
37
|
:type idx: int
|
|
39
|
-
:param default: The fallback value to be returned if
|
|
38
|
+
:param default: The fallback value to be returned if retrieval fails.
|
|
40
39
|
:type default: T
|
|
41
40
|
:return: The element at the specified index, or the default value
|
|
42
|
-
if the index is out of range.
|
|
41
|
+
if the index is out of range or lst is None/not subscriptable.
|
|
43
42
|
:rtype: T
|
|
44
43
|
"""
|
|
45
44
|
try:
|
|
46
45
|
return lst[idx]
|
|
47
|
-
except IndexError:
|
|
46
|
+
except (IndexError, TypeError):
|
|
48
47
|
return default
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: none-shall-parse
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.3
|
|
4
4
|
Summary: Trinity Shared Python utilities.
|
|
5
5
|
Author: Andries Niemandt, Jan Badenhorst
|
|
6
6
|
Author-email: Andries Niemandt <andries.niemandt@trintel.co.za>, Jan Badenhorst <jan@trintel.co.za>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
none_shall_parse/__init__.py,sha256=VQrtFMNFZGpZAWhbSnVuJIhGamkb2WUzXOzleUQL274,4051
|
|
2
|
+
none_shall_parse/dates.py,sha256=rT_1gE5QeS9YxOQJET0qWesEZ5u2qS5ATN0SnM6LPXY,28459
|
|
3
|
+
none_shall_parse/imeis.py,sha256=1fFRdFryOAg2djbZjtDmJb8aRdKZ-vhqbJUVy-AFaEA,6769
|
|
4
|
+
none_shall_parse/lists.py,sha256=AZ5UbPYcLJQkLTP5X6mD72c2gcJ6helgfzBAVRWft2U,1746
|
|
5
|
+
none_shall_parse/parse.py,sha256=77bXZAtwFksRwuZ9Ax0lPxEjFpyjkQBqRa5mBc1WkF4,6843
|
|
6
|
+
none_shall_parse/strings.py,sha256=F7491CJAHJjL7vdEGwoH_4S6PjaovYUS_yzVGJ-bYIE,8463
|
|
7
|
+
none_shall_parse/types.py,sha256=WAgILMtW2_fm9MBpUuQvq68yXYBNd3rnSoQk70ibOd4,1320
|
|
8
|
+
none_shall_parse-0.6.3.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
9
|
+
none_shall_parse-0.6.3.dist-info/METADATA,sha256=df4CPiPG9v1IURs8NVlUpqbIiWc0UPbW3R-lC97yDxY,1701
|
|
10
|
+
none_shall_parse-0.6.3.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
none_shall_parse/__init__.py,sha256=CTi08pRGuTem3KhOAKP_hO1oBfMzEHZymDhn4jIH0tU,3833
|
|
2
|
-
none_shall_parse/dates.py,sha256=DRgVVAFUOHj42nIiXuMYrwOtCEsf6qz1eU0y8eP6q9o,26661
|
|
3
|
-
none_shall_parse/imeis.py,sha256=20pONoUhLKomZxAJegqSSjG72hZjYs60r8IcaRt-15M,6770
|
|
4
|
-
none_shall_parse/lists.py,sha256=PKtYD8H1Z7MqbJQ5eFpsK_jgexLwiratMJiYeRMiOwc,1701
|
|
5
|
-
none_shall_parse/parse.py,sha256=77bXZAtwFksRwuZ9Ax0lPxEjFpyjkQBqRa5mBc1WkF4,6843
|
|
6
|
-
none_shall_parse/strings.py,sha256=F7491CJAHJjL7vdEGwoH_4S6PjaovYUS_yzVGJ-bYIE,8463
|
|
7
|
-
none_shall_parse/types.py,sha256=WAgILMtW2_fm9MBpUuQvq68yXYBNd3rnSoQk70ibOd4,1320
|
|
8
|
-
none_shall_parse-0.4.5.dist-info/WHEEL,sha256=4n27za1eEkOnA7dNjN6C5-O2rUiw6iapszm14Uj-Qmk,79
|
|
9
|
-
none_shall_parse-0.4.5.dist-info/METADATA,sha256=eTXaYu1c9TKU4CnZmqj6U3XWqQXnwNxsB7ejV05IFQA,1701
|
|
10
|
-
none_shall_parse-0.4.5.dist-info/RECORD,,
|