none-shall-parse 0.4.2__tar.gz → 0.4.3__tar.gz

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.3
2
2
  Name: none-shall-parse
3
- Version: 0.4.2
3
+ Version: 0.4.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>
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "none-shall-parse"
7
- version = "0.4.2"
7
+ version = "0.4.3"
8
8
  description = "Trinity Shared Python utilities."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -3,8 +3,10 @@ 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
7
 
7
8
  import pendulum
9
+ from pendulum import DateTime, Date
8
10
  from pendulum import local_timezone
9
11
  from pendulum.tz.exceptions import InvalidTimezone
10
12
 
@@ -24,38 +26,38 @@ class DateUtilsError(Exception):
24
26
  self.message = message
25
27
 
26
28
 
27
- def assert_week_start_date_is_valid(wso):
29
+ def assert_week_start_date_is_valid(wso: int) -> None:
28
30
  if wso < 1 or wso > 7:
29
31
  raise DateUtilsError("Weeks can only start on days between 1 and 7")
30
32
 
31
33
 
32
- def assert_month_start_date_is_valid(mso):
34
+ def assert_month_start_date_is_valid(mso: int) -> None:
33
35
  if mso > 28 or mso < 1:
34
36
  raise DateUtilsError("Months can only start on days between 1 and 28")
35
37
 
36
38
 
37
- def get_datetime_now(naive: bool = False, tz: str | None = None) -> datetime:
39
+ def get_datetime_now(naive: bool = False, tz: str | None = None) -> DateTime:
38
40
  """
39
41
  Get the current date and time.
40
42
 
41
43
  This function retrieves the current date and time using the pendulum library. It can
42
- return the datetime in either a naive or timezone-aware format, depending on the
44
+ return the DateTime in either a naive or timezone-aware format, depending on the
43
45
  parameters provided.
44
46
 
45
47
  Args:
46
- naive (bool): If True, returns a naive datetime object without timezone information.
48
+ naive (bool): If True, returns a naive DateTime object without timezone information.
47
49
  Defaults to True.
48
- tz (Optional[str]): The timezone to use if a timezone-aware datetime is requested.
50
+ tz (Optional[str]): The timezone to use if a timezone-aware DateTime is requested.
49
51
  If not provided and naive is False, the default system timezone
50
52
  is used.
51
53
 
52
54
  Returns:
53
- datetime: The current date and time based on the specified parameters.
55
+ pendulum.DateTime: The current date and time based on the specified parameters.
54
56
  """
55
57
  return pendulum.now().naive() if naive else pendulum.now(tz=tz)
56
58
 
57
59
 
58
- def za_now() -> datetime:
60
+ def za_now() -> DateTime:
59
61
  """
60
62
  Returns the current date and time in the South African timezone.
61
63
 
@@ -64,7 +66,7 @@ def za_now() -> datetime:
64
66
  time localization.
65
67
 
66
68
  Returns:
67
- datetime: The current date and time in the South African timezone.
69
+ pendulum.DateTime: The current date and time in the South African timezone.
68
70
  """
69
71
  return get_datetime_now(naive=False, tz=ZA_TZ)
70
72
 
@@ -95,39 +97,39 @@ def za_ordinal_year_day_tomorrow() -> int:
95
97
  return pendulum.now(ZA_TZ).add(days=1).day_of_year
96
98
 
97
99
 
98
- def utc_epoch_start() -> datetime:
100
+ def utc_epoch_start() -> DateTime:
99
101
  """
100
- Gets the UTC epoch start time as a datetime object.
102
+ Gets the UTC epoch start time as a DateTime object.
101
103
 
102
104
  This function calculates the start of the UNIX epoch (January 1, 1970)
103
- in UTC as a datetime object. It leverages the `pendulum` library for
105
+ in UTC as a DateTime object. It leverages the `pendulum` library for
104
106
  handling the time calculation with the specified UTC timezone.
105
107
 
106
108
  Returns:
107
- datetime: A datetime object representing the start of the UTC epoch.
109
+ pendulum.DateTime: A pendulum DateTime object representing the start of the UTC epoch.
108
110
  """
109
111
  return pendulum.from_timestamp(0)
110
112
 
111
113
 
112
114
  def _now_offset_n_units(n: int, units: str, naive: bool = False,
113
- tz: str | None = None) -> datetime:
115
+ tz: str | None = None) -> DateTime:
114
116
  """
115
- Calculate a datetime object offset by a specified number of time units.
117
+ Calculate a DateTime object offset by a specified number of time units.
116
118
 
117
- This function allows you to calculate a datetime offset by a given number of units
119
+ This function allows you to calculate a DateTime offset by a given number of units
118
120
  (minutes, hours, days, etc.) from the current time. You can also specify the timezone
119
- and whether the returned datetime should be naive or timezone-aware.
121
+ and whether the returned DateTime should be naive or timezone-aware.
120
122
 
121
123
  Parameters:
122
124
  n (int): The number of units to offset the current time by.
123
125
  units (str): The type of time unit to offset by.
124
- naive (bool): Whether to return a naive datetime (without timezone information).
126
+ naive (bool): Whether to return a naive DateTime (without timezone information).
125
127
  Defaults to False.
126
- tz (Optional[str]): The timezone of the resulting datetime. If not provided,
128
+ tz (Optional[str]): The timezone of the resulting DateTime. If not provided,
127
129
  the system's local timezone is used.
128
130
 
129
131
  Returns:
130
- datetime: The calculated datetime object, optionally timezone-aware or naive.
132
+ DateTime: The calculated DateTime object, optionally timezone-aware or naive.
131
133
  """
132
134
  kwargs = {units: n}
133
135
  return pendulum.now().add(**kwargs).naive() if naive else pendulum.now(tz).add(
@@ -135,58 +137,58 @@ def _now_offset_n_units(n: int, units: str, naive: bool = False,
135
137
 
136
138
 
137
139
  def now_offset_n_minutes(n: int, naive: bool = False,
138
- tz: str | None = None) -> datetime:
140
+ tz: str | None = None) -> DateTime:
139
141
  return _now_offset_n_units(n, units="minutes", naive=naive, tz=tz)
140
142
 
141
143
 
142
144
  def now_offset_n_hours(n: int, naive: bool = False,
143
- tz: str | None = None) -> datetime:
145
+ tz: str | None = None) -> DateTime:
144
146
  return _now_offset_n_units(n, units="hours", naive=naive, tz=tz)
145
147
 
146
148
 
147
149
  def now_offset_n_days(n: int, naive: bool = False,
148
- tz: str | None = None) -> datetime:
150
+ tz: str | None = None) -> DateTime:
149
151
  return _now_offset_n_units(n, units="days", naive=naive, tz=tz)
150
152
 
151
153
 
152
154
  def get_datetime_tomorrow(naive: bool = False,
153
- tz: str | None = None) -> datetime:
154
- """Get tomorrow's datetime"""
155
+ tz: str | None = None) -> DateTime:
156
+ """Get tomorrow's DateTime"""
155
157
  return now_offset_n_days(1, naive=naive, tz=tz)
156
158
 
157
159
 
158
160
  def get_datetime_yesterday(naive: bool = False,
159
- tz: str | None = None) -> datetime:
160
- """Get yesterday's datetime"""
161
+ tz: str | None = None) -> DateTime:
162
+ """Get yesterday's DateTime"""
161
163
  return now_offset_n_days(-1, naive=naive, tz=tz)
162
164
 
163
165
 
164
- def get_utc_datetime_offset_n_days(n: int = 0) -> datetime:
165
- """Get UTC datetime n with an offset of n days"""
166
+ def get_utc_datetime_offset_n_days(n: int = 0) -> DateTime:
167
+ """Get UTC DateTime n with an offset of n days"""
166
168
  return pendulum.now(UTC_TZ).add(days=n)
167
169
 
168
170
 
169
171
  def epoch_to_datetime(epoch_int: int | float, naive: bool = False,
170
- tz: str | None = None) -> datetime:
172
+ tz: str | None = None) -> DateTime:
171
173
  """
172
- Converts an epoch timestamp to a datetime object.
174
+ Converts an epoch timestamp to a DateTime object.
173
175
 
174
176
  This function takes an input epoch timestamp and converts it into a
175
- datetime object using the Pendulum library. It supports conversion
176
- to either naive or timezone-aware datetime objects based on the
177
+ DateTime object using the Pendulum library. It supports conversion
178
+ to either naive or timezone-aware DateTime objects based on the
177
179
  parameters provided.
178
180
 
179
181
  Parameters:
180
- epoch_int (int | float): The epoch timestamp to convert to a datetime
182
+ epoch_int (int | float): The epoch timestamp to convert to a DateTime
181
183
  object. It can be provided as an integer or float value.
182
- naive (bool): If True, the resulting datetime object will be naive
184
+ naive (bool): If True, the resulting DateTime object will be naive
183
185
  (without timezone information). Defaults to False.
184
- tz (Optional[str]): The timezone in which the resulting datetime object
186
+ tz (Optional[str]): The timezone in which the resulting DateTime object
185
187
  should be created. If not provided, the system's default timezone
186
188
  will be used.
187
189
 
188
190
  Returns:
189
- datetime: A datetime object representing the converted epoch timestamp.
191
+ DateTime: A DateTime object representing the converted epoch timestamp.
190
192
  """
191
193
 
192
194
  # We force the timezone to the user's local timezone if none was supplied
@@ -200,18 +202,18 @@ def epoch_to_datetime(epoch_int: int | float, naive: bool = False,
200
202
  return pendulum.from_timestamp(int(epoch_int), tz=tz)
201
203
 
202
204
 
203
- def epoch_to_utc_datetime(epoch_int: int | float | str) -> datetime:
205
+ def epoch_to_utc_datetime(epoch_int: int | float | str) -> DateTime:
204
206
  """
205
- Converts an epoch timestamp to a UTC datetime object.
207
+ Converts an epoch timestamp to a UTC DateTime object.
206
208
 
207
209
  This function takes an integer or float representing an epoch timestamp,
208
- and converts it to a datetime object in UTC timezone using Pendulum.
210
+ and converts it to a DateTime object in UTC timezone using Pendulum.
209
211
 
210
212
  Parameters:
211
213
  epoch_int: An epoch timestamp represented as an integer or float.
212
214
 
213
215
  Returns:
214
- A datetime object in UTC corresponding to the provided epoch timestamp.
216
+ A DateTime object in UTC corresponding to the provided epoch timestamp.
215
217
  """
216
218
  return pendulum.from_timestamp(int(epoch_int), tz=UTC_TZ)
217
219
 
@@ -223,7 +225,7 @@ def is_office_hours_in_timezone(epoch_int: int | float | str, tz: str | None = N
223
225
 
224
226
  Office hours are considered to be between 08:00 and 17:00 (8 AM to 5 PM) in
225
227
  the specified timezone. The function converts the provided epoch timestamp into
226
- a datetime object according to the given timezone and checks whether the time falls
228
+ a DateTime object according to the given timezone and checks whether the time falls
227
229
  within the defined office hours.
228
230
 
229
231
  Parameters:
@@ -248,7 +250,7 @@ def is_office_hours_in_timezone(epoch_int: int | float | str, tz: str | None = N
248
250
 
249
251
 
250
252
  def get_datetime_from_ordinal_and_sentinel(
251
- sentinel: datetime | None = None) -> Callable[[int], datetime]:
253
+ sentinel: DateTimeLike | None = None) -> Callable[[int], DateTime]:
252
254
  """
253
255
  Given an ordinal year day, and a sentinel datetime, get the closest past
254
256
  datetime to the sentinel that had the given ordinal year day.
@@ -258,19 +260,20 @@ def get_datetime_from_ordinal_and_sentinel(
258
260
  """
259
261
  # Check timezone awareness
260
262
  naive = sentinel.tzinfo is None
261
-
262
- sentinel_pdt = pendulum.instance(sentinel)
263
- sentinel_doy = sentinel_pdt.day_of_year
264
- sentinel_year = sentinel_pdt.year
263
+ sentinel = pendulum.instance(sentinel)
264
+ if naive:
265
+ sentinel = sentinel.naive()
266
+ sentinel_doy = sentinel.day_of_year
267
+ sentinel_year = sentinel.year
265
268
 
266
269
  if naive:
267
270
  this_year = pendulum.naive(sentinel_year, 1, 1)
268
271
  last_year = pendulum.naive(sentinel_year - 1, 1, 1)
269
272
  else:
270
- this_year = pendulum.datetime(sentinel_year, 1, 1, tz=sentinel_pdt.timezone)
271
- last_year = pendulum.datetime(sentinel_year - 1, 1, 1, tz=sentinel_pdt.timezone)
273
+ this_year = pendulum.datetime(sentinel_year, 1, 1, tz=sentinel.timezone)
274
+ last_year = pendulum.datetime(sentinel_year - 1, 1, 1, tz=sentinel.timezone)
272
275
 
273
- def f(ordinal: int) -> datetime:
276
+ def f(ordinal: int) -> DateTime:
274
277
  dt = this_year if ordinal <= sentinel_doy else last_year
275
278
 
276
279
  # Handle leap year edge case
@@ -278,7 +281,7 @@ def get_datetime_from_ordinal_and_sentinel(
278
281
  if naive:
279
282
  return pendulum.naive(1970, 1, 1)
280
283
  else:
281
- return pendulum.datetime(1970, 1, 1, tz=sentinel_pdt.timezone)
284
+ return pendulum.datetime(1970, 1, 1, tz=sentinel.timezone)
282
285
 
283
286
  result = dt.add(days=ordinal - 1)
284
287
  return result
@@ -287,7 +290,7 @@ def get_datetime_from_ordinal_and_sentinel(
287
290
 
288
291
 
289
292
  # ------------------------------------------------------------------[ Span Functions ]--
290
- def day_span(pts: datetime) -> Tuple[datetime, datetime]:
293
+ def day_span(pts: DateTimeLike) -> Tuple[DateTime, DateTime]:
291
294
  """
292
295
  Returns the beginning and end of the day passed in.
293
296
  begin is inclusive and end is exclusive.
@@ -300,13 +303,11 @@ def day_span(pts: datetime) -> Tuple[datetime, datetime]:
300
303
  dt = datetime(2023, 12, 25, 14, 30, 45)
301
304
 
302
305
  start, end = day_span(dt)
303
- print(type(start)) # <class 'datetime.datetime'>
306
+ print(type(start)) # <class 'DateTime.DateTime'>
304
307
 
305
308
  """
306
309
  # Check if input is naive or aware
307
310
  naive = pts.tzinfo is None
308
-
309
- # Convert to Pendulum for easier manipulation
310
311
  pdt = pendulum.instance(pts)
311
312
 
312
313
  # Use Pendulum's clean API
@@ -319,7 +320,7 @@ def day_span(pts: datetime) -> Tuple[datetime, datetime]:
319
320
  return begin, end
320
321
 
321
322
 
322
- def week_span(wso: int) -> Callable[[datetime], Tuple[datetime, datetime]]:
323
+ def week_span(wso: int) -> Callable[[DateTimeLike], Tuple[DateTime, DateTime]]:
323
324
  """
324
325
  Given an integer between 1 and 7, return a function that will give the
325
326
  start and end dates of the week.
@@ -339,7 +340,7 @@ def week_span(wso: int) -> Callable[[datetime], Tuple[datetime, datetime]]:
339
340
  """
340
341
  assert_week_start_date_is_valid(wso)
341
342
 
342
- def find_dates(pts: datetime) -> Tuple[datetime, datetime]:
343
+ def find_dates(pts: DateTimeLike) -> Tuple[DateTime, DateTime]:
343
344
  # Check if input is naive or aware
344
345
  naive = pts.tzinfo is None
345
346
  pdt = pendulum.instance(pts)
@@ -359,7 +360,7 @@ def week_span(wso: int) -> Callable[[datetime], Tuple[datetime, datetime]]:
359
360
  return find_dates
360
361
 
361
362
 
362
- def month_span(mso: int) -> Callable[[datetime], Tuple[datetime, datetime]]:
363
+ def month_span(mso: int) -> Callable[[DateTimeLike], Tuple[DateTime, DateTime]]:
363
364
  """
364
365
  Given an integer between 1 and 28, return a function that will give the
365
366
  start and end dates of the custom month period.
@@ -379,7 +380,7 @@ def month_span(mso: int) -> Callable[[datetime], Tuple[datetime, datetime]]:
379
380
  """
380
381
  assert_month_start_date_is_valid(mso)
381
382
 
382
- def find_dates(pts: datetime) -> Tuple[datetime, datetime]:
383
+ def find_dates(pts: DateTimeLike) -> Tuple[DateTime, DateTime]:
383
384
  # Convert to Pendulum
384
385
  naive = pts.tzinfo is None
385
386
  pdt = pendulum.instance(pts)
@@ -403,22 +404,22 @@ def month_span(mso: int) -> Callable[[datetime], Tuple[datetime, datetime]]:
403
404
  return find_dates
404
405
 
405
406
 
406
- def arb_span(dates: Sequence[str | datetime], naive: bool = False) -> Callable[
407
- [Any], Tuple[datetime, datetime]]:
407
+ def arb_span(dates: Sequence[str | DateTimeLike], naive: bool = False) -> Callable[
408
+ [Any], Tuple[DateTime, DateTime]]:
408
409
  """
409
410
  Parses two given dates and returns a callable function that provides the date range
410
411
  as a tuple of datetime objects. The function ensures the date range is valid and
411
412
  always returns the earlier date as the start and the later date as the end.
412
413
 
413
414
  Parameters:
414
- dates (Sequence[str | datetime]): A sequence containing exactly two dates where
415
+ dates (Sequence[str | DateTimeLike]): A sequence containing exactly two dates where
415
416
  each date is either a string or a datetime object.
416
417
  naive (bool): Optional flag. If True, the returned datetime objects will not
417
418
  have timezone information (naive datetime). Defaults to False.
418
419
 
419
420
  Returns:
420
- Callable[[Any], Tuple[datetime, datetime]]: A function that, when invoked,
421
- returns a tuple of datetime objects (start, end) representing the date range.
421
+ Callable[[Any], Tuple[DateTime, DateTime]]: A function that, when invoked,
422
+ returns a tuple of DateTime objects (start, end) representing the date range.
422
423
 
423
424
  Raises:
424
425
  DateUtilsError: If the provided dates are invalid, identical, or there's an error
@@ -466,9 +467,9 @@ def arb_span(dates: Sequence[str | datetime], naive: bool = False) -> Callable[
466
467
  except Exception as ex:
467
468
  raise DateUtilsError(f"Error parsing dates: {ex}")
468
469
 
469
- def find_dates(*args) -> Tuple[datetime, datetime]:
470
+ def find_dates(*args) -> Tuple[DateTime, DateTime]:
470
471
  """
471
- :return: tuple of datetime objects (start, end)
472
+ :return: tuple of DateTime objects (start, end)
472
473
  """
473
474
  if naive:
474
475
  return begin.naive(), end.naive()
@@ -478,9 +479,9 @@ def arb_span(dates: Sequence[str | datetime], naive: bool = False) -> Callable[
478
479
 
479
480
 
480
481
  def unroll_span_func(
481
- f: Callable[[datetime], Tuple[datetime, datetime]],
482
- cover: datetime | None = None,
483
- ) -> Tuple[List[datetime], List[int], List[str], datetime, datetime]:
482
+ f: Callable[[DateTimeLike], Tuple[DateTime, DateTime]],
483
+ cover: DateTimeLike | None = None,
484
+ ) -> Tuple[List[DateTime], List[int], List[str], DateTime, DateTime]:
484
485
  """
485
486
  Generate keys for a date range based on a provided function.
486
487
 
@@ -490,14 +491,14 @@ def unroll_span_func(
490
491
 
491
492
  Args:
492
493
  f: Function that takes a base date and returns a tuple of start and end dates.
493
- cover: Base date for computing the range. Defaults to the current date if None.
494
+ cover: Base datetime for computing the range. Defaults to the current date if None.
494
495
 
495
496
  Returns:
496
497
  A tuple containing:
497
- - List of datetime objects.
498
+ - List of DateTime objects.
498
499
  - List of ordinal day integers.
499
- - Start date of the range (as datetime).
500
- - End date of the range (as datetime).
500
+ - Start date of the range (as DateTime).
501
+ - End date of the range (as DateTime).
501
502
  If ord_ints_only is True, returns (ordinal_days, start, end, iso_dates).
502
503
 
503
504
  Raises:
@@ -507,15 +508,13 @@ def unroll_span_func(
507
508
  naive = cover.tzinfo is None
508
509
  try:
509
510
  start, end = f(cover)
511
+ start = pendulum.instance(start)
512
+ start = start.naive() if naive else start
513
+ end = pendulum.instance(end)
514
+ end = end.naive() if naive else end
510
515
  except Exception as e:
511
516
  raise DateUtilsError(f"Function f failed to compute date range: {str(e)}")
512
517
 
513
- # Make sure we can use pendulum with these dates
514
- start = pendulum.instance(start) if isinstance(start, datetime) else start
515
- start = start.naive() if naive else start
516
- end = pendulum.instance(end) if isinstance(end, datetime) else end
517
- end = end.naive() if naive else end
518
-
519
518
  try:
520
519
  # Generate date range using pendulum.interval
521
520
  # The absolute kwarg ensures that we do not have to care about dates passed
@@ -536,8 +535,8 @@ def unroll_span_func(
536
535
 
537
536
 
538
537
  def keys_for_span_func(
539
- f: Callable[[datetime], Tuple[datetime, datetime]],
540
- cover: datetime | None = None,
538
+ f: Callable[[DateTimeLike], Tuple[DateTime, DateTime]],
539
+ cover: DateTimeLike | None = None,
541
540
  key_in_format: str = "ODIN_{}",
542
541
  key_out_format: str = "ODOUT_{}",
543
542
  ):
@@ -553,8 +552,8 @@ def keys_for_span_func(
553
552
  Returns:
554
553
  - List of input keys (empty if key_in_format is None).
555
554
  - List of output keys (empty if key_out_format is None).
556
- - Start date of the range (as datetime).
557
- - End date of the range (as datetime).
555
+ - Start date of the range (as DateTime).
556
+ - End date of the range (as DateTime).
558
557
 
559
558
  Raises:
560
559
  DateUtilsError: If the date range cannot be processed.
@@ -565,8 +564,8 @@ def keys_for_span_func(
565
564
  return keys_in, keys_out, start, end
566
565
 
567
566
 
568
- def calendar_month_start_end(date_in_month: datetime | None = None) -> Tuple[
569
- datetime, datetime]:
567
+ def calendar_month_start_end(date_in_month: DateTimeLike | None = None) -> Tuple[
568
+ DateTime, DateTime]:
570
569
  naive = date_in_month.tzinfo is None
571
570
 
572
571
  if date_in_month is None:
@@ -593,7 +592,7 @@ def unix_timestamp() -> int:
593
592
  return round(time.time())
594
593
 
595
594
 
596
- def sentinel_date_and_ordinal_to_date(sentinel_date: datetime | date,
595
+ def sentinel_date_and_ordinal_to_date(sentinel_date: DateTimeLike | date,
597
596
  ordinal: int | float | str) -> date:
598
597
  """Convert sentinel date and ordinal day to actual date"""
599
598
  year = sentinel_date.year
@@ -620,13 +619,13 @@ def standard_tz_timestring(ts: int | float, tz: str = ZA_TZ) -> str:
620
619
  Format timestamp as: 2022-02-22 15:28:10 (SAST)
621
620
  :param ts: Seconds since epoch
622
621
  :param tz: Timezone string
623
- :return: Formatted datetime string
622
+ :return: Formatted date time string
624
623
  """
625
624
  dt = pendulum.from_timestamp(int(ts), tz=tz)
626
625
  return dt.strftime("%Y-%m-%d %H:%M:%S (%Z)")
627
626
 
628
627
 
629
- def get_notice_end_date(given_date: datetime | date | None = None) -> date:
628
+ def get_notice_end_date(given_date: DateTimeLike | date | Date | None = None) -> Date:
630
629
  """
631
630
  A notice end date is the end of the month of the given date if the given date
632
631
  is before or on the 15th. If the given date is after the 15th, the notice period
@@ -650,11 +649,13 @@ def get_notice_end_date(given_date: datetime | date | None = None) -> date:
650
649
  # End of next month
651
650
  end_date = pdt.add(months=2).start_of('month')
652
651
 
652
+ if isinstance(end_date, DateTime):
653
+ return end_date.date()
653
654
  return end_date
654
655
 
655
656
 
656
- def dt_to_za_time_string(v: datetime) -> str:
657
- """Convert datetime to South Africa time string"""
657
+ def dt_to_za_time_string(v: DateTimeLike) -> str:
658
+ """Convert DateTime to South Africa time string"""
658
659
  # Convert to Pendulum
659
660
  naive = v.tzinfo is None
660
661
  if naive:
@@ -674,24 +675,24 @@ def months_ago_selection() -> List[Tuple[int, str]]:
674
675
  ]
675
676
 
676
677
 
677
- def is_aware(dt: datetime) -> bool:
678
- """Check if a datetime object is timezone-aware."""
678
+ def is_aware(dt: DateTimeLike | Date | date) -> bool:
679
+ """Check if a DateTime object is timezone-aware."""
679
680
  return dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None
680
681
 
681
682
 
682
- def make_aware(dt: datetime | None, tz: str = None) -> datetime | None:
683
+ def make_aware(dt: DateTimeLike | date | Date | None, tz: str = None) -> DateTime | Date | None:
683
684
  """
684
- Convert a naive datetime to a timezone-aware datetime using Pendulum.
685
+ Convert a naive DateTime to a timezone-aware DateTime using Pendulum.
685
686
 
686
687
  Args:
687
- dt: The datetime object to convert. If None, returns None.
688
+ dt: The DateTime object to convert. If None, returns None.
688
689
  tz: The timezone to apply (default: The user's default timezone).).
689
690
 
690
691
  Returns:
691
- A timezone-aware datetime object.
692
+ A timezone-aware DateTime object.
692
693
 
693
694
  Raises:
694
- TypeError: If dt is not a datetime object or None.
695
+ TypeError: If dt is not a DateTime object or None.
695
696
  ValueError: If dt is already timezone-aware.
696
697
  DateUtilsError: If the timezone string is invalid.
697
698
  """
@@ -714,8 +715,8 @@ def make_aware(dt: datetime | None, tz: str = None) -> datetime | None:
714
715
  raise DateUtilsError(f"Invalid timezone: {tz}") from e
715
716
 
716
717
 
717
- def unaware_to_utc_aware(dt: datetime | None) -> datetime | None:
718
- """Convert naive datetime to UTC-aware datetime using Pendulum."""
718
+ def unaware_to_utc_aware(dt: DateTimeLike | date | Date | None) -> DateTime | Date | None:
719
+ """Convert naive DateTime to UTC-aware DateTime using Pendulum."""
719
720
  if not isinstance(dt, (datetime, type(None))):
720
721
  raise TypeError(f"Expected datetime or None, got {type(dt)}")
721
722
 
@@ -1,5 +1,8 @@
1
+ from datetime import datetime, date
1
2
  from typing import Protocol, Sequence, Tuple, Union, TypeVar
2
3
 
4
+ from pendulum import DateTime, Date
5
+
3
6
 
4
7
  class StringLike(Protocol):
5
8
  """
@@ -29,4 +32,10 @@ class StringLike(Protocol):
29
32
 
30
33
  ChoicesType = Sequence[Tuple[Union[int, str], StringLike]]
31
34
 
35
+ DateLike = Union[Date, date]
36
+
37
+ DateTimeLike = Union[DateTime, datetime]
38
+
39
+ DateTimeOrDateLike = Union[DateTimeLike, DateLike]
40
+
32
41
  T = TypeVar("T")