holidays 0.47__py3-none-any.whl → 0.49__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. holidays/__init__.py +1 -1
  2. holidays/calendars/gregorian.py +31 -7
  3. holidays/calendars/persian.py +3 -2
  4. holidays/calendars/thai.py +24 -20
  5. holidays/countries/__init__.py +1 -0
  6. holidays/countries/angola.py +2 -2
  7. holidays/countries/aruba.py +2 -3
  8. holidays/countries/australia.py +1 -3
  9. holidays/countries/belgium.py +1 -2
  10. holidays/countries/bolivia.py +1 -2
  11. holidays/countries/brazil.py +1 -2
  12. holidays/countries/cambodia.py +7 -8
  13. holidays/countries/canada.py +2 -1
  14. holidays/countries/chile.py +6 -6
  15. holidays/countries/colombia.py +1 -2
  16. holidays/countries/curacao.py +3 -4
  17. holidays/countries/cyprus.py +1 -2
  18. holidays/countries/denmark.py +1 -2
  19. holidays/countries/finland.py +3 -3
  20. holidays/countries/france.py +1 -2
  21. holidays/countries/greece.py +11 -6
  22. holidays/countries/hongkong.py +398 -133
  23. holidays/countries/israel.py +13 -13
  24. holidays/countries/italy.py +2 -4
  25. holidays/countries/japan.py +17 -6
  26. holidays/countries/jersey.py +2 -2
  27. holidays/countries/laos.py +7 -23
  28. holidays/countries/madagascar.py +2 -3
  29. holidays/countries/malaysia.py +545 -235
  30. holidays/countries/moldova.py +1 -2
  31. holidays/countries/netherlands.py +2 -3
  32. holidays/countries/new_zealand.py +10 -11
  33. holidays/countries/palau.py +127 -0
  34. holidays/countries/portugal.py +2 -6
  35. holidays/countries/saudi_arabia.py +2 -3
  36. holidays/countries/south_korea.py +18 -5
  37. holidays/countries/sweden.py +2 -3
  38. holidays/countries/switzerland.py +2 -3
  39. holidays/countries/timor_leste.py +23 -1
  40. holidays/countries/united_states.py +1 -1
  41. holidays/countries/uruguay.py +3 -4
  42. holidays/financial/__init__.py +1 -0
  43. holidays/financial/ice_futures_europe.py +47 -0
  44. holidays/financial/ny_stock_exchange.py +17 -4
  45. holidays/groups/chinese.py +2 -3
  46. holidays/groups/christian.py +18 -19
  47. holidays/groups/international.py +10 -0
  48. holidays/groups/islamic.py +2 -2
  49. holidays/groups/persian.py +2 -2
  50. holidays/helpers.py +9 -3
  51. holidays/holiday_base.py +133 -66
  52. holidays/locale/en_US/LC_MESSAGES/MY.mo +0 -0
  53. holidays/locale/en_US/LC_MESSAGES/MY.po +250 -0
  54. holidays/locale/ms_MY/LC_MESSAGES/MY.mo +0 -0
  55. holidays/locale/ms_MY/LC_MESSAGES/MY.po +250 -0
  56. holidays/mixins.py +31 -0
  57. holidays/observed_holiday_base.py +25 -13
  58. holidays/registry.py +2 -0
  59. {holidays-0.47.dist-info → holidays-0.49.dist-info}/METADATA +29 -21
  60. {holidays-0.47.dist-info → holidays-0.49.dist-info}/RECORD +64 -57
  61. {holidays-0.47.dist-info → holidays-0.49.dist-info}/AUTHORS +0 -0
  62. {holidays-0.47.dist-info → holidays-0.49.dist-info}/LICENSE +0 -0
  63. {holidays-0.47.dist-info → holidays-0.49.dist-info}/WHEEL +0 -0
  64. {holidays-0.47.dist-info → holidays-0.49.dist-info}/top_level.txt +0 -0
@@ -11,11 +11,10 @@
11
11
  # License: MIT (see LICENSE file)
12
12
 
13
13
  from datetime import date
14
- from datetime import timedelta as td
15
14
 
16
15
  from dateutil.easter import EASTER_ORTHODOX, EASTER_WESTERN, easter
17
16
 
18
- from holidays.calendars.gregorian import GREGORIAN_CALENDAR, JAN, DEC
17
+ from holidays.calendars.gregorian import GREGORIAN_CALENDAR, JAN, DEC, _timedelta
19
18
  from holidays.calendars.julian import JULIAN_CALENDAR
20
19
  from holidays.calendars.julian_revised import JULIAN_REVISED_CALENDAR
21
20
 
@@ -123,7 +122,7 @@ class ChristianHolidays:
123
122
  Day, or sometimes Holy Thursday.
124
123
  https://en.wikipedia.org/wiki/Feast_of_the_Ascension
125
124
  """
126
- return self._add_holiday(name, self._easter_sunday + td(days=+39))
125
+ return self._add_holiday(name, _timedelta(self._easter_sunday, +39))
127
126
 
128
127
  def _add_ash_monday(self, name) -> date:
129
128
  """
@@ -133,7 +132,7 @@ class ChristianHolidays:
133
132
  or Green Monday. The first day of Great Lent.
134
133
  https://en.wikipedia.org/wiki/Clean_Monday
135
134
  """
136
- return self._add_holiday(name, self._easter_sunday + td(days=-48))
135
+ return self._add_holiday(name, _timedelta(self._easter_sunday, -48))
137
136
 
138
137
  def _add_ash_wednesday(self, name) -> date:
139
138
  """
@@ -142,7 +141,7 @@ class ChristianHolidays:
142
141
  A holy day of prayer and fasting. It marks the beginning of Lent.
143
142
  https://en.wikipedia.org/wiki/Ash_Wednesday
144
143
  """
145
- return self._add_holiday(name, self._easter_sunday + td(days=-46))
144
+ return self._add_holiday(name, _timedelta(self._easter_sunday, -46))
146
145
 
147
146
  def _add_assumption_of_mary_day(self, name, calendar=None) -> date:
148
147
  """
@@ -182,7 +181,7 @@ class ChristianHolidays:
182
181
  the liturgical season of Lent.
183
182
  https://en.wikipedia.org/wiki/Carnival
184
183
  """
185
- return self._add_holiday(name, self._easter_sunday + td(days=-48))
184
+ return self._add_holiday(name, _timedelta(self._easter_sunday, -48))
186
185
 
187
186
  def _add_carnival_tuesday(self, name) -> date:
188
187
  """
@@ -192,7 +191,7 @@ class ChristianHolidays:
192
191
  the liturgical season of Lent.
193
192
  https://en.wikipedia.org/wiki/Carnival
194
193
  """
195
- return self._add_holiday(name, self._easter_sunday + td(days=-47))
194
+ return self._add_holiday(name, _timedelta(self._easter_sunday, -47))
196
195
 
197
196
  def _add_christmas_day(self, name, calendar=None) -> date:
198
197
  """
@@ -212,7 +211,7 @@ class ChristianHolidays:
212
211
  https://en.wikipedia.org/wiki/Boxing_Day
213
212
  https://en.wikipedia.org/wiki/Christmas
214
213
  """
215
- return self._add_holiday(name, self.__get_christmas_day(calendar) + td(days=+1))
214
+ return self._add_holiday(name, _timedelta(self.__get_christmas_day(calendar), +1))
216
215
 
217
216
  def _add_christmas_day_three(self, name, calendar=None) -> date:
218
217
  """
@@ -221,7 +220,7 @@ class ChristianHolidays:
221
220
  A holiday celebrated 2 days after Christmas Day (in some countries).
222
221
  https://en.wikipedia.org/wiki/Christmas
223
222
  """
224
- return self._add_holiday(name, self.__get_christmas_day(calendar) + td(days=+2))
223
+ return self._add_holiday(name, _timedelta(self.__get_christmas_day(calendar), +2))
225
224
 
226
225
  def _add_christmas_eve(self, name, calendar=None) -> date:
227
226
  """
@@ -231,7 +230,7 @@ class ChristianHolidays:
231
230
  the festival commemorating the birth of Jesus Christ.
232
231
  https://en.wikipedia.org/wiki/Christmas_Eve
233
232
  """
234
- return self._add_holiday(name, self.__get_christmas_day(calendar) + td(days=-1))
233
+ return self._add_holiday(name, _timedelta(self.__get_christmas_day(calendar), -1))
235
234
 
236
235
  def _add_corpus_christi_day(self, name) -> date:
237
236
  """
@@ -243,7 +242,7 @@ class ChristianHolidays:
243
242
  of Jesus Christ in the elements of the Eucharist.
244
243
  https://en.wikipedia.org/wiki/Feast_of_Corpus_Christi
245
244
  """
246
- return self._add_holiday(name, self._easter_sunday + td(days=+60))
245
+ return self._add_holiday(name, _timedelta(self._easter_sunday, +60))
247
246
 
248
247
  def _add_easter_monday(self, name, calendar=None) -> date:
249
248
  """
@@ -254,7 +253,7 @@ class ChristianHolidays:
254
253
  some countries.
255
254
  https://en.wikipedia.org/wiki/Easter_Monday
256
255
  """
257
- return self._add_holiday(name, self.__get_easter_sunday(calendar) + td(days=+1))
256
+ return self._add_holiday(name, _timedelta(self.__get_easter_sunday(calendar), +1))
258
257
 
259
258
  def _add_easter_sunday(self, name, calendar=None) -> date:
260
259
  """
@@ -294,7 +293,7 @@ class ChristianHolidays:
294
293
  Great Friday, Great and Holy Friday.
295
294
  https://en.wikipedia.org/wiki/Good_Friday
296
295
  """
297
- return self._add_holiday(name, self.__get_easter_sunday(calendar) + td(days=-2))
296
+ return self._add_holiday(name, _timedelta(self.__get_easter_sunday(calendar), -2))
298
297
 
299
298
  def _add_holy_saturday(self, name) -> date:
300
299
  """
@@ -303,7 +302,7 @@ class ChristianHolidays:
303
302
  Great and Holy Saturday is a day between Good Friday and Easter Sunday.
304
303
  https://en.wikipedia.org/wiki/Holy_Saturday
305
304
  """
306
- return self._add_holiday(name, self._easter_sunday + td(days=-1))
305
+ return self._add_holiday(name, _timedelta(self._easter_sunday, -1))
307
306
 
308
307
  def _add_holy_thursday(self, name) -> date:
309
308
  """
@@ -314,7 +313,7 @@ class ChristianHolidays:
314
313
  Jesus Christ with the Apostles, as described in the canonical gospels.
315
314
  https://en.wikipedia.org/wiki/Maundy_Thursday
316
315
  """
317
- return self._add_holiday(name, self._easter_sunday + td(days=-3))
316
+ return self._add_holiday(name, _timedelta(self._easter_sunday, -3))
318
317
 
319
318
  def _add_immaculate_conception_day(self, name) -> date:
320
319
  """
@@ -345,7 +344,7 @@ class ChristianHolidays:
345
344
  Palm Sunday marks the first day of Holy Week.
346
345
  https://en.wikipedia.org/wiki/Palm_Sunday
347
346
  """
348
- return self._add_holiday(name, self._easter_sunday + td(days=-7))
347
+ return self._add_holiday(name, _timedelta(self._easter_sunday, -7))
349
348
 
350
349
  def _add_rejoicing_day(self, name) -> date:
351
350
  """
@@ -356,7 +355,7 @@ class ChristianHolidays:
356
355
  Pascha (Easter).In Ukrainian tradition it is called Provody.
357
356
  https://en.wikipedia.org/wiki/Radonitsa
358
357
  """
359
- return self._add_holiday(name, self._easter_sunday + td(days=+9))
358
+ return self._add_holiday(name, _timedelta(self._easter_sunday, +9))
360
359
 
361
360
  def _add_saint_georges_day(self, name) -> date:
362
361
  """
@@ -418,7 +417,7 @@ class ChristianHolidays:
418
417
  https://en.wikipedia.org/wiki/Pentecost
419
418
  https://en.wikipedia.org/wiki/Whit_Monday
420
419
  """
421
- return self._add_holiday(name, self._easter_sunday + td(days=+50))
420
+ return self._add_holiday(name, _timedelta(self._easter_sunday, +50))
422
421
 
423
422
  def _add_whit_sunday(self, name) -> date:
424
423
  """
@@ -430,4 +429,4 @@ class ChristianHolidays:
430
429
  Feast of Weeks.
431
430
  https://en.wikipedia.org/wiki/Pentecost
432
431
  """
433
- return self._add_holiday(name, self._easter_sunday + td(days=+49))
432
+ return self._add_holiday(name, _timedelta(self._easter_sunday, +49))
@@ -194,3 +194,13 @@ class InternationalHolidays:
194
194
  https://en.wikipedia.org/wiki/Victory_Day_(9_May)
195
195
  """
196
196
  return self._add_holiday_may_9(name)
197
+
198
+ def _add_united_nations_day(self, name):
199
+ """
200
+ Add United Nations Day (Oct 24th)
201
+
202
+ United Nations Day is an annual commemorative day, reflecting the
203
+ official creation of the United Nations on 24 October 1945.
204
+ https://en.wikipedia.org/wiki/United_Nations_Day
205
+ """
206
+ return self._add_holiday_oct_24(name)
@@ -11,10 +11,10 @@
11
11
  # License: MIT (see LICENSE file)
12
12
 
13
13
  from datetime import date
14
- from datetime import timedelta as td
15
14
  from typing import Iterable, Set, Tuple
16
15
 
17
16
  from holidays.calendars import _IslamicLunar
17
+ from holidays.calendars.gregorian import _timedelta
18
18
 
19
19
 
20
20
  class IslamicHolidays:
@@ -264,7 +264,7 @@ class IslamicHolidays:
264
264
  estimated_label = getattr(self, "estimated_label", "%s (estimated)")
265
265
  for dt, is_estimated in dates:
266
266
  if days_delta != 0:
267
- dt += td(days=days_delta)
267
+ dt = _timedelta(dt, days_delta)
268
268
 
269
269
  dt = self._add_holiday(
270
270
  self.tr(estimated_label) % self.tr(name) if is_estimated else name, dt
@@ -11,9 +11,9 @@
11
11
  # License: MIT (see LICENSE file)
12
12
 
13
13
  from datetime import date
14
- from datetime import timedelta as td
15
14
  from typing import Optional
16
15
 
16
+ from holidays.calendars.gregorian import _timedelta
17
17
  from holidays.calendars.persian import _Persian
18
18
 
19
19
 
@@ -142,5 +142,5 @@ class PersianCalendarHolidays:
142
142
  if dt is None:
143
143
  return None
144
144
  if days_delta != 0:
145
- dt += td(days=days_delta)
145
+ dt = _timedelta(dt, days_delta)
146
146
  return self._add_holiday(name, dt)
holidays/helpers.py CHANGED
@@ -24,13 +24,19 @@ def _normalize_arguments(cls, value):
24
24
  A set created from `value` argument.
25
25
 
26
26
  """
27
+ if value is None:
28
+ return set()
29
+
27
30
  if isinstance(value, cls):
28
31
  return {value}
29
32
 
30
- return set(value) if value is not None else set()
33
+ try:
34
+ return {v if isinstance(v, cls) else cls(v) for v in value}
35
+ except TypeError: # non-iterable
36
+ return {value if isinstance(value, cls) else cls(value)}
31
37
 
32
38
 
33
- def _normalize_tuple(data):
39
+ def _normalize_tuple(value):
34
40
  """Normalize tuple.
35
41
 
36
42
  :param data:
@@ -40,4 +46,4 @@ def _normalize_tuple(data):
40
46
  An unchanged object for tuple of tuples, e.g., ((JAN, 10), (DEC, 31)).
41
47
  An object put into a tuple otherwise, e.g., ((JAN, 10),).
42
48
  """
43
- return data if not data or isinstance(data[0], tuple) else (data,)
49
+ return value if not value or isinstance(value[0], tuple) else (value,)
holidays/holiday_base.py CHANGED
@@ -13,7 +13,6 @@
13
13
  __all__ = ("DateLike", "HolidayBase", "HolidaySum")
14
14
 
15
15
  import copy
16
- import re
17
16
  import warnings
18
17
  from calendar import isleap
19
18
  from datetime import date, datetime, timedelta, timezone
@@ -24,7 +23,6 @@ from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union, cast
24
23
 
25
24
  from dateutil.parser import parse
26
25
 
27
- from holidays.calendars import gregorian
28
26
  from holidays.calendars.gregorian import (
29
27
  MON,
30
28
  TUE,
@@ -33,8 +31,12 @@ from holidays.calendars.gregorian import (
33
31
  FRI,
34
32
  SAT,
35
33
  SUN,
34
+ _timedelta,
36
35
  _get_nth_weekday_from,
37
36
  _get_nth_weekday_of_month,
37
+ DAYS,
38
+ MONTHS,
39
+ WEEKDAYS,
38
40
  )
39
41
  from holidays.constants import HOLIDAY_NAME_DELIMITER, PUBLIC
40
42
  from holidays.helpers import _normalize_arguments, _normalize_tuple
@@ -230,6 +232,8 @@ class HolidayBase(Dict[date, str]):
230
232
  ones."""
231
233
  weekend: Set[int] = {SAT, SUN}
232
234
  """Country weekend days."""
235
+ weekend_workdays: Set[date] = set()
236
+ """Working days moved to weekends."""
233
237
  default_category: str = PUBLIC
234
238
  """The entity category used by default."""
235
239
  default_language: Optional[str] = None
@@ -353,6 +357,7 @@ class HolidayBase(Dict[date, str]):
353
357
  self.language = language.lower() if language else None
354
358
  self.observed = observed
355
359
  self.subdiv = subdiv
360
+ self.weekend_workdays = set()
356
361
 
357
362
  supported_languages = set(self.supported_languages)
358
363
  self.tr = (
@@ -427,77 +432,107 @@ class HolidayBase(Dict[date, str]):
427
432
  except AttributeError as e:
428
433
  # This part is responsible for _add_holiday_* syntactic sugar support.
429
434
  add_holiday_prefix = "_add_holiday_"
430
- # Raise early if prefix doesn't match to avoid regex checks.
435
+ # Raise early if prefix doesn't match to avoid patterns checks.
431
436
  if name[: len(add_holiday_prefix)] != add_holiday_prefix:
432
437
  raise e
433
438
 
439
+ tokens = name.split("_")
440
+
434
441
  # Handle <month> <day> patterns (e.g., _add_holiday_jun_15()).
435
- month_day = re.match(r"_add_holiday_(\w{3})_(\d{1,2})", name)
436
- if month_day:
437
- month, day = month_day.groups()
438
- return lambda name: self._add_holiday(
439
- name,
440
- date(self._year, getattr(gregorian, month.upper()), int(day)),
441
- )
442
+ if len(tokens) == 5:
443
+ *_, month, day = tokens
444
+ if month in MONTHS and day in DAYS:
445
+ return lambda name: self._add_holiday(
446
+ name,
447
+ date(self._year, MONTHS[month], int(day)),
448
+ )
442
449
 
443
- # Handle <last/nth> <weekday> of <month> patterns (e.g.,
444
- # _add_holiday_last_mon_of_aug() or _add_holiday_3rd_fri_of_aug()).
445
- nth_weekday_of_month = re.match(
446
- r"_add_holiday_(last|\d\w{2})_(\w{3})_of_(\w{3})", name
447
- )
448
- if nth_weekday_of_month:
449
- number, weekday, month = nth_weekday_of_month.groups()
450
- return lambda name: self._add_holiday(
451
- name,
452
- _get_nth_weekday_of_month(
453
- -1 if number == "last" else +int(re.sub(r"\D", "", number)),
454
- getattr(gregorian, weekday.upper()),
455
- getattr(gregorian, month.upper()),
456
- self._year,
457
- ),
458
- )
450
+ elif len(tokens) == 7:
451
+ # Handle <last/nth> <weekday> of <month> patterns (e.g.,
452
+ # _add_holiday_last_mon_of_aug() or _add_holiday_3rd_fri_of_aug()).
453
+ *_, number, weekday, of, month = tokens
454
+ if (
455
+ of == "of"
456
+ and (number == "last" or number[0].isdigit())
457
+ and month in MONTHS
458
+ and weekday in WEEKDAYS
459
+ ):
460
+ return lambda name: self._add_holiday(
461
+ name,
462
+ _get_nth_weekday_of_month(
463
+ -1 if number == "last" else int(number[0]),
464
+ WEEKDAYS[weekday],
465
+ MONTHS[month],
466
+ self._year,
467
+ ),
468
+ )
469
+
470
+ # Handle <n> days <past/prior> easter patterns (e.g.,
471
+ # _add_holiday_8_days_past_easter() or
472
+ # _add_holiday_5_days_prior_easter()).
473
+ *_, days, unit, delta_direction, easter = tokens
474
+ if (
475
+ unit in {"day", "days"}
476
+ and delta_direction in {"past", "prior"}
477
+ and easter == "easter"
478
+ and len(days) < 3
479
+ and days.isdigit()
480
+ ):
481
+ return lambda name: self._add_holiday(
482
+ name,
483
+ _timedelta(
484
+ self._easter_sunday,
485
+ +int(days) if delta_direction == "past" else -int(days),
486
+ ),
487
+ )
459
488
 
460
489
  # Handle <n> day(s) <past/prior> <last/<nth> <weekday> of <month> patterns (e.g.,
461
490
  # _add_holiday_1_day_past_1st_fri_of_aug() or
462
491
  # _add_holiday_5_days_prior_last_fri_of_aug()).
463
- nth_weekday_of_month_with_delta = re.match(
464
- r"_add_holiday_(\d{1,2})_days?_(past|prior)_(last|\d\w{2})_(\w{3})_of_(\w{3})",
465
- name,
466
- )
467
- if nth_weekday_of_month_with_delta:
468
- (
469
- days,
470
- delta_direction,
471
- number,
472
- weekday,
473
- month,
474
- ) = nth_weekday_of_month_with_delta.groups()
475
- return lambda name: self._add_holiday(
476
- name,
477
- _get_nth_weekday_of_month(
478
- -1 if number == "last" else +int(re.sub(r"\D", "", number)),
479
- getattr(gregorian, weekday.upper()),
480
- getattr(gregorian, month.upper()),
481
- self._year,
492
+ elif len(tokens) == 10:
493
+ *_, days, unit, delta_direction, number, weekday, of, month = tokens
494
+ if (
495
+ unit in {"day", "days"}
496
+ and delta_direction in {"past", "prior"}
497
+ and of == "of"
498
+ and len(days) < 3
499
+ and days.isdigit()
500
+ and (number == "last" or number[0].isdigit())
501
+ and month in MONTHS
502
+ and weekday in WEEKDAYS
503
+ ):
504
+ return lambda name: self._add_holiday(
505
+ name,
506
+ _timedelta(
507
+ _get_nth_weekday_of_month(
508
+ -1 if number == "last" else int(number[0]),
509
+ WEEKDAYS[weekday],
510
+ MONTHS[month],
511
+ self._year,
512
+ ),
513
+ +int(days) if delta_direction == "past" else -int(days),
514
+ ),
482
515
  )
483
- + timedelta(days=+int(days) if delta_direction == "past" else -int(days)),
484
- )
485
516
 
486
517
  # Handle <nth> <weekday> <before/from> <month> <day> patterns (e.g.,
487
518
  # _add_holiday_1st_mon_before_jun_15() or _add_holiday_1st_mon_from_jun_15()).
488
- nth_weekday_from = re.match(
489
- r"_add_holiday_(\d{1,2})\w{2}_(\w+)_(before|from)_(\w{3})_(\d{1,2})", name
490
- )
491
- if nth_weekday_from:
492
- number, weekday, date_direction, month, day = nth_weekday_from.groups()
493
- return lambda name: self._add_holiday(
494
- name,
495
- _get_nth_weekday_from(
496
- -int(number) if date_direction == "before" else +int(number),
497
- getattr(gregorian, weekday.upper()),
498
- date(self._year, getattr(gregorian, month.upper()), int(day)),
499
- ),
500
- )
519
+ elif len(tokens) == 8:
520
+ *_, number, weekday, date_direction, month, day = tokens
521
+ if (
522
+ date_direction in {"before", "from"}
523
+ and number[0].isdigit()
524
+ and month in MONTHS
525
+ and weekday in WEEKDAYS
526
+ and day in DAYS
527
+ ):
528
+ return lambda name: self._add_holiday(
529
+ name,
530
+ _get_nth_weekday_from(
531
+ -int(number[0]) if date_direction == "before" else +int(number[0]),
532
+ WEEKDAYS[weekday],
533
+ date(self._year, MONTHS[month], int(day)),
534
+ ),
535
+ )
501
536
 
502
537
  raise e # No match.
503
538
 
@@ -527,7 +562,7 @@ class HolidayBase(Dict[date, str]):
527
562
 
528
563
  days_in_range = []
529
564
  for delta_days in range(0, date_diff.days, step):
530
- day = start + timedelta(days=delta_days)
565
+ day = _timedelta(start, delta_days)
531
566
  if day in self:
532
567
  days_in_range.append(day)
533
568
 
@@ -676,9 +711,13 @@ class HolidayBase(Dict[date, str]):
676
711
  .lower()
677
712
  )
678
713
 
679
- @cached_property
714
+ @property
680
715
  def _sorted_categories(self):
681
- return sorted(self.categories)
716
+ return (
717
+ [self.default_category] + sorted(self.categories - {self.default_category})
718
+ if self.default_category in self.categories
719
+ else sorted(self.categories)
720
+ )
682
721
 
683
722
  @classmethod
684
723
  def get_subdivision_aliases(cls) -> Dict[str, List]:
@@ -724,14 +763,14 @@ class HolidayBase(Dict[date, str]):
724
763
  )
725
764
  else: # Substituted holidays.
726
765
  to_month, to_day, from_month, from_day, *optional = data
766
+ from_date = date(optional[0] if optional else self._year, from_month, from_day)
727
767
  self._add_holiday(
728
768
  self.tr(self.substituted_label)
729
- % date(
730
- optional[0] if optional else self._year, from_month, from_day
731
- ).strftime(self.tr(self.substituted_date_format)),
769
+ % from_date.strftime(self.tr(self.substituted_date_format)),
732
770
  to_month,
733
771
  to_day,
734
772
  )
773
+ self.weekend_workdays.add(from_date)
735
774
 
736
775
  def _check_weekday(self, weekday: int, *args) -> bool:
737
776
  """
@@ -922,6 +961,34 @@ class HolidayBase(Dict[date, str]):
922
961
 
923
962
  raise AttributeError(f"Unknown lookup type: {lookup}")
924
963
 
964
+ def get_nth_workday(self, key: DateLike, n: int) -> date:
965
+ """Return n-th working day from provided date (if n is positive)
966
+ or n-th working day before provided date (if n is negative).
967
+ """
968
+ direction = +1 if n > 0 else -1
969
+ dt = self.__keytransform__(key)
970
+ for _ in range(abs(n)):
971
+ dt = _timedelta(dt, direction)
972
+ while not self.is_workday(dt):
973
+ dt = _timedelta(dt, direction)
974
+ return dt
975
+
976
+ def get_workdays_number(self, key1: DateLike, key2: DateLike) -> int:
977
+ """Return the number of working days between two dates (not including the start date)."""
978
+ dt1 = self.__keytransform__(key1)
979
+ dt2 = self.__keytransform__(key2)
980
+ if dt1 == dt2:
981
+ return 0
982
+ if dt1 > dt2:
983
+ dt1, dt2 = dt2, dt1
984
+
985
+ return sum(self.is_workday(_timedelta(dt1, n)) for n in range(1, (dt2 - dt1).days + 1))
986
+
987
+ def is_workday(self, key: DateLike) -> bool:
988
+ """Return True if date is a working day (not a holiday or a weekend)."""
989
+ dt = self.__keytransform__(key)
990
+ return dt in self.weekend_workdays if self._is_weekend(dt) else dt not in self
991
+
925
992
  def pop(self, key: DateLike, default: Union[str, Any] = None) -> Union[str, Any]:
926
993
  """If date is a holiday, remove it and return its date, else return
927
994
  default.
Binary file