everysk-lib 1.10.2__cp312-cp312-win_amd64.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.
Files changed (137) hide show
  1. everysk/__init__.py +30 -0
  2. everysk/_version.py +683 -0
  3. everysk/api/__init__.py +61 -0
  4. everysk/api/api_requestor.py +167 -0
  5. everysk/api/api_resources/__init__.py +23 -0
  6. everysk/api/api_resources/api_resource.py +371 -0
  7. everysk/api/api_resources/calculation.py +779 -0
  8. everysk/api/api_resources/custom_index.py +42 -0
  9. everysk/api/api_resources/datastore.py +81 -0
  10. everysk/api/api_resources/file.py +42 -0
  11. everysk/api/api_resources/market_data.py +223 -0
  12. everysk/api/api_resources/parser.py +66 -0
  13. everysk/api/api_resources/portfolio.py +43 -0
  14. everysk/api/api_resources/private_security.py +42 -0
  15. everysk/api/api_resources/report.py +65 -0
  16. everysk/api/api_resources/report_template.py +39 -0
  17. everysk/api/api_resources/tests.py +115 -0
  18. everysk/api/api_resources/worker_execution.py +64 -0
  19. everysk/api/api_resources/workflow.py +65 -0
  20. everysk/api/api_resources/workflow_execution.py +93 -0
  21. everysk/api/api_resources/workspace.py +42 -0
  22. everysk/api/http_client.py +63 -0
  23. everysk/api/tests.py +32 -0
  24. everysk/api/utils.py +262 -0
  25. everysk/config.py +451 -0
  26. everysk/core/_tests/serialize/test_json.py +336 -0
  27. everysk/core/_tests/serialize/test_orjson.py +295 -0
  28. everysk/core/_tests/serialize/test_pickle.py +48 -0
  29. everysk/core/cloud_function/main.py +78 -0
  30. everysk/core/cloud_function/tests.py +86 -0
  31. everysk/core/compress.py +245 -0
  32. everysk/core/datetime/__init__.py +12 -0
  33. everysk/core/datetime/calendar.py +144 -0
  34. everysk/core/datetime/date.py +424 -0
  35. everysk/core/datetime/date_expression.py +299 -0
  36. everysk/core/datetime/date_mixin.py +1475 -0
  37. everysk/core/datetime/date_settings.py +30 -0
  38. everysk/core/datetime/datetime.py +713 -0
  39. everysk/core/exceptions.py +435 -0
  40. everysk/core/fields.py +1176 -0
  41. everysk/core/firestore.py +555 -0
  42. everysk/core/fixtures/_settings.py +29 -0
  43. everysk/core/fixtures/other/_settings.py +18 -0
  44. everysk/core/fixtures/user_agents.json +88 -0
  45. everysk/core/http.py +691 -0
  46. everysk/core/lists.py +92 -0
  47. everysk/core/log.py +709 -0
  48. everysk/core/number.py +37 -0
  49. everysk/core/object.py +1469 -0
  50. everysk/core/redis.py +1021 -0
  51. everysk/core/retry.py +51 -0
  52. everysk/core/serialize.py +674 -0
  53. everysk/core/sftp.py +414 -0
  54. everysk/core/signing.py +53 -0
  55. everysk/core/slack.py +127 -0
  56. everysk/core/string.py +199 -0
  57. everysk/core/tests.py +240 -0
  58. everysk/core/threads.py +199 -0
  59. everysk/core/undefined.py +70 -0
  60. everysk/core/unittests.py +73 -0
  61. everysk/core/workers.py +241 -0
  62. everysk/sdk/__init__.py +23 -0
  63. everysk/sdk/base.py +98 -0
  64. everysk/sdk/brutils/cnpj.py +391 -0
  65. everysk/sdk/brutils/cnpj_pd.py +129 -0
  66. everysk/sdk/engines/__init__.py +26 -0
  67. everysk/sdk/engines/cache.py +185 -0
  68. everysk/sdk/engines/compliance.py +37 -0
  69. everysk/sdk/engines/cryptography.py +69 -0
  70. everysk/sdk/engines/expression.cp312-win_amd64.pyd +0 -0
  71. everysk/sdk/engines/expression.pyi +55 -0
  72. everysk/sdk/engines/helpers.cp312-win_amd64.pyd +0 -0
  73. everysk/sdk/engines/helpers.pyi +26 -0
  74. everysk/sdk/engines/lock.py +120 -0
  75. everysk/sdk/engines/market_data.py +244 -0
  76. everysk/sdk/engines/settings.py +19 -0
  77. everysk/sdk/entities/__init__.py +23 -0
  78. everysk/sdk/entities/base.py +784 -0
  79. everysk/sdk/entities/base_list.py +131 -0
  80. everysk/sdk/entities/custom_index/base.py +209 -0
  81. everysk/sdk/entities/custom_index/settings.py +29 -0
  82. everysk/sdk/entities/datastore/base.py +160 -0
  83. everysk/sdk/entities/datastore/settings.py +17 -0
  84. everysk/sdk/entities/fields.py +375 -0
  85. everysk/sdk/entities/file/base.py +215 -0
  86. everysk/sdk/entities/file/settings.py +63 -0
  87. everysk/sdk/entities/portfolio/base.py +248 -0
  88. everysk/sdk/entities/portfolio/securities.py +241 -0
  89. everysk/sdk/entities/portfolio/security.py +580 -0
  90. everysk/sdk/entities/portfolio/settings.py +97 -0
  91. everysk/sdk/entities/private_security/base.py +226 -0
  92. everysk/sdk/entities/private_security/settings.py +17 -0
  93. everysk/sdk/entities/query.py +603 -0
  94. everysk/sdk/entities/report/base.py +214 -0
  95. everysk/sdk/entities/report/settings.py +23 -0
  96. everysk/sdk/entities/script.py +310 -0
  97. everysk/sdk/entities/secrets/base.py +128 -0
  98. everysk/sdk/entities/secrets/script.py +119 -0
  99. everysk/sdk/entities/secrets/settings.py +17 -0
  100. everysk/sdk/entities/settings.py +48 -0
  101. everysk/sdk/entities/tags.py +174 -0
  102. everysk/sdk/entities/worker_execution/base.py +307 -0
  103. everysk/sdk/entities/worker_execution/settings.py +63 -0
  104. everysk/sdk/entities/workflow_execution/base.py +113 -0
  105. everysk/sdk/entities/workflow_execution/settings.py +32 -0
  106. everysk/sdk/entities/workspace/base.py +99 -0
  107. everysk/sdk/entities/workspace/settings.py +27 -0
  108. everysk/sdk/settings.py +67 -0
  109. everysk/sdk/tests.py +105 -0
  110. everysk/sdk/worker_base.py +47 -0
  111. everysk/server/__init__.py +9 -0
  112. everysk/server/applications.py +63 -0
  113. everysk/server/endpoints.py +516 -0
  114. everysk/server/example_api.py +69 -0
  115. everysk/server/middlewares.py +80 -0
  116. everysk/server/requests.py +62 -0
  117. everysk/server/responses.py +119 -0
  118. everysk/server/routing.py +64 -0
  119. everysk/server/settings.py +36 -0
  120. everysk/server/tests.py +36 -0
  121. everysk/settings.py +98 -0
  122. everysk/sql/__init__.py +9 -0
  123. everysk/sql/connection.py +232 -0
  124. everysk/sql/model.py +376 -0
  125. everysk/sql/query.py +417 -0
  126. everysk/sql/row_factory.py +63 -0
  127. everysk/sql/settings.py +49 -0
  128. everysk/sql/utils.py +129 -0
  129. everysk/tests.py +23 -0
  130. everysk/utils.py +81 -0
  131. everysk/version.py +15 -0
  132. everysk_lib-1.10.2.dist-info/.gitignore +5 -0
  133. everysk_lib-1.10.2.dist-info/METADATA +326 -0
  134. everysk_lib-1.10.2.dist-info/RECORD +137 -0
  135. everysk_lib-1.10.2.dist-info/WHEEL +5 -0
  136. everysk_lib-1.10.2.dist-info/licenses/LICENSE.txt +9 -0
  137. everysk_lib-1.10.2.dist-info/top_level.txt +2 -0
@@ -0,0 +1,1475 @@
1
+
2
+ ###############################################################################
3
+ #
4
+ # (C) Copyright 2023 EVERYSK TECHNOLOGIES
5
+ #
6
+ # This is an unpublished work containing confidential and proprietary
7
+ # information of EVERYSK TECHNOLOGIES. Disclosure, use, or reproduction
8
+ # without authorization of EVERYSK TECHNOLOGIES is prohibited.
9
+ #
10
+ ###############################################################################
11
+ from calendar import monthrange, SATURDAY, SUNDAY
12
+ from datetime import datetime, date, timedelta
13
+ from typing import Union, Self, TYPE_CHECKING
14
+
15
+ from everysk.core.datetime import date_settings
16
+
17
+ if TYPE_CHECKING:
18
+ from everysk.core.datetime import Date, DateTime
19
+
20
+
21
+ def get_holidays(calendar: str, years: list = Undefined) -> dict:
22
+ """
23
+ Uses https://pypi.org/project/holidays/ to make a list of dates.
24
+ Pass a list of years if you need a more specific list.
25
+
26
+ Args:
27
+ calendar (str): Two digit country symbol.
28
+ years (list, optional): List of int years. Ex: [2021, 2022]. Defaults to Undefined.
29
+
30
+ Returns:
31
+ dict: A dictionary containing holiday dates as keys and holiday names as values.
32
+
33
+ Example:
34
+ >>> from everysk.core.datetime.date_mixin import get_holidays
35
+ >>> br_holidays = get_holidays('BR', years=[2021, 2022])
36
+ >>> print(brazil_holidays)
37
+ {
38
+ datetime.date(2021, 1, 1): 'Confraternização Universal',
39
+ datetime.date(2023, 4, 2): 'Sexta-feira Santa',
40
+ ...
41
+ }
42
+ """
43
+ if calendar:
44
+ kwargs = {'calendar': calendar}
45
+
46
+ if years is not Undefined:
47
+ kwargs['years'] = years
48
+
49
+ # This module has 10 mb so we only import it when needed
50
+ from everysk.core.datetime import calendar # pylint: disable=import-outside-toplevel
51
+ return calendar.get_holidays(**kwargs)
52
+
53
+ return {}
54
+
55
+
56
+ class DateMixin:
57
+
58
+ @property
59
+ def quarter(self) -> int:
60
+ """
61
+ Get the quarter of the year for the current date.
62
+
63
+ Returns:
64
+ int: The quarter of the year (1 to 4) for the current date.
65
+
66
+ Example:
67
+ >>> date_obj = Date(2023, 7, 31)
68
+ >>> date_obj.quarter
69
+ 3
70
+
71
+ >>> date_time_obj = DateTime(2023, 7, 31, 12, 0)
72
+ >>> date_time_obj.quarter
73
+ 3
74
+ """
75
+ return (self.month - 1) // 3 + 1
76
+
77
+ @property
78
+ def month_name(self) -> str:
79
+ """
80
+ Get the name of the month for the current date.
81
+
82
+ Returns:
83
+ str: The name of the month for the current date.
84
+
85
+ Example:
86
+ >>> date_obj = Date(2023, 7, 31)
87
+ >>> date_obj.month_name
88
+ 'July'
89
+
90
+ >>> date_time_obj = DateTime(2023, 7, 31, 12, 0)
91
+ >>> date_time_obj.month_name
92
+ 'July'
93
+ """
94
+ return self.strftime('%B')
95
+
96
+ @property
97
+ def week_of_year(self) -> int:
98
+ """
99
+ Get the week of the year for the current date.
100
+
101
+ Returns:
102
+ int: The week of the year (0 to 53) for the current date.
103
+
104
+ Example:
105
+ >>> date_obj = Date(2023, 7, 31)
106
+ >>> date_obj.week_of_year
107
+ 31 # Represents the 30th week of the year.
108
+
109
+ >>> date_time_obj = DateTime(2023, 7, 31, 12, 0)
110
+ >>> date_time_obj.week_of_year
111
+ 31
112
+ """
113
+ return int(self.strftime('%U'))
114
+
115
+ @property
116
+ def week_of_month(self) -> int:
117
+ """
118
+ Get the week of the month for the current date.
119
+
120
+ Returns:
121
+ int: The week of the month (1 to 5) for the current date.
122
+
123
+ Example:
124
+ >>> date_obj = Date(2023, 7, 31)
125
+ >>> date_obj.week_of_month
126
+ 5 # Represents the fifth week of the month.
127
+
128
+ >>> date_time_obj = DateTime(2023, 7, 31, 12, 0)
129
+ >>> date_time_obj.week_of_month
130
+ 5
131
+ """
132
+ return (self.day - 1) // 7 + 1
133
+
134
+ @property
135
+ def day_name(self) -> str:
136
+ """
137
+ Get the name of the day of the week for the current date.
138
+
139
+ Returns:
140
+ str: The name of the day of the week for the current date.
141
+
142
+ Example:
143
+ >>> date_obj = Date(2023, 7, 31)
144
+ >>> date_obj.day_name
145
+ 'Sunday'
146
+
147
+ >>> date_obj = DateTime(2023, 7, 31, 12, 00)
148
+ >>> date_obj.day_name
149
+ 'Sunday'
150
+ """
151
+ return self.strftime('%A')
152
+
153
+ @property
154
+ def day_of_year(self) -> int:
155
+ """
156
+ Get the day of the year for the current date.
157
+
158
+ Returns:
159
+ int: The day of the year (1 to 366) for the current date.
160
+
161
+ Example:
162
+ >>> date_obj = Date(2023, 7, 31)
163
+ >>> date_obj.day_of_year
164
+ 212 # Represents the 212th day of the year.
165
+
166
+ >>> date_obj = DateTime(2023, 7, 31, 12, 00)
167
+ >>> date_obj.day_of_year
168
+ 212
169
+ """
170
+ return self.timetuple().tm_yday
171
+
172
+ def toordinal(self, start_date: Union['Date', 'DateTime'] = None) -> int:
173
+ """
174
+ Get the ordinal day count since a base date for the current date.
175
+
176
+ This method returns the ordinal day count since a specified `start_date` for the current date.
177
+ If `start_date` is provided, the day count is calculated relative to that date; otherwise, it's calculated
178
+ relative to the base date '00010101'.
179
+
180
+ Args:
181
+ start_date (Date or DateTime, optional): The base date for calculating the ordinal day count. Defaults to None.
182
+
183
+ Raises:
184
+ AttributeError: When the start date is not a Date, DateTime or None.
185
+
186
+ Returns:
187
+ int: The ordinal day count since the specified `start_date` or the base date '00010101'.
188
+
189
+ Example:
190
+ >>> date_obj = Date(2023, 7, 31)
191
+ >>> date_obj.toordinal()
192
+ 738732 # Represents the ordinal day count since '00010101'.
193
+
194
+ >>> base_date = DateTime(2000, 1, 1, 12, 0)
195
+ >>> date_obj = DateTime(2023, 7, 31, 12, 0)
196
+ >>> date_obj.toordinal(start_date=base_date)
197
+ 8613 # Represents the ordinal day count since '20000101'.
198
+ """
199
+ delta = 0
200
+
201
+ # pylint: disable=not-callable
202
+ if start_date:
203
+ start_date = date(start_date.year, start_date.month, start_date.day)
204
+ delta = start_date.toordinal() - 1
205
+
206
+ result = date(self.year, self.month, self.day).toordinal() - delta
207
+ return result
208
+
209
+ ####################################
210
+ ### HELPERS
211
+ ####################################
212
+
213
+ @classmethod
214
+ def market_start(cls) -> Self:
215
+ """
216
+ This class method returns the market start date, which is the date when the market opens for the first time.
217
+
218
+ Returns:
219
+ Date or DateTime: The market start date.
220
+
221
+ Example:
222
+ >>> Date.market_start()
223
+ Date(2023, 7, 1)
224
+
225
+ >>> DateTime.market_start()
226
+ DateTime(2023, 7, 1, 0, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC'))
227
+ """
228
+ return cls.ensure(date_settings.MARKET_START_DATE_TIME)
229
+
230
+ @staticmethod
231
+ def _adjust_direction(start_date: Union['Date', 'DateTime'], end_date: Union['Date', 'DateTime']) -> tuple[Union['Date', 'DateTime'], Union['Date', 'DateTime'], int]:
232
+ """
233
+ Adjust the start and end dates and calculate a multiplier if necessary.
234
+
235
+ This static method takes two dates, `start_date` and `end_date`, and adjusts them if `end_date` is earlier than `start_date`.
236
+ It also calculates a multiplier to adjust the calculation direction if needed.
237
+
238
+ Args:
239
+ start_date (Union['Date', 'DateTime']): The start date of the date range.
240
+ end_date (Union['Date', 'DateTime']): The end date of the date range.
241
+
242
+ Returns:
243
+ tuple[Union['Date', 'DateTime'], Union['Date', 'DateTime'], int]: A tuple containing the adjusted `start_date`, `end_date`, and a direction (1 or -1).
244
+
245
+ Example:
246
+ >>> start = DateTime(2023, 7, 1)
247
+ >>> end = DateTime(2023, 6, 1)
248
+ >>> DateTime._adjust_direction(start, end)
249
+ (DateTime(2023, 6, 1, 0, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC')),
250
+ DateTime(2023, 7, 1, 0, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC')),
251
+ -1)
252
+
253
+ >>> start = Date(2023, 7, 1)
254
+ >>> end = Date(2023, 6, 1)
255
+ >>> Date._adjust_direction(start, end)
256
+ (Date(2023, 6, 1), Date(2023, 7, 1), -1)
257
+ """
258
+ direction = 1
259
+
260
+ if end_date < start_date:
261
+ end_date, start_date = start_date, end_date
262
+ direction = -1
263
+
264
+ return (start_date, end_date, direction)
265
+
266
+ ####################################
267
+ ### GET LAST DATE OF
268
+ ####################################
269
+
270
+ def get_last_day_of_week(self, bizdays: bool = False, calendar: str = None) -> Self:
271
+ """
272
+ Get the last day of the week for a given date, optionally considering business days and holidays.
273
+
274
+ This method calculates the last day of the week (Saturday) for the current date or the provided 'Date' or 'DateTime' object.
275
+ Optionally, it can also consider business days by excluding weekends and accounting for holidays.
276
+
277
+ Args:
278
+ bizdays (bool, optional): If True, consider business days by excluding Weekends. Defaults to False.
279
+ calendar (str, optional): A calendar name used to determine holidays. Defaults to None, which means no holidays are considered.
280
+
281
+ Raises:
282
+ NotImplementedError: If an invalid `calendar` name is provided.
283
+
284
+ Returns:
285
+ Date or DateTime: The last day of the week (Sunday) for the current date or the provided `Date` or `DateTime` object, considering business days and holidays if specified.
286
+
287
+ Example:
288
+ To calculate the last day of the week for the current date:
289
+ >>> Date(2023, 8, 11).get_last_day_of_week()
290
+ Date(2023, 8, 12)
291
+
292
+ To calculate the last day of the week with business days and holidays using a specific calendar:
293
+ >>> DateTime(2023, 8, 11, 12, 0).get_last_day_of_week(bizdays=True)
294
+ DateTime(2023, 8, 11, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC'))
295
+ """
296
+ last_day = self
297
+
298
+ while last_day.weekday() != SATURDAY:
299
+ last_day += timedelta(days=1)
300
+
301
+ if bizdays:
302
+ last_day -= timedelta(days=1)
303
+
304
+ if calendar:
305
+ hdays = get_holidays(calendar, years=[self.year - 1, self.year, self.year + 1])
306
+
307
+ while last_day.date() in hdays or (bizdays and last_day.weekday() in (SUNDAY, SATURDAY)):
308
+ last_day -= timedelta(days=1)
309
+
310
+ return last_day
311
+
312
+ def get_last_day_of_month(self, bizdays: bool = False, calendar: str = None) -> Self:
313
+ """
314
+ Get the last day of the month for a given date, optionally considering business days and holidays.
315
+
316
+ This method calculates the last day of the month for the current date or the provided `Date` or `DateTime` object.
317
+ Optionally, it can also consider business days by excluding weekends and accounting for holidays.
318
+
319
+ Args:
320
+ bizdays (bool, optional): If True, consider business days by excluding weekends. Defaults to False.
321
+ calendar (str, optional): A calendar name used to determine holidays. Defaults to None, which means no holidays are considered.
322
+
323
+ Raises:
324
+ NotImplementedError: If an invalid `calendar` name is provided.
325
+
326
+ Returns:
327
+ Date or DateTime: The last day of the month for the current date or the provided `Date` or `DateTime` object, considering business days and holidays if specified.
328
+
329
+ Example:
330
+ To calculate the last day of the month for the current date:
331
+ >>> Date(2023, 8, 11).get_last_day_of_month()
332
+ Date(2023, 8, 31)
333
+
334
+ To calculate the last day of the month with business days and holidays using a specific calendar:
335
+ >>> DateTime(2023, 8, 11, 12, 0).get_last_day_of_month(bizdays=True)
336
+ DateTime(2023, 8, 31, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC'))
337
+ """
338
+ last_day = self
339
+
340
+ day = monthrange(last_day.year, last_day.month)[1]
341
+ last_day = last_day.replace(year=last_day.year, month=last_day.month, day=day)
342
+
343
+ if bizdays:
344
+ while last_day.weekday() in (SUNDAY, SATURDAY):
345
+ last_day -= timedelta(days=1)
346
+
347
+ if calendar:
348
+ hdays = get_holidays(calendar, years=[self.year])
349
+
350
+ while last_day.date() in hdays or (bizdays and last_day.weekday() in (SUNDAY, SATURDAY)):
351
+ last_day -= timedelta(days=1)
352
+
353
+ return last_day
354
+
355
+ def get_last_day_of_quarter(self, bizdays: bool = False, calendar: str = None) -> Self:
356
+ """
357
+ Get the last day of the quarter for a given date, optionally considering business days and holidays.
358
+
359
+ This method calculates the last day of the quarter for the current date or the provided `Date` or `DateTime` object.
360
+ Optionally, it can also consider business days by excluding weekends and accounting for holidays.
361
+
362
+ Args:
363
+ bizdays (bool, optional): If True, consider business days by excluding weekends. Defaults to False.
364
+ calendar (str, optional): A calendar name used to determine holidays. Defaults to None, which means no holidays are considered.
365
+
366
+ Raises:
367
+ NotImplementedError: If an invalid `calendar` name is provided.
368
+
369
+ Returns:
370
+ Date or DateTime: The last day of the quarter for the current date or the provided `Date` or `DateTime` object, considering business days and holidays if specified.
371
+
372
+ Example:
373
+ To calculate the last day of the quarter for the current date:
374
+ >>> Date(2023, 8, 11).get_last_day_of_quarter()
375
+ Date(2023, 9, 30)
376
+
377
+ To calculate the last day of the quarter with business days and holidays using a specific calendar:
378
+ >>> DateTime(2023, 8, 11, 12, 0).get_last_day_of_quarter(bizdays=True)
379
+ DDateTime(2023, 9, 29, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC'))
380
+ """
381
+ current_quarter = (self.month - 1) // 3 + 1
382
+ last_month_of_quarter = current_quarter * 3
383
+
384
+ # To handle months with varying numbers of days, set the date to the first day of the last month of the quarter.
385
+ # This prevents the month from being instantiated with an incorrect number of days
386
+ new_date = self.replace(day=1, month=last_month_of_quarter)
387
+ last_day = new_date.get_last_day_of_month(bizdays=bizdays, calendar=calendar)
388
+
389
+ return last_day
390
+
391
+ def get_last_day_of_year(self, bizdays: bool = False, calendar: str = None) -> Self:
392
+ """
393
+ Get the last day of the year for a given date, optionally considering business days and holidays.
394
+
395
+ This method calculates the last day of the year for the current date or the provided Date or DateTime object.
396
+ Optionally, it can also consider business days by excluding weekends and accounting for holidays.
397
+
398
+ Args:
399
+ bizdays (bool, optional): If True, consider business days by excluding weekends. Defaults to False.
400
+ calendar (str, optional): A calendar name used to determine holidays. Defaults to None, which means no holidays are considered.
401
+
402
+ Raises:
403
+ ValueError: If an invalid `calendar` name is provided.
404
+
405
+ Returns:
406
+ Date or DateTime: The last day of the year for the current date or the provided Date or DateTime object, considering business days and holidays if specified.
407
+
408
+ Example:
409
+ To calculate the last day of the year for the current date:
410
+ >>> Date(2023, 8, 11).get_last_day_of_year()
411
+ Date(2023, 12, 31)
412
+
413
+ To calculate the last day of the year with business days and holidays using a specific calendar:
414
+ >>> DateTime(2023, 8, 11).get_last_day_of_year(bizdays=True)
415
+ DateTime(2023, 12, 29, 0, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC'))
416
+ """
417
+ last_day = self.replace(month=12)
418
+
419
+ last_day = last_day.get_last_day_of_month(bizdays=bizdays, calendar=calendar)
420
+
421
+ return last_day
422
+
423
+ ####################################
424
+ ### IS LAST DATE OF
425
+ ####################################
426
+
427
+ def is_last_day_of_week(self, bizdays: bool = False, calendar: str = None) -> bool:
428
+ """
429
+ Check if a given date is the last day of the week, optionally considering business days and holidays.
430
+
431
+ This method checks if the input date is the last day of the week (Saturday).
432
+ Optionally, it can also consider business days by excluding weekends and accounting for holidays.
433
+
434
+ Args:
435
+ bizdays (bool, optional): If True, consider business days by excluding Sundays. Defaults to False.
436
+ calendar (str, optional): The name of the calendar to use for holiday calculation. If provided, holidays will be considered when checking for the last day of the week. Defaults to None.
437
+
438
+ Raises:
439
+ NotImplementedError: If an invalid `calendar` name is provided.
440
+
441
+ Returns:
442
+ bool: True if the input date is the last day of the week, considering business days and holidays if specified, False otherwise.
443
+
444
+ Example:
445
+ To check if a specific date is the last day of the week:
446
+ >>> Date(2023, 8, 12).is_last_day_of_week()
447
+ True
448
+
449
+ To check if a date is the last day of the week with business days and holidays using a specific calendar:
450
+ >>> DateTime(2023, 8, 11, 12, 0).is_last_day_of_week(bizdays=True)
451
+ True
452
+ """
453
+ return self == self.get_last_day_of_week(bizdays=bizdays, calendar=calendar)
454
+
455
+ def is_last_day_of_month(self, bizdays: bool = False, calendar: str = None) -> bool:
456
+ """
457
+ Check if a given date is the last day of the month, optionally considering business days and holidays.
458
+
459
+ This method checks if the input date is the last day of the month.
460
+ Optionally, it can also consider business days by excluding weekends and accounting for holidays.
461
+
462
+ Args:
463
+ bizdays (bool, optional): If True, consider business days by excluding weekends. Defaults to False.
464
+ calendar (str, optional): The name of the calendar to use for holiday calculation. If provided, holidays will be considered when checking for the last day of the month. Defaults to None.
465
+
466
+ Raises:
467
+ NotImplementedError: If an invalid `calendar` name is provided.
468
+
469
+ Returns:
470
+ bool: True if the input date is the last day of the month, considering business days and holidays if specified, False otherwise.
471
+
472
+ Example:
473
+ To check if a specific date is the last day of the month:
474
+ >>> Date(2023, 8, 31).is_last_day_of_month()
475
+ True
476
+
477
+ To check if a date is the last day of the month with business days and holidays using a specific calendar:
478
+ >>> DateTime(2023, 8, 30, 12, 0).is_last_day_of_month(bizdays=True)
479
+ False
480
+ """
481
+ return self == self.get_last_day_of_month(bizdays=bizdays, calendar=calendar)
482
+
483
+ def is_last_day_of_quarter(self, bizdays: bool = False, calendar: str = None) -> bool:
484
+ """
485
+ Check if a given date is the last day of the quarter, optionally considering business days and holidays.
486
+
487
+ This method checks if the input date is the last day of the quarter.
488
+ Optionally, it can also consider business days by excluding weekends and accounting for holidays.
489
+
490
+ Args:
491
+ bizdays (bool, optional): If True, consider business days by excluding weekends. Defaults to False.
492
+ calendar (str, optional): The name of the calendar to use for holiday calculation. If provided, holidays will be considered when checking for the last day of the quarter. Defaults to None.
493
+
494
+ Raises:
495
+ NotImplementedError: If an invalid `calendar` name is provided.
496
+
497
+ Returns:
498
+ bool: True if the input date is the last day of the quarter, considering business days and holidays if specified, False otherwise.
499
+
500
+ Example:
501
+ To check if a specific date is the last day of the quarter:
502
+ >>> Date(2023, 9, 30).is_last_day_of_quarter()
503
+ True
504
+
505
+ To check if a date is the last day of the quarter with business days and holidays using a specific calendar:
506
+ >>> DateTime(2023, 9, 29, 12, 0).is_last_day_of_quarter(bizdays=True)
507
+ True
508
+ """
509
+ return self == self.get_last_day_of_quarter(bizdays=bizdays, calendar=calendar)
510
+
511
+ def is_last_day_of_year(self, bizdays: bool = False, calendar: str = None) -> bool:
512
+ """
513
+ Check if a given date is the last day of the year, optionally considering business days and holidays.
514
+
515
+ This method checks if the input date is the last day of the year.
516
+ Optionally, it can also consider business days by excluding weekends and accounting for holidays.
517
+
518
+ Args:
519
+ bizdays (bool, optional): If True, consider business days by excluding weekends. Defaults to False.
520
+ calendar (str, optional): The name of the calendar to use for holiday calculation. If provided, holidays will be considered when checking for the last day of the year. Defaults to None.
521
+
522
+ Raises:
523
+ NotImplementedError: If an invalid `calendar` name is provided.
524
+
525
+ Returns:
526
+ bool: True if the input date is the last day of the year, considering business days and holidays if specified, False otherwise.
527
+
528
+ Example:
529
+ To check if a specific date is the last day of the year:
530
+ >>> Date(2023, 12, 31).is_last_day_of_year()
531
+ True
532
+
533
+ To check if a date is the last day of the year with business days and holidays using a specific calendar:
534
+ >>> DateTime(2023, 12, 30, 12, 0).is_last_day_of_year(bizdays=True)
535
+ False
536
+ """
537
+ return self == self.get_last_day_of_year(bizdays=bizdays, calendar=calendar)
538
+
539
+ ####################################
540
+ ### GET FIRST DATE OF
541
+ ####################################
542
+
543
+ def get_first_day_of_week(self, bizdays: bool = False, calendar: str = None) -> Self:
544
+ """
545
+ Get the first day of the week for a given date, optionally considering business days and holidays.
546
+
547
+ This static method takes a Date or DateTime object and calculates the first day of the week (Sunday) for that week.
548
+ Optionally, it can also consider business days by excluding weekends and accounting for holidays.
549
+
550
+ Args:
551
+ bizdays (bool, optional): If True, consider business days by excluding weekends. Defaults to False.
552
+ calendar (str, optional): The name of the calendar to use for holiday calculation. If provided, holidays will be considered when checking for the last day of the year. Defaults to None.
553
+
554
+ Raises:
555
+ NotImplementedError: If an invalid `calendar` name is provided.
556
+
557
+ Returns:
558
+ Date or DateTime: The first day of the week (Sunday) for the input date, considering business days and holidays if specified.
559
+
560
+ Example:
561
+ To check if a specific date is the first day of the week:
562
+ >>> Date(2023, 8, 11).get_first_day_of_week()
563
+ Date(2023, 8, 6)
564
+
565
+ To calculate the first day of the week with business days and holidays using a specific calendar:
566
+ >>> DateTime(2023, 8, 11, 12, 0).get_first_day_of_week(bizdays=True)
567
+ DateTime(2023, 8, 7, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC'))
568
+ """
569
+ first_day = self
570
+
571
+ while first_day.weekday() != SUNDAY:
572
+ first_day -= timedelta(days=1)
573
+
574
+ if bizdays:
575
+ first_day += timedelta(days=1)
576
+
577
+ if calendar:
578
+ hdays = get_holidays(calendar, years=[self.year - 1, self.year, self.year + 1])
579
+
580
+ while first_day.date() in hdays or (bizdays and first_day.weekday() in (SUNDAY, SATURDAY)):
581
+ first_day += timedelta(days=1)
582
+
583
+ return first_day
584
+
585
+ def get_first_day_of_month(self, bizdays: bool = False, calendar: str = None) -> Self:
586
+ """
587
+ Get the first day of the month for a given date, optionally considering business days and holidays.
588
+
589
+ This static method takes a Date or DateTime object and calculates the first day of the month for that date.
590
+ Optionally, it can also consider business days by excluding weekends and accounting for holidays.
591
+
592
+ Args:
593
+ bizdays (bool, optional): If True, consider business days by excluding weekends. Defaults to False.
594
+ calendar (str, optional): The name of the calendar to use for holiday calculation. If provided, holidays will be considered when checking for the last day of the year. Defaults to None.
595
+
596
+ Raises:
597
+ NotImplementedError: If an invalid `calendar` name is provided.
598
+
599
+ Returns:
600
+ Date or DateTime: The first day of the month for the input date, considering business days and holidays if specified.
601
+
602
+ Example:
603
+ To check if a specific date is the first day of the month:
604
+ >>> Date(2023, 8, 11).get_first_day_of_month()
605
+ Date(2023, 8, 1)
606
+
607
+ To calculate the first day of the month with business days and holidays using a specific calendar:
608
+ >>> DateTime(2023, 8, 11, 12, 0).get_first_day_of_month(bizdays=True)
609
+ DateTime(2023, 8, 1, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC'))
610
+ """
611
+ first_day = self.replace(day=1)
612
+
613
+ if bizdays:
614
+ while first_day.weekday() in (SUNDAY, SATURDAY):
615
+ first_day += timedelta(days=1)
616
+
617
+ if calendar:
618
+ hdays = get_holidays(calendar, years=[self.year])
619
+
620
+ while first_day.date() in hdays or (bizdays and first_day.weekday() in (SUNDAY, SATURDAY)):
621
+ first_day += timedelta(days=1)
622
+
623
+ return first_day
624
+
625
+ def get_first_day_of_quarter(self, bizdays: bool = False, calendar: str = None) -> Self:
626
+ """
627
+ Get the first day of the quarter for a given date, optionally considering business days and holidays.
628
+
629
+ This static method takes a Date or DateTime object and calculates the first day of the quarter for that date.
630
+ Optionally, it can also consider business days by excluding weekends and accounting for holidays.
631
+
632
+ Args:
633
+ bizdays (bool, optional): If True, consider business days by excluding weekends. Defaults to False.
634
+ calendar (str, optional): The name of the calendar to use for holiday calculation. If provided, holidays will be considered when checking for the last day of the year. Defaults to None.
635
+
636
+ Raises:
637
+ NotImplementedError: If an invalid `calendar` name is provided.
638
+
639
+ Returns:
640
+ Date or DateTime: The first day of the quarter for the input date, considering business days and holidays if specified.
641
+
642
+ Example:
643
+ To check if a specific date is the first day of the quarter:
644
+ >>> Date(2023, 8, 11).get_first_day_of_quarter()
645
+ Date(2023, 7, 1)
646
+
647
+ To calculate the first day of the quarter with business days and holidays using a specific calendar:
648
+ >>> DateTime(2023, 8, 11, 12, 0).get_first_day_of_quarter(bizdays=True)
649
+ DateTime(2023, 7, 3, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC'))
650
+ """
651
+ first_month_of_quarter = (self.quarter - 1) * 3 + 1
652
+
653
+ first_day = self.replace(month=first_month_of_quarter, day=1)
654
+
655
+ first_day = first_day.get_first_day_of_month(bizdays=bizdays, calendar=calendar)
656
+
657
+ return first_day
658
+
659
+ def get_first_day_of_year(self, bizdays: bool = False, calendar: str = None) -> Self:
660
+ """
661
+ Get the first day of the year for a given date, optionally considering business days and holidays.
662
+
663
+ This static method takes a Date or DateTime object and calculates the first day of the year for that date.
664
+ Optionally, it can also consider business days by excluding weekends and accounting for holidays.
665
+
666
+ Args:
667
+ bizdays (bool, optional): If True, consider business days by excluding weekends. Defaults to False.
668
+ calendar (str, optional): The name of the calendar to use for holiday calculation. If provided, holidays will be considered when checking for the last day of the year. Defaults to None.
669
+
670
+ Raises:
671
+ NotImplementedError: If an invalid `calendar` name is provided.
672
+
673
+ Returns:
674
+ Date or DateTime: The first day of the year for the input date, considering business days and holidays if specified.
675
+
676
+ Example:
677
+ To check if a specific date is the first day of the year:
678
+ >>> Date(2023, 8, 11).get_first_day_of_year()
679
+ Date(2023, 1, 1)
680
+
681
+ To calculate the first day of the year with business days and holidays using a specific calendar:
682
+ >>> DateTime(2023, 8, 11, 12, 0).get_first_day_of_year(bizdays=True)
683
+ DateTime(2023, 1, 2, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC'))
684
+ """
685
+ first_day = self.replace(month=1, day=1)
686
+
687
+ first_day = first_day.get_first_day_of_month(bizdays=bizdays, calendar=calendar)
688
+
689
+ return first_day
690
+
691
+ ####################################
692
+ ### IS FIRST DATE OF
693
+ ####################################
694
+
695
+ def is_first_day_of_week(self, bizdays: bool = False, calendar: str = None) -> bool:
696
+ """
697
+ Check if a provided date corresponds to the first day of the week, optionally considering business days and holidays.
698
+
699
+ This method compares the provided `Date` or `DateTime` object with the first day of the week (Sunday) and returns True if they are the same.
700
+ Optionally, it can also consider business days by excluding weekends and accounting for holidays.
701
+
702
+ Args:
703
+ bizdays (bool, optional): If True, consider business days by excluding weekends. Defaults to False.
704
+ calendar (str, optional): The name of the calendar to use for holiday calculation. If provided, holidays will be considered when checking for the last day of the year. Defaults to None.
705
+
706
+ Raises:
707
+ NotImplementedError: If an invalid `calendar` name is provided.
708
+
709
+ Returns:
710
+ bool: True if the `Date or DateTime` is the same as the first day of the week, False otherwise.
711
+
712
+ Example:
713
+ To check if a specific date is the first day of the week:
714
+ >>> Date(2023, 8, 13).is_first_day_of_week()
715
+ True
716
+
717
+ To check if the current date is the first day of the week with business days and holidays using a specific calendar:
718
+ >>> DateTime(2023, 8, 13, 12, 0).is_first_day_of_week(bizdays=True)
719
+ False
720
+ """
721
+ return self == self.get_first_day_of_week(bizdays=bizdays, calendar=calendar)
722
+
723
+ def is_first_day_of_month(self, bizdays: bool = False, calendar: str = None) -> bool:
724
+ """
725
+ Check if a provided date corresponds to the first day of the month, optionally considering business days and holidays.
726
+
727
+ This method compares the provided `Date` or `DateTime` object with the first day of the month and returns True if they are the same.
728
+ Optionally, it can also consider business days by excluding weekends and accounting for holidays.
729
+
730
+ Args:
731
+ bizdays (bool, optional): If True, consider business days by excluding weekends. Defaults to False.
732
+ calendar (str, optional): The name of the calendar to use for holiday calculation. If provided, holidays will be considered when checking for the last day of the year. Defaults to None.
733
+
734
+ Raises:
735
+ NotImplementedError: If an invalid `calendar` name is provided.
736
+
737
+ Returns:
738
+ bool: True if the `Date or DateTime` is the same as the first day of the month, False otherwise.
739
+
740
+ Example:
741
+ To check if a specific date is the first day of the month:
742
+ >>> Date(2023, 8, 1).is_first_day_of_month()
743
+ True
744
+
745
+ To check if the current date is the first day of the month with business days and holidays using a specific calendar:
746
+ >>> DateTime(2023, 8, 1, 12, 0).is_first_day_of_month(bizdays=True)
747
+ True
748
+ """
749
+ return self == self.get_first_day_of_month(bizdays=bizdays, calendar=calendar)
750
+
751
+ def is_first_day_of_quarter(self, bizdays: bool = False, calendar: str = None) -> bool:
752
+ """
753
+ Check if a provided date corresponds to the first day of the quarter, optionally considering business days and holidays.
754
+
755
+ This method compares the provided `Date` or `DateTime` object with the first day of the quarter and returns True if they are the same.
756
+ Optionally, it can also consider business days by excluding weekends and accounting for holidays.
757
+
758
+ Args:
759
+ bizdays (bool, optional): If True, consider business days by excluding weekends. Defaults to False.
760
+ calendar (str, optional): The name of the calendar to use for holiday calculation. If provided, holidays will be considered when checking for the last day of the year. Defaults to None.
761
+
762
+ Raises:
763
+ NotImplementedError: If an invalid `calendar` name is provided.
764
+
765
+ Returns:
766
+ bool: True if the `Date or DateTime` is the same as the first day of the quarter, False otherwise.
767
+
768
+ Example:
769
+ To check if a specific date is the first day of the quarter:
770
+ >>> Date(2023, 7, 1).is_first_day_of_quarter()
771
+ True
772
+
773
+ To check if the current date is the first day of the quarter with business days and holidays using a specific calendar:
774
+ >>> DateTime(2023, 7, 1, 12, 0).is_first_day_of_quarter(bizdays=True)
775
+ True
776
+ """
777
+ return self == self.get_first_day_of_quarter(bizdays=bizdays, calendar=calendar)
778
+
779
+ def is_first_day_of_year(self, bizdays: bool = False, calendar: str = None) -> bool:
780
+ """
781
+ Check if a provided date corresponds to the first day of the year, optionally considering business days and holidays.
782
+
783
+ This method compares the provided `Date` or `DateTime` object with the first day of the year and returns True if they are the same.
784
+ Optionally, it can also consider business days by excluding weekends and accounting for holidays.
785
+
786
+ Args:
787
+ bizdays (bool, optional): If True, consider business days by excluding weekends. Defaults to False.
788
+ calendar (str, optional): The name of the calendar to use for holiday calculation. If provided, holidays will be considered when checking for the last day of the year. Defaults to None.
789
+
790
+ Raises:
791
+ NotImplementedError: If an invalid `calendar` name is provided.
792
+
793
+ Returns:
794
+ bool: True if the `Date or DateTime` is the same as the first day of the year, False otherwise.
795
+
796
+ Example:
797
+ To check if a specific date is the first day of the year:
798
+ >>> Date(2023, 1, 1).is_first_day_of_year()
799
+ True
800
+
801
+ To check if the current date is the first day of the year with business days and holidays using a specific calendar:
802
+ >>> DateTime(2023, 1, 1, 12, 0).is_first_day_of_year(bizdays=True)
803
+ False
804
+ """
805
+ return self == self.get_first_day_of_year(bizdays=bizdays, calendar=calendar)
806
+
807
+ ####################################
808
+ ### DATE DELTA
809
+ ####################################
810
+
811
+ def delta(self, period: int | float, periodicity: str = 'D', calendar: str = None) -> Self:
812
+ """
813
+ Calculate a new Date or DateTime object by adding or subtracting a specified time period.
814
+
815
+ This method calculates a new Date or DateTime object by adding or subtracting a specified time period based on the given periodicity.
816
+ The valid periodicity options are 'D' (days), 'B' (business days), 'W' (weeks), 'M' (months), and 'Y' (years).
817
+ It can also consider holidays based on the provided holiday calendar.
818
+
819
+ Args:
820
+ period (int | float): The number of time periods to add or subtract. A positive value adds, and a negative value subtracts.
821
+ periodicity (str, optional): The periodicity of the time period. Valid options are 'D' (days), 'B' (business days), 'W' (weeks), 'M' (months), and 'Y' (years). Defaults to 'D'.
822
+ calendar (str, optional): The name of the holiday calendar to use for considering holidays. Defaults to None.
823
+
824
+ Raises:
825
+ ValueError: If an invalid periodicity is provided.
826
+
827
+ Returns:
828
+ Date or DateTime: A new Date or DateTime object resulting from the addition or subtraction of the specified time period.
829
+
830
+ Example:
831
+ To calculate a new Date by adding 7 days:
832
+ >>> Date(2023, 7, 1).delta(7, periodicity='D')
833
+ Date(2023, 7, 8)
834
+
835
+ To calculate a new DateTime by subtracting 3 business days using a specific calendar:
836
+ >>> DateTime(2023, 7, 1, 12, 0).delta(-3, periodicity='B')
837
+ DateTime(2023, 6, 28, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC')
838
+ """
839
+ new_date = None
840
+
841
+ if periodicity == 'D':
842
+ new_date = self.days_delta(period)
843
+ elif periodicity == 'B':
844
+ new_date = self.bizdays_delta(period, calendar=calendar)
845
+ elif periodicity == 'W':
846
+ new_date = self.weeks_delta(period)
847
+ elif periodicity == 'M':
848
+ new_date = self.months_delta(period)
849
+ elif periodicity == 'Y':
850
+ new_date = self.years_delta(period)
851
+ else:
852
+ raise ValueError("Invalid periodicity. Please choose one of the following: D, B, W, M, Y.")
853
+
854
+ return new_date
855
+
856
+ def days_delta(self, days: int | float) -> Self:
857
+ """
858
+ Calculate a new Date or DateTime object by adding or subtracting a specified number of days.
859
+
860
+ This method calculates a new Date or DateTime object by adding or subtracting a specified number of days.
861
+
862
+ Args:
863
+ days (int | float): The number of days to add or subtract. A positive value adds, and a negative value subtracts.
864
+
865
+ Raises:
866
+ ValueError: If the input value `days` is greater than 50,000, indicating it's out of the valid range.
867
+
868
+ Returns:
869
+ Date or DateTime: A new Date or DateTime object resulting from the addition or subtraction of the specified number of days.
870
+
871
+ Example:
872
+ To calculate a new Date by adding 7 days:
873
+ >>> Date(2023, 7, 1).days_delta(7)
874
+ Date(2023, 7, 8)
875
+
876
+ To calculate a new DateTime by subtracting 3 days:
877
+ >>> DateTime(2023, 7, 1, 12, 0).days_delta(-3)
878
+ DateTime(2023, 6, 28, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC'))
879
+ """
880
+ if days > date_settings.MAX_DAYS_RANGE:
881
+ raise ValueError('Value out of range')
882
+
883
+ new_date = self + timedelta(days=days)
884
+ return new_date
885
+
886
+ def bizdays_delta(self, bizdays: int | float, calendar: str = None) -> Self:
887
+ """
888
+ Calculate a new Date or DateTime object by adding or subtracting a specified number of business days.
889
+
890
+ This method calculates a new Date or DateTime object by adding or subtracting a specified number of business days.
891
+ Business days exclude weekends (Saturdays and Sundays) and optionally specified holidays based on the provided calendar.
892
+
893
+ Args:
894
+ bizdays (int | float): The number of business days to add or subtract. A positive value adds, and a negative value subtracts.
895
+ calendar (str, optional): An optional calendar name specifying holidays to be considered when calculating business days. Defaults to None (no holiday consideration).
896
+
897
+ Returns:
898
+ Date or DateTime: A new Date or DateTime object resulting from the addition or subtraction of the specified number of business days.
899
+
900
+ Example:
901
+ To calculate a new Date by adding 5 business days:
902
+ >>> Date(2023, 7, 1).bizdays_delta(5)
903
+ Date(2023, 7, 7)
904
+
905
+ To calculate a new DateTime by subtracting 3 business days with a specific calendar:
906
+ >>> DateTime(2023, 7, 1, 12, 0).bizdays_delta(-3)
907
+ DateTime(2023, 6, 28, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC'))
908
+ """
909
+ direction = 1 if bizdays >= 0 else -1
910
+ bizdays = abs(bizdays)
911
+
912
+ if bizdays > date_settings.MAX_DAYS_RANGE:
913
+ raise ValueError('Value out of range')
914
+
915
+ count = 0
916
+ new_date = self
917
+
918
+ hdays = get_holidays(calendar)
919
+
920
+ while count < bizdays:
921
+ new_date += timedelta(days=direction)
922
+ if new_date.weekday() not in (SATURDAY, SUNDAY) and new_date.date() not in hdays:
923
+ count += 1
924
+
925
+ return new_date
926
+
927
+ def weeks_delta(self, weeks: int | float) -> Self:
928
+ """
929
+ Calculate a new Date or DateTime object by adding or subtracting a specified number of weeks.
930
+
931
+ This method calculates a new Date or DateTime object by adding or subtracting a specified number of weeks.
932
+
933
+ Args:
934
+ weeks (int | float): The number of weeks to add or subtract. A positive value adds, and a negative value subtracts.
935
+
936
+ Returns:
937
+ Date or DateTime: A new Date or DateTime object resulting from the addition or subtraction of the specified number of weeks.
938
+
939
+ Example:
940
+ To calculate a new Date by adding 3 weeks:
941
+ >>> Date(2023, 7, 1).weeks_delta(3)
942
+ Date(2023, 7, 22)
943
+
944
+ To calculate a new DateTime by subtracting 2 weeks:
945
+ >>> DateTime(2023, 7, 1, 12, 0).weeks_delta(-2)
946
+ DateTime(2023, 6, 17, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC'))
947
+ """
948
+ new_date = self + timedelta(weeks=weeks)
949
+ return new_date
950
+
951
+ def months_delta(self, months: int) -> Self:
952
+ """
953
+ Calculate a new Date or DateTime object by adding or subtracting a specified number of months.
954
+
955
+ This method calculates a new Date or DateTime object by adding or subtracting a specified number of months.
956
+ The day of the resulting date is adjusted to ensure it remains valid within the month, considering leap years.
957
+
958
+ Args:
959
+ months (int): The number of months to add or subtract. A positive value adds, and a negative value subtracts.
960
+
961
+ Returns:
962
+ Date or DateTime: A new Date or DateTime object resulting from the addition or subtraction of the specified number of months.
963
+
964
+ Example:
965
+ To calculate a new Date by adding 3 months:
966
+ >>> Date(2023, 7, 1).months_delta(3)
967
+ Date(2023, 10, 1)
968
+
969
+ To calculate a new DateTime by subtracting 2 months:
970
+ >>> DateTime(2023, 7, 1, 12, 0).months_delta(-2)
971
+ DateTime(2023, 5, 1, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC'))
972
+ """
973
+ months = int(months)
974
+
975
+ month = self.month + months - 1
976
+ year = self.year + month // 12
977
+ month = month % 12 + 1
978
+
979
+ days_of_month = monthrange(year, month)[1]
980
+ day = min(self.day, days_of_month)
981
+
982
+ end_date = self.replace(year=year, month=month, day=day)
983
+ return end_date
984
+
985
+ def years_delta(self, years: int) -> Self:
986
+ """
987
+ Calculate a new Date object by adding or subtracting a specified number of years.
988
+
989
+ This method calculates a new Date object by adding or subtracting a specified number of years.
990
+ It ensures that the day and month of the resulting date remain valid within the year, considering leap years.
991
+
992
+ Args:
993
+ years (int): The number of years to add or subtract. A positive value adds, and a negative value subtracts.
994
+
995
+ Returns:
996
+ Date or DateTime: A new Date or DateTime object resulting from the addition or subtraction of the specified number of years.
997
+
998
+ Example:
999
+ To calculate a new Date by adding 2 years:
1000
+ >>> Date(2023, 7, 15).years_delta(2)
1001
+ Date(2025, 7, 15)
1002
+
1003
+ To calculate a new Date by subtracting 5 years:
1004
+ >>> Date(2023, 7, 15).years_delta(-5)
1005
+ Date(2018, 7, 15)
1006
+ """
1007
+ years = int(years)
1008
+
1009
+ year = self.year + years
1010
+ month = self.month
1011
+ day = self.day
1012
+
1013
+ days_of_month = monthrange(year, month)[1]
1014
+ day = min(self.day, days_of_month)
1015
+
1016
+ end_date = self.replace(year=year, month=month, day=day)
1017
+
1018
+ return end_date
1019
+
1020
+ ####################################
1021
+ ### IS BUSINESS DAY
1022
+ ####################################
1023
+
1024
+ def is_business_day(self, calendar: str = None)-> bool:
1025
+ """
1026
+ Checks if the date is a business day, considering weekends and optional holidays.
1027
+
1028
+ Args:
1029
+ calendar (str, optional):
1030
+ The name of the calendar used to determine holidays.
1031
+ If None, only weekends are considered.
1032
+
1033
+ Returns:
1034
+ bool:
1035
+ True if the date is a business day, False otherwise.
1036
+ """
1037
+ if self.weekday() in (SATURDAY, SUNDAY):
1038
+ return False
1039
+
1040
+ hdays = get_holidays(calendar, [self.year])
1041
+
1042
+ return not self.date() in hdays
1043
+
1044
+ ####################################
1045
+ ### NEAREST BUSINESS DAY
1046
+ ####################################
1047
+
1048
+ def nearest_business_day(self, direction: str = "following", calendar: str = None) -> Self:
1049
+ """
1050
+ Get the nearest business day for a given reference date, considering weekends and optional holidays.
1051
+
1052
+ Args:
1053
+ reference_date (Date or DateTime):
1054
+ The date from which to find the nearest business day.
1055
+ direction (str):
1056
+ Determines whether to find the next ('following') or previous ('preceding') business day.
1057
+ Defaults to 'following'.
1058
+ calendar (str, optional):
1059
+ A calendar name used to determine holidays. Defaults to None, which means no holidays are considered.
1060
+
1061
+ Raises:
1062
+ ValueError: If an invalid `direction` is provided.
1063
+
1064
+ Returns:
1065
+ Date or DateTime:
1066
+ The nearest business day for the given reference date.
1067
+ """
1068
+ if direction not in ("following", "preceding"):
1069
+ raise ValueError("Invalid direction. Choose 'following' or 'preceding'.")
1070
+
1071
+ nearest_day = self
1072
+
1073
+ if nearest_day.is_business_day(calendar):
1074
+ return nearest_day
1075
+
1076
+ delta = 1 if direction == "following" else -1
1077
+ return nearest_day.bizdays_delta(bizdays=delta, calendar=calendar)
1078
+
1079
+ ####################################
1080
+ ### DATES DIFF
1081
+ ####################################
1082
+
1083
+ @classmethod
1084
+ def diff(cls, start_date: Union['Date', 'DateTime', date, datetime], end_date: Union['Date', 'DateTime', date, datetime], periodicity: str = 'D', calendar: str = None) -> int:
1085
+ """
1086
+ Calculate the difference in periods (days, business days, weeks, months, or years) between two dates.
1087
+
1088
+ This class method calculates the difference in periods (days, business days, weeks, months, or years)
1089
+ between two dates. It provides flexibility in choosing the periodicity of the difference calculation.
1090
+
1091
+ Args:
1092
+ start_date (Union[Date, DateTime, date, datetime]): The starting date for the difference calculation.
1093
+ end_date (Union[Date, DateTime, date, datetime]): The ending date for the difference calculation.
1094
+ periodicity (str, optional): The periodicity of the difference calculation. Possible values: 'D', 'B', 'W', 'M', 'Y'. 'D' (default) calculates the difference in days.
1095
+ calendar (str, optional): The name of the calendar to use for business day calculations. Defaults to None.
1096
+
1097
+ Raises:
1098
+ ValueError: If an invalid `periodicity` value is provided. Valid values are 'D', 'B', 'W', 'M', or 'Y'.
1099
+
1100
+ Returns:
1101
+ int: The difference in periods between the two dates, based on the specified periodicity.
1102
+
1103
+ Example:
1104
+ To calculate the difference in months between two Date objects:
1105
+ >>> start_date = Date(2023, 1, 15)
1106
+ >>> end_date = Date(2023, 5, 10)
1107
+ >>> Date.diff(start_date, end_date, periodicity='M')
1108
+ 3
1109
+
1110
+ To calculate the difference in business days between two DateTime objects using a specific calendar:
1111
+ >>> start_date = DateTime(2023, 7, 15, 12, 0)
1112
+ >>> end_date = DateTime(2023, 7, 20, 12, 0)
1113
+ >>> DateTime.diff(start_date, end_date, periodicity='B')
1114
+ 4
1115
+ """
1116
+ start_date, end_date = cls.ensure(start_date), cls.ensure(end_date)
1117
+
1118
+ if start_date == end_date:
1119
+ return 0
1120
+
1121
+ if periodicity == 'D':
1122
+ diff = cls.days_diff(start_date, end_date)
1123
+ elif periodicity == 'B':
1124
+ diff = cls.bizdays_diff(start_date, end_date, calendar=calendar)
1125
+ elif periodicity == 'W':
1126
+ diff = cls.weeks_diff(start_date, end_date)
1127
+ elif periodicity == 'M':
1128
+ diff = cls.months_diff(start_date, end_date)
1129
+ elif periodicity == 'Y':
1130
+ diff = cls.years_diff(start_date, end_date)
1131
+ else:
1132
+ raise ValueError("Invalid periodicity. Please choose one of the following: D, B, W, M, Y.")
1133
+
1134
+ return diff
1135
+
1136
+ @classmethod
1137
+ def days_diff(cls, start_date: Union[date, datetime, 'Date', 'DateTime'], end_date: Union[date, datetime, 'Date', 'DateTime']) -> int:
1138
+ """
1139
+ Calculate the difference in days between two dates.
1140
+
1141
+ This class method calculates the difference in days between two dates. It considers the order of the dates and
1142
+ returns a positive or negative integer accordingly.
1143
+
1144
+ Args:
1145
+ start_date (Union[date, datetime, 'Date', 'DateTime']): The starting date for the difference calculation.
1146
+ end_date (Union[date, datetime, 'Date', 'DateTime']): The ending date for the difference calculation.
1147
+
1148
+ Returns:
1149
+ int: The difference in days between the two dates. A positive value if end_date is greater than start_date, and a negative value if start_date is greater than end_date.
1150
+
1151
+ Example:
1152
+ To calculate the difference in days between two Date objects:
1153
+ >>> start_date = Date(2023, 1, 15)
1154
+ >>> end_date = Date(2023, 5, 10)
1155
+ >>> Date.days_diff(start_date, end_date)
1156
+ 115
1157
+
1158
+ To calculate the difference in days between two DateTime objects:
1159
+ >>> start_date = DateTime(2023, 7, 15, 12, 0)
1160
+ >>> end_date = DateTime(2023, 7, 20, 12, 0)
1161
+ >>> DateTime.days_diff(start_date, end_date)
1162
+ 5
1163
+ """
1164
+ start_date, end_date = cls.ensure(start_date), cls.ensure(end_date)
1165
+ start_date, end_date, multiplier = cls._adjust_direction(start_date, end_date)
1166
+
1167
+ days_diff = (end_date - start_date).days
1168
+
1169
+ return days_diff * multiplier
1170
+
1171
+ @classmethod
1172
+ def bizdays_diff(cls, start_date: Union[date, datetime, 'Date', 'DateTime'], end_date: Union[date, datetime, 'Date', 'DateTime'], calendar: str = None) -> int:
1173
+ """
1174
+ Calculate the difference in business days between two dates.
1175
+
1176
+ This class method calculates the difference in business days (weekdays excluding Saturdays and Sundays)
1177
+ between two dates. It considers the order of the dates and returns a positive or negative integer accordingly.
1178
+ Additionally, it can take into account a custom holiday calendar to exclude holidays from the calculation.
1179
+
1180
+ Args:
1181
+ start_date (Union[date, datetime, 'Date', 'DateTime']): The starting date for the difference calculation.
1182
+ end_date (Union[date, datetime, 'Date', 'DateTime']): The ending date for the difference calculation.
1183
+ calendar (str, optional): The name of the holiday calendar to use for excluding holidays. Defaults to None.
1184
+
1185
+ Returns:
1186
+ int: The difference in business days between the two dates. A positive value if end_date is greater than
1187
+ start_date, and a negative value if start_date is greater than end_date.
1188
+
1189
+ Example:
1190
+ To calculate the difference in business days between two Date objects:
1191
+ >>> start_date = Date(2023, 1, 15)
1192
+ >>> end_date = Date(2023, 1, 20)
1193
+ >>> Date.bizdays_diff(start_date, end_date)
1194
+ 4
1195
+
1196
+ To calculate the difference in business days between two DateTime objects with a custom holiday calendar:
1197
+ >>> start_date = DateTime(2023, 7, 15, 12, 0)
1198
+ >>> end_date = DateTime(2023, 7, 20, 12, 0)
1199
+ >>> DateTime.bizdays_diff(start_date, end_date)
1200
+ 4
1201
+ """
1202
+ start_date, end_date = cls.ensure(start_date), cls.ensure(end_date)
1203
+ start_date, end_date, multiplier = cls._adjust_direction(start_date, end_date)
1204
+
1205
+ bdays_diff = 0
1206
+
1207
+ # We need to add one day here to correct calculate the difference
1208
+ # https://everysk.atlassian.net/browse/COD-3913
1209
+ current = start_date + timedelta(days=1)
1210
+ hdays = get_holidays(calendar)
1211
+
1212
+ while current <= end_date:
1213
+ if current.weekday() not in (SATURDAY, SUNDAY) and current.date() not in hdays:
1214
+ bdays_diff += 1
1215
+ current += timedelta(days=1)
1216
+
1217
+ return bdays_diff * multiplier
1218
+
1219
+ @classmethod
1220
+ def weeks_diff(cls, start_date: Union[date, datetime, 'Date', 'DateTime'], end_date: Union[date, datetime, 'Date', 'DateTime']) -> int:
1221
+ """
1222
+ Calculate the difference in weeks between two dates.
1223
+
1224
+ This class method calculates the difference in weeks between two dates. It considers the order of the dates
1225
+ and returns a positive or negative integer accordingly.
1226
+
1227
+ Args:
1228
+ start_date (Union[date, datetime, 'Date', 'DateTime']): The starting date for the difference calculation.
1229
+ end_date (Union[date, datetime, 'Date', 'DateTime']): The ending date for the difference calculation.
1230
+
1231
+ Returns:
1232
+ int: The difference in weeks between the two dates. A positive value if end_date is greater than
1233
+ start_date, and a negative value if start_date is greater than end_date.
1234
+
1235
+ Example:
1236
+ To calculate the difference in weeks between two Date objects:
1237
+ >>> start_date = Date(2023, 1, 1)
1238
+ >>> end_date = Date(2023, 1, 21)
1239
+ >>> Date.weeks_diff(start_date, end_date)
1240
+ 2
1241
+
1242
+ To calculate the difference in weeks between two DateTime objects:
1243
+ >>> start_date = DateTime(2023, 7, 1, 12, 0)
1244
+ >>> end_date = DateTime(2023, 7, 15, 12, 0)
1245
+ >>> DateTime.weeks_diff(start_date, end_date)
1246
+ 2
1247
+ """
1248
+ start_date, end_date = cls.ensure(start_date), cls.ensure(end_date)
1249
+ start_date, end_date, multiplier = cls._adjust_direction(start_date, end_date)
1250
+
1251
+ weeks_diff = (end_date - start_date).days // 7
1252
+
1253
+ return weeks_diff * multiplier
1254
+
1255
+ @classmethod
1256
+ def months_diff(cls, start_date: Union[date, datetime, 'Date', 'DateTime'], end_date: Union[date, datetime, 'Date', 'DateTime']) -> int:
1257
+ """
1258
+ Calculate the difference in months between two dates.
1259
+
1260
+ This class method calculates the difference in months between two dates. It considers the order of the dates
1261
+ and returns a positive or negative integer accordingly.
1262
+
1263
+ Args:
1264
+ start_date (Union[date, datetime, 'Date', 'DateTime']): The starting date for the difference calculation.
1265
+ end_date (Union[date, datetime, 'Date', 'DateTime']): The ending date for the difference calculation.
1266
+
1267
+ Returns:
1268
+ int: The difference in months between the two dates. A positive value if end_date is greater than start_date, and a negative value if start_date is greater than end_date.
1269
+
1270
+ Example:
1271
+ To calculate the difference in months between two Date objects:
1272
+ >>> start_date = Date(2023, 1, 1)
1273
+ >>> end_date = Date(2023, 3, 15)
1274
+ >>> Date.months_diff(start_date, end_date)
1275
+ 2
1276
+
1277
+ To calculate the difference in months between two DateTime objects:
1278
+ >>> start_date = DateTime(2023, 7, 1, 12, 0)
1279
+ >>> end_date = DateTime(2024, 3, 15, 12, 0)
1280
+ >>> DateTime.months_diff(start_date, end_date)
1281
+ 8
1282
+ """
1283
+ start_date, end_date = cls.ensure(start_date), cls.ensure(end_date)
1284
+ start_date, end_date, multiplier = cls._adjust_direction(start_date, end_date)
1285
+
1286
+ months_diff = (end_date.year - start_date.year) * 12 + (end_date.month - start_date.month)
1287
+
1288
+ if end_date.day < start_date.day:
1289
+ months_diff -= 1
1290
+
1291
+ return months_diff * multiplier
1292
+
1293
+ @classmethod
1294
+ def years_diff(cls, start_date: Union[date, datetime, 'Date', 'DateTime'], end_date: Union[date, datetime, 'Date', 'DateTime']) -> int:
1295
+ """
1296
+ Calculate the difference in years between two dates.
1297
+
1298
+ This class method calculates the difference in years between two dates. It considers the order of the dates
1299
+ and returns a positive or negative integer accordingly.
1300
+
1301
+ Args:
1302
+ start_date (Union[date, datetime, 'Date', 'DateTime']): The starting date for the difference calculation.
1303
+ end_date (Union[date, datetime, 'Date', 'DateTime']): The ending date for the difference calculation.
1304
+
1305
+ Returns:
1306
+ int: The difference in years between the two dates. A positive value if end_date is greater than
1307
+ start_date, and a negative value if start_date is greater than end_date.
1308
+
1309
+ Example:
1310
+ To calculate the difference in years between two Date objects:
1311
+ >>> start_date = Date(2020, 1, 1)
1312
+ >>> end_date = Date(2023, 12, 31)
1313
+ >>> Date.years_diff(start_date, end_date)
1314
+ 3
1315
+
1316
+ To calculate the difference in years between two DateTime objects:
1317
+ >>> start_date = DateTime(2010, 7, 1, 12, 0)
1318
+ >>> end_date = DateTime(2022, 3, 15, 12, 0)
1319
+ >>> DateTime.years_diff(start_date, end_date)
1320
+ 11
1321
+ """
1322
+ start_date, end_date = cls.ensure(start_date), cls.ensure(end_date)
1323
+ start_date, end_date, multiplier = cls._adjust_direction(start_date, end_date)
1324
+
1325
+ years_diff = end_date.year - start_date.year
1326
+
1327
+ if end_date.month < start_date.month or (end_date.month == start_date.month and end_date.day < start_date.day):
1328
+ years_diff -= 1
1329
+
1330
+ return years_diff * multiplier
1331
+
1332
+ ####################################
1333
+ ### DATES RANGE
1334
+ ####################################
1335
+
1336
+ @classmethod
1337
+ def range(cls, start_date: Union[date, datetime, 'Date', 'DateTime'], end_date: Union[date, datetime, 'Date', 'DateTime'], periodicity: str = 'D', calendar: str = None) -> list[Self]:
1338
+ """
1339
+ Generate a range of dates between two dates based on the specified periodicity.
1340
+
1341
+ This class method generates a list of dates between a start date and an end date, based on the specified
1342
+ periodicity. The generated list includes both the start and end dates.
1343
+
1344
+ Args:
1345
+ start_date (Union[date, datetime, 'Date', 'DateTime']): The starting date of the range.
1346
+ end_date (Union[date, datetime, 'Date', 'DateTime']): The ending date of the range.
1347
+ periodicity (str, optional): The periodicity of the date range. Options include 'D' (daily) and 'B' (business days). Defaults to 'D'.
1348
+ calendar (str, optional): The name of the calendar to use for business day calculations. If None, no holidays are considered. Defaults to None.
1349
+
1350
+ Raises:
1351
+ ValueError: If an invalid periodicity is provided or if the date range exceeds 30000 days in length.
1352
+
1353
+ Returns:
1354
+ list[Self]: A list of Date or DateTime objects representing the date range.
1355
+
1356
+ Example:
1357
+ To generate a daily date range between two Date objects:
1358
+ >>> start_date = Date(2023, 1, 1)
1359
+ >>> end_date = Date(2023, 1, 5)
1360
+ >>> Date.range(start_date, end_date)
1361
+ [Date(2023, 1, 1), Date(2023, 1, 2), Date(2023, 1, 3), Date(2023, 1, 4)]
1362
+
1363
+ To generate a business day date range between two DateTime objects:
1364
+ >>> start_date = DateTime(2023, 1, 1, 12, 0)
1365
+ >>> end_date = DateTime(2023, 1, 7, 12, 0)
1366
+ >>> DateTime.range(start_date, end_date, periodicity='B')
1367
+ [DateTime(2023, 1, 2, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC')),
1368
+ DateTime(2023, 1, 3, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC')),
1369
+ DateTime(2023, 1, 4, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC')),
1370
+ DateTime(2023, 1, 5, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC')),
1371
+ DateTime(2023, 1, 6, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC'))]
1372
+ """
1373
+ start_date, end_date = cls.ensure(start_date), cls.ensure(end_date)
1374
+
1375
+ if start_date == end_date:
1376
+ return []
1377
+
1378
+ if abs(end_date - start_date).days > date_settings.MAX_DAYS_RANGE:
1379
+ raise ValueError('Value out of range')
1380
+
1381
+ dates = None
1382
+
1383
+ if periodicity == 'D':
1384
+ dates = cls.days_range(start_date, end_date)
1385
+ elif periodicity == 'B':
1386
+ dates = cls.bizdays_range(start_date, end_date, calendar=calendar)
1387
+ else:
1388
+ raise ValueError("Invalid periodicity. Please choose one of the following: D, B.")
1389
+
1390
+ return dates
1391
+
1392
+ @classmethod
1393
+ def days_range(cls, start_date: Union[date, datetime, 'Date', 'DateTime'], end_date: Union[date, datetime, 'Date', 'DateTime']) -> list[Self]:
1394
+ """
1395
+ Generate a daily date range between two dates.
1396
+
1397
+ This class method generates a list of daily dates between a start date (inclusive) and an end date (exclusive).
1398
+
1399
+ Args:
1400
+ start_date (Union[date, datetime, 'Date', 'DateTime']): The starting date of the range.
1401
+ end_date (Union[date, datetime, 'Date', 'DateTime']): The ending date of the range.
1402
+
1403
+ Returns:
1404
+ list[Date or DateTime]: A list of Date or DateTime objects representing the daily date range.
1405
+
1406
+ Example:
1407
+ To generate a daily date range between two Date objects:
1408
+ >>> start_date = Date(2023, 1, 1)
1409
+ >>> end_date = Date(2023, 1, 5)
1410
+ >>> Date.days_range(start_date, end_date)
1411
+ [Date(2023, 1, 1), Date(2023, 1, 2), Date(2023, 1, 3), Date(2023, 1, 4)]
1412
+
1413
+ To generate a daily date range between two DateTime objects:
1414
+ >>> start_date = DateTime(2023, 1, 1, 12, 0)
1415
+ >>> end_date = DateTime(2023, 1, 5, 12, 0)
1416
+ >>> DateTime.days_range(start_date, end_date)
1417
+ [DateTime(2023, 1, 1, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC')),
1418
+ DateTime(2023, 1, 2, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC')),
1419
+ DateTime(2023, 1, 3, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC')),
1420
+ DateTime(2023, 1, 4, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC'))]
1421
+ """
1422
+ start_date, end_date = cls.ensure(start_date), cls.ensure(end_date)
1423
+
1424
+ dates = []
1425
+
1426
+ while start_date < end_date:
1427
+ dates.append(start_date)
1428
+ start_date += timedelta(days=1)
1429
+
1430
+ return dates
1431
+
1432
+ @classmethod
1433
+ def bizdays_range(cls, start_date: Union[date, datetime, 'Date', 'DateTime'], end_date: Union[date, datetime, 'Date', 'DateTime'], calendar: str = None) -> list[Self]:
1434
+ """
1435
+ Generate a business days date range between two dates, excluding weekends and holidays.
1436
+
1437
+ This class method generates a list of business days (weekdays excluding Saturdays, Sundays, and specified holidays)
1438
+ between a start date (inclusive) and an end date (exclusive).
1439
+
1440
+ Args:
1441
+ start_date (Union[date, datetime, 'Date', 'DateTime']): The starting date of the range.
1442
+ end_date (Union[date, datetime, 'Date', 'DateTime']): The ending date of the range.
1443
+ calendar (str, optional): A calendar name specifying holidays to be excluded. Defaults to None.
1444
+
1445
+ Returns:
1446
+ list[Date or DateTime]: A list of Date or DateTime objects representing the business days date range.
1447
+
1448
+ Example:
1449
+ To generate a business days date range between two Date objects, excluding holidays:
1450
+ >>> start_date = Date(2023, 1, 1)
1451
+ >>> end_date = Date(2023, 1, 5)
1452
+ >>> Date.bizdays_range(start_date, end_date)
1453
+ [Date(2023, 1, 1), Date(2023, 1, 3), Date(2023, 1, 4)]
1454
+
1455
+ To generate a business days date range between two DateTime objects, excluding holidays:
1456
+ >>> start_date = DateTime(2023, 1, 1, 12, 0)s
1457
+ >>> end_date = DateTime(2023, 1, 5, 12, 0)
1458
+ >>> DateTime.bizdays_range(start_date, end_date)
1459
+ [DateTime(2023, 1, 2, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC')),
1460
+ DateTime(2023, 1, 3, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC')),
1461
+ DateTime(2023, 1, 4, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC'))]
1462
+ """
1463
+ start_date, end_date = cls.ensure(start_date), cls.ensure(end_date)
1464
+
1465
+ hdays = get_holidays(calendar)
1466
+
1467
+ dates = []
1468
+
1469
+ while start_date < end_date:
1470
+ if start_date.weekday() not in (SATURDAY, SUNDAY) and start_date.date() not in hdays:
1471
+ dates.append(start_date)
1472
+
1473
+ start_date += timedelta(days=1)
1474
+
1475
+ return dates