holidays 0.46__py3-none-any.whl → 0.48__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 (56) hide show
  1. holidays/__init__.py +1 -1
  2. holidays/calendars/gregorian.py +9 -0
  3. holidays/countries/__init__.py +2 -0
  4. holidays/countries/andorra.py +18 -20
  5. holidays/countries/australia.py +19 -4
  6. holidays/countries/azerbaijan.py +1 -0
  7. holidays/countries/belgium.py +1 -2
  8. holidays/countries/bolivia.py +1 -2
  9. holidays/countries/bosnia_and_herzegovina.py +1 -0
  10. holidays/countries/botswana.py +5 -8
  11. holidays/countries/brazil.py +1 -2
  12. holidays/countries/brunei.py +1 -0
  13. holidays/countries/burkina_faso.py +1 -0
  14. holidays/countries/cameroon.py +1 -0
  15. holidays/countries/chad.py +1 -0
  16. holidays/countries/chile.py +6 -6
  17. holidays/countries/colombia.py +1 -2
  18. holidays/countries/cyprus.py +1 -2
  19. holidays/countries/denmark.py +1 -2
  20. holidays/countries/ethiopia.py +1 -0
  21. holidays/countries/finland.py +1 -1
  22. holidays/countries/france.py +1 -2
  23. holidays/countries/gabon.py +1 -0
  24. holidays/countries/greece.py +11 -6
  25. holidays/countries/honduras.py +3 -4
  26. holidays/countries/indonesia.py +1 -0
  27. holidays/countries/jordan.py +81 -0
  28. holidays/countries/latvia.py +4 -5
  29. holidays/countries/moldova.py +1 -2
  30. holidays/countries/new_zealand.py +5 -6
  31. holidays/countries/pakistan.py +1 -0
  32. holidays/countries/palau.py +127 -0
  33. holidays/countries/poland.py +1 -2
  34. holidays/countries/portugal.py +2 -6
  35. holidays/countries/south_africa.py +2 -0
  36. holidays/countries/south_korea.py +1 -1
  37. holidays/countries/tanzania.py +1 -0
  38. holidays/countries/timor_leste.py +24 -1
  39. holidays/countries/united_arab_emirates.py +1 -0
  40. holidays/countries/united_states.py +1 -1
  41. holidays/countries/uruguay.py +3 -4
  42. holidays/countries/uzbekistan.py +1 -0
  43. holidays/financial/european_central_bank.py +17 -3
  44. holidays/groups/international.py +10 -0
  45. holidays/holiday_base.py +129 -65
  46. holidays/locale/ar/LC_MESSAGES/JO.mo +0 -0
  47. holidays/locale/ar/LC_MESSAGES/JO.po +79 -0
  48. holidays/locale/en_US/LC_MESSAGES/JO.mo +0 -0
  49. holidays/locale/en_US/LC_MESSAGES/JO.po +79 -0
  50. holidays/registry.py +2 -0
  51. {holidays-0.46.dist-info → holidays-0.48.dist-info}/AUTHORS +1 -0
  52. {holidays-0.46.dist-info → holidays-0.48.dist-info}/METADATA +28 -18
  53. {holidays-0.46.dist-info → holidays-0.48.dist-info}/RECORD +56 -50
  54. {holidays-0.46.dist-info → holidays-0.48.dist-info}/LICENSE +0 -0
  55. {holidays-0.46.dist-info → holidays-0.48.dist-info}/WHEEL +0 -0
  56. {holidays-0.46.dist-info → holidays-0.48.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,127 @@
1
+ # holidays
2
+ # --------
3
+ # A fast, efficient Python library for generating country, province and state
4
+ # specific sets of holidays on the fly. It aims to make determining whether a
5
+ # specific date is a holiday as fast and flexible as possible.
6
+ #
7
+ # Authors: Vacanza Team and individual contributors (see AUTHORS file)
8
+ # dr-prodigy <dr.prodigy.github@gmail.com> (c) 2017-2023
9
+ # ryanss <ryanssdev@icloud.com> (c) 2014-2017
10
+ # Website: https://github.com/vacanza/python-holidays
11
+ # License: MIT (see LICENSE file)
12
+
13
+ from holidays.calendars.gregorian import SEP, NOV
14
+ from holidays.constants import ARMED_FORCES, HALF_DAY, PUBLIC
15
+ from holidays.groups import ChristianHolidays, InternationalHolidays, StaticHolidays
16
+ from holidays.observed_holiday_base import ObservedHolidayBase, SAT_TO_PREV_FRI, SUN_TO_NEXT_MON
17
+
18
+
19
+ class Palau(ObservedHolidayBase, ChristianHolidays, InternationalHolidays):
20
+ """
21
+ References:
22
+ - http://www.paclii.org/pw/legis/consol_act/gpt1262/ # Chapter 7, Holidays.
23
+ - https://www.palaugov.pw/wp-content/uploads/2017/11/RPPL-No.-10-15-re.-Family-Day-Holiday.pdf
24
+ - https://www.facebook.com/PalauPresident/posts/195883107230463 # EO336 Memorial Day repealed
25
+ - https://www.taiwanembassy.org/pal_en/post/792.html # Earliest source for President's Day
26
+
27
+ If any of the holidays enumerated in section 701 of this chapter falls on Sunday, the
28
+ following Monday shall be observed as a holiday. If any of the holidays enumerated in
29
+ section 701 of this chapter falls on Saturday, the preceding Friday shall be observed
30
+ as a holiday.
31
+
32
+ As there's no record of President's Day (Jun 1) and Independence Day (Oct 1) being
33
+ legal holiday before 2017, as seen in RPRL 10-15, they shall be assumed to start in 2018
34
+ for our current implementation.
35
+ """
36
+
37
+ country = "PW"
38
+ supported_categories = (ARMED_FORCES, HALF_DAY, PUBLIC)
39
+ observed_label = "%s (observed)"
40
+
41
+ def __init__(self, *args, **kwargs):
42
+ ChristianHolidays.__init__(self)
43
+ InternationalHolidays.__init__(self)
44
+ StaticHolidays.__init__(self, PalauStaticHolidays)
45
+ kwargs.setdefault("observed_rule", SAT_TO_PREV_FRI + SUN_TO_NEXT_MON)
46
+ super().__init__(*args, **kwargs)
47
+
48
+ def _populate_public_holidays(self):
49
+ # Republic of Palau Public Law No. 2-15.
50
+ # The legislation was first adopted by the 2nd Olbiil Era Kelulau (1984-1988),
51
+ # but since we cannot find any info on its actual adoption date, we may as
52
+ # well use the formation date of the country as the placeholder cut-off date.
53
+ if self._year <= 1980:
54
+ return None
55
+
56
+ # Fixed Date Public Holidays.
57
+
58
+ # New Year's Day.
59
+ name = "New Year's Day"
60
+ self._add_observed(self._add_new_years_day(name))
61
+ self._add_observed(self._next_year_new_years_day, name=name, rule=SAT_TO_PREV_FRI)
62
+
63
+ # Youth Day.
64
+ self._add_observed(self._add_holiday_mar_15("Youth Day"))
65
+
66
+ # Senior Citizens Day.
67
+ self._add_observed(self._add_holiday_may_5("Senior Citizens Day"))
68
+
69
+ if self._year in {2011, 2012}:
70
+ # Memorial Day.
71
+ self._add_holiday_last_mon_of_may("Memorial Day")
72
+
73
+ if self._year >= 2018:
74
+ # President's Day.
75
+ self._add_observed(self._add_holiday_jun_1("President's Day"))
76
+
77
+ # Constitution Day.
78
+ self._add_observed(self._add_holiday_jul_9("Constitution Day"))
79
+
80
+ # Labor Day.
81
+ self._add_holiday_1st_mon_of_sep("Labor Day")
82
+
83
+ if self._year >= 2018:
84
+ # Independence Day.
85
+ self._add_observed(self._add_holiday_oct_1("Independence Day"))
86
+
87
+ # United Nations Day.
88
+ self._add_observed(self._add_united_nations_day("United Nations Day"))
89
+
90
+ # Thanksgiving Day.
91
+ self._add_holiday_4th_thu_of_nov("Thanksgiving Day")
92
+
93
+ if self._year >= 2017:
94
+ # Family Day.
95
+ self._add_holiday_4th_fri_of_nov("Family Day")
96
+
97
+ # Christmas Day.
98
+ self._add_observed(self._add_christmas_day("Christmas Day"))
99
+
100
+
101
+ class PW(Palau):
102
+ pass
103
+
104
+
105
+ class PLW(Palau):
106
+ pass
107
+
108
+
109
+ class PalauStaticHolidays:
110
+ """
111
+ Sources:
112
+ - https://www.facebook.com/photo?fbid=1774513196034105&set=a.175933635892077
113
+ - https://www.facebook.com/photo/?fbid=1794692910682800&set=a.175933635892077
114
+ - https://www.facebook.com/photo/?fbid=1408133829338712&set=a.175933635892077
115
+ """
116
+
117
+ special_armed_forces_holidays = {
118
+ 2020: (NOV, 11, "Veterans Day"),
119
+ }
120
+
121
+ special_half_day_holidays = {
122
+ 2019: (SEP, 30, "Preparation for the 25th Independence Day of the Republic of Palau"),
123
+ }
124
+
125
+ special_public_holidays = {
126
+ 2020: (NOV, 3, "National Day of Democracy"),
127
+ }
@@ -10,7 +10,6 @@
10
10
  # Website: https://github.com/vacanza/python-holidays
11
11
  # License: MIT (see LICENSE file)
12
12
 
13
- from datetime import timedelta as td
14
13
  from gettext import gettext as tr
15
14
 
16
15
  from holidays.calendars.gregorian import NOV
@@ -68,7 +67,7 @@ class Poland(HolidayBase, ChristianHolidays, InternationalHolidays, StaticHolida
68
67
 
69
68
  if self._year <= 1950:
70
69
  # Ascension Day.
71
- self._add_holiday(tr("Wniebowstąpienie Pańskie"), self._easter_sunday + td(days=+40))
70
+ self._add_ascension_thursday(tr("Wniebowstąpienie Pańskie"))
72
71
 
73
72
  # Pentecost.
74
73
  self._add_whit_sunday(tr("Zielone Świątki"))
@@ -10,7 +10,6 @@
10
10
  # Website: https://github.com/vacanza/python-holidays
11
11
  # License: MIT (see LICENSE file)
12
12
 
13
- from datetime import timedelta as td
14
13
  from gettext import gettext as tr
15
14
 
16
15
  from holidays.constants import OPTIONAL, PUBLIC
@@ -184,11 +183,8 @@ class Portugal(HolidayBase, ChristianHolidays, InternationalHolidays):
184
183
  self._add_holiday_aug_22(tr("Dia de Nossa Senhora das Graças"))
185
184
 
186
185
  def _populate_subdiv_05_public_holidays(self):
187
- self._add_holiday(
188
- # Feast of Our Lady of Mércoles.
189
- tr("Dia de Nossa Senhora de Mércoles"),
190
- self._easter_sunday + td(days=+16),
191
- )
186
+ # Feast of Our Lady of Mércoles.
187
+ self._add_holiday_16_days_past_easter(tr("Dia de Nossa Senhora de Mércoles"))
192
188
 
193
189
  def _populate_subdiv_06_public_holidays(self):
194
190
  # St. Elizabeth's Day.
@@ -20,6 +20,7 @@ class SouthAfrica(ObservedHolidayBase, ChristianHolidays, InternationalHolidays,
20
20
  https://www.gov.za/about-sa/public-holidays
21
21
  https://en.wikipedia.org/wiki/Public_holidays_in_South_Africa
22
22
  https://www.gov.za/speeches/president-cyril-ramaphosa-progress-economic-recovery-30-oct-2023-0000
23
+ https://www.gov.za/documents/notices/public-holidays-act-declaration-29-may-2024-public-holiday-23-feb-2024
23
24
  """
24
25
 
25
26
  country = "ZA"
@@ -147,6 +148,7 @@ class SouthAfricaStaticHolidays:
147
148
  2022: (DEC, 27, presidential_decree_holiday),
148
149
  # Winning the 2023 Rugby World Cup
149
150
  2023: (DEC, 15, presidential_decree_holiday),
151
+ 2024: (MAY, 29, national_and_provincial_elections),
150
152
  }
151
153
 
152
154
  special_public_holidays_observed = {
@@ -198,7 +198,7 @@ class SouthKorea(
198
198
 
199
199
  if 1950 <= self._year <= 1975:
200
200
  # United Nations Day.
201
- self._add_holiday_oct_24(tr("국제연합일"))
201
+ self._add_united_nations_day(tr("국제연합일"))
202
202
 
203
203
  # Chuseok.
204
204
  name = tr("추석")
@@ -264,6 +264,7 @@ class TanzaniaIslamicHolidays(_CustomIslamicHolidays):
264
264
  2021: (MAY, 14),
265
265
  2022: (MAY, 3),
266
266
  2023: (APR, 22),
267
+ 2024: (APR, 10),
267
268
  }
268
269
 
269
270
  MAWLID_DATES = {
@@ -31,10 +31,11 @@ class TimorLeste(
31
31
  References:
32
32
  - https://mj.gov.tl/jornal/lawsTL/RDTL-Law/RDTL-Laws/Law-2005-10.pdf # 2005 Law
33
33
  - http://timor-leste.gov.tl/?p=14494&lang=en # 2016 Amendment
34
+ - http://timor-leste.gov.tl/?p=30266&lang=en # 2022
34
35
  - http://timor-leste.gov.tl/?p=31750&lang=en # 2023 (en_US)
35
36
  - http://timor-leste.gov.tl/?p=31750&lang=pt # 2023 (pt_PT)
36
37
  - http://timor-leste.gov.tl/?p=31750&lang=tp # 2023 (tet)
37
- - http://timor-leste.gov.tl/?p=30266&lang=en # 2022
38
+ - http://timor-leste.gov.tl/?p=35833&lang=en # 2024
38
39
 
39
40
  Limitations:
40
41
 
@@ -201,6 +202,7 @@ class TimorLesteIslamicHolidays(_CustomIslamicHolidays):
201
202
  2021: (JUL, 19),
202
203
  2022: (JUL, 9),
203
204
  2023: (JUN, 29),
205
+ 2024: (JUN, 17),
204
206
  }
205
207
 
206
208
  EID_AL_FITR_DATES = {
@@ -217,6 +219,7 @@ class TimorLesteIslamicHolidays(_CustomIslamicHolidays):
217
219
  2021: (MAY, 13),
218
220
  2022: (MAY, 2),
219
221
  2023: (APR, 22),
222
+ 2024: (APR, 10),
220
223
  }
221
224
 
222
225
 
@@ -395,20 +398,28 @@ class TimorLesteStaticHolidays:
395
398
  (JAN, 2, special_national_holidays),
396
399
  # http://timor-leste.gov.tl/?p=23607&lang=en
397
400
  (FEB, 26, special_national_holidays),
401
+ # http://timor-leste.gov.tl/?p=25455&lang=en
402
+ (AUG, 20, special_national_holidays),
398
403
  # http://timor-leste.gov.tl/?p=25502&lang=en
399
404
  (AUG, 31, special_national_holidays),
400
405
  # http://timor-leste.gov.tl/?p=26030&lang=en
401
406
  (NOV, 3, special_national_holidays),
407
+ # http://timor-leste.gov.tl/?p=26365&lang=en
408
+ (DEC, 24, special_national_holidays),
402
409
  ),
403
410
  2021: (
404
411
  # http://timor-leste.gov.tl/?p=26865&lang=en
405
412
  (FEB, 12, special_national_holidays),
413
+ # http://timor-leste.gov.tl/?p=26896&lang=en
414
+ (FEB, 17, special_national_holidays),
406
415
  # http://timor-leste.gov.tl/?p=29682&lang=en
407
416
  (NOV, 3, special_national_holidays),
408
417
  ),
409
418
  2022: (
410
419
  # http://timor-leste.gov.tl/?p=30029&lang=en
411
420
  (FEB, 1, special_national_holidays),
421
+ # http://timor-leste.gov.tl/?p=30194&lang=en
422
+ (MAR, 2, special_national_holidays),
412
423
  # http://timor-leste.gov.tl/?p=30254&lang=en
413
424
  (MAR, 18, presidential_election),
414
425
  # http://timor-leste.gov.tl/?p=30429&lang=en
@@ -416,11 +427,23 @@ class TimorLesteStaticHolidays:
416
427
  (APR, 18, presidential_election),
417
428
  (APR, 19, presidential_election),
418
429
  (APR, 20, presidential_election),
430
+ # http://timor-leste.gov.tl/?p=31404&lang=en
431
+ (OCT, 31, special_national_holidays),
432
+ # http://timor-leste.gov.tl/?p=31574&lang=en
433
+ (DEC, 9, special_national_holidays),
434
+ # http://timor-leste.gov.tl/?p=31633&lang=en
435
+ (DEC, 26, special_national_holidays),
419
436
  ),
420
437
  2023: (
421
438
  # http://timor-leste.gov.tl/?p=31641&lang=en
422
439
  (JAN, 2, special_national_holidays),
423
440
  # http://timor-leste.gov.tl/?p=31798&lang=en
424
441
  (JAN, 23, special_national_holidays),
442
+ # http://timor-leste.gov.tl/?p=32191&lang=en
443
+ (FEB, 22, special_national_holidays),
444
+ ),
445
+ 2024: (
446
+ # http://timor-leste.gov.tl/?p=36002&lang=en
447
+ (FEB, 14, special_national_holidays),
425
448
  ),
426
449
  }
@@ -118,6 +118,7 @@ class UnitedArabEmiratesIslamicHolidays(_CustomIslamicHolidays):
118
118
  2021: (MAY, 13),
119
119
  2022: (MAY, 2),
120
120
  2023: (APR, 21),
121
+ 2024: (APR, 10),
121
122
  }
122
123
 
123
124
  HIJRI_NEW_YEAR_DATES = {
@@ -609,7 +609,7 @@ class UnitedStates(ObservedHolidayBase, ChristianHolidays, InternationalHolidays
609
609
 
610
610
  # Confederate Memorial Day
611
611
  if self._year >= 1866:
612
- self._add_holiday_4th_mon_of_apr("Confederate Memorial Day")
612
+ self._add_holiday_last_mon_of_apr("Confederate Memorial Day")
613
613
 
614
614
  def _populate_subdiv_mt_public_holidays(self):
615
615
  # Election Day
@@ -11,7 +11,6 @@
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 gettext import gettext as tr
16
15
 
17
16
  from holidays.calendars.gregorian import MAR
@@ -123,9 +122,9 @@ class Uruguay(ObservedHolidayBase, ChristianHolidays, InternationalHolidays, Sta
123
122
 
124
123
  # Tourism Week.
125
124
  name = tr("Semana de Turismo")
126
- self._add_holiday(name, self._easter_sunday + td(days=-6))
127
- self._add_holiday(name, self._easter_sunday + td(days=-5))
128
- self._add_holiday(name, self._easter_sunday + td(days=-4))
125
+ self._add_holiday_6_days_prior_easter(name)
126
+ self._add_holiday_5_days_prior_easter(name)
127
+ self._add_holiday_4_days_prior_easter(name)
129
128
  self._add_holy_thursday(name)
130
129
  self._add_good_friday(name)
131
130
 
@@ -144,6 +144,7 @@ class UzbekistanIslamicHolidays(_CustomIslamicHolidays):
144
144
  2021: (MAY, 13),
145
145
  2022: (MAY, 2),
146
146
  2023: (APR, 21),
147
+ 2024: (APR, 10),
147
148
  }
148
149
 
149
150
 
@@ -10,22 +10,30 @@
10
10
  # Website: https://github.com/vacanza/python-holidays
11
11
  # License: MIT (see LICENSE file)
12
12
 
13
- from holidays.groups import ChristianHolidays, InternationalHolidays
13
+ from holidays.calendars.gregorian import DEC
14
+ from holidays.groups import ChristianHolidays, InternationalHolidays, StaticHolidays
14
15
  from holidays.holiday_base import HolidayBase
15
16
 
16
17
 
17
18
  class EuropeanCentralBank(HolidayBase, ChristianHolidays, InternationalHolidays):
18
- # https://en.wikipedia.org/wiki/TARGET2
19
- # http://www.ecb.europa.eu/press/pr/date/2000/html/pr001214_4.en.html
19
+ """
20
+ References:
21
+ - https://en.wikipedia.org/wiki/TARGET2
22
+ - https://www.ecb.europa.eu/press/pr/date/1999/html/pr990715_1.en.html
23
+ - https://www.ecb.europa.eu/press/pr/date/2000/html/pr001214_4.en.html
24
+ """
20
25
 
21
26
  market = "ECB"
22
27
 
23
28
  def __init__(self, *args, **kwargs):
24
29
  ChristianHolidays.__init__(self)
25
30
  InternationalHolidays.__init__(self)
31
+ StaticHolidays.__init__(self, EuropeanCentralBankStaticHolidays)
26
32
  super().__init__(*args, **kwargs)
27
33
 
28
34
  def _populate(self, year):
35
+ if year <= 1999:
36
+ return None
29
37
  super()._populate(year)
30
38
 
31
39
  self._add_new_years_day("New Year's Day")
@@ -45,3 +53,9 @@ class ECB(EuropeanCentralBank):
45
53
 
46
54
  class TAR(EuropeanCentralBank):
47
55
  pass
56
+
57
+
58
+ class EuropeanCentralBankStaticHolidays:
59
+ special_public_holidays = {
60
+ 2000: (DEC, 31, "Additional closing day"),
61
+ }
@@ -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)
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,
@@ -35,6 +33,9 @@ from holidays.calendars.gregorian import (
35
33
  SUN,
36
34
  _get_nth_weekday_from,
37
35
  _get_nth_weekday_of_month,
36
+ DAYS,
37
+ MONTHS,
38
+ WEEKDAYS,
38
39
  )
39
40
  from holidays.constants import HOLIDAY_NAME_DELIMITER, PUBLIC
40
41
  from holidays.helpers import _normalize_arguments, _normalize_tuple
@@ -230,6 +231,8 @@ class HolidayBase(Dict[date, str]):
230
231
  ones."""
231
232
  weekend: Set[int] = {SAT, SUN}
232
233
  """Country weekend days."""
234
+ weekend_workdays: Set[date] = set()
235
+ """Working days moved to weekends."""
233
236
  default_category: str = PUBLIC
234
237
  """The entity category used by default."""
235
238
  default_language: Optional[str] = None
@@ -353,6 +356,7 @@ class HolidayBase(Dict[date, str]):
353
356
  self.language = language.lower() if language else None
354
357
  self.observed = observed
355
358
  self.subdiv = subdiv
359
+ self.weekend_workdays = set()
356
360
 
357
361
  supported_languages = set(self.supported_languages)
358
362
  self.tr = (
@@ -427,77 +431,103 @@ class HolidayBase(Dict[date, str]):
427
431
  except AttributeError as e:
428
432
  # This part is responsible for _add_holiday_* syntactic sugar support.
429
433
  add_holiday_prefix = "_add_holiday_"
430
- # Raise early if prefix doesn't match to avoid regex checks.
434
+ # Raise early if prefix doesn't match to avoid patterns checks.
431
435
  if name[: len(add_holiday_prefix)] != add_holiday_prefix:
432
436
  raise e
433
437
 
438
+ tokens = name.split("_")
439
+
434
440
  # 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
- )
441
+ if len(tokens) == 5:
442
+ *_, month, day = tokens
443
+ if month in MONTHS and day in DAYS:
444
+ return lambda name: self._add_holiday(
445
+ name,
446
+ date(self._year, MONTHS[month], int(day)),
447
+ )
442
448
 
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
- )
449
+ elif len(tokens) == 7:
450
+ # Handle <last/nth> <weekday> of <month> patterns (e.g.,
451
+ # _add_holiday_last_mon_of_aug() or _add_holiday_3rd_fri_of_aug()).
452
+ *_, number, weekday, of, month = tokens
453
+ if (
454
+ of == "of"
455
+ and (number == "last" or number[0].isdigit())
456
+ and month in MONTHS
457
+ and weekday in WEEKDAYS
458
+ ):
459
+ return lambda name: self._add_holiday(
460
+ name,
461
+ _get_nth_weekday_of_month(
462
+ -1 if number == "last" else int(number[0]),
463
+ WEEKDAYS[weekday],
464
+ MONTHS[month],
465
+ self._year,
466
+ ),
467
+ )
468
+
469
+ # Handle <n> days <past/prior> easter patterns (e.g.,
470
+ # _add_holiday_8_days_past_easter() or
471
+ # _add_holiday_5_days_prior_easter()).
472
+ *_, days, unit, delta_direction, easter = tokens
473
+ if (
474
+ unit in {"day", "days"}
475
+ and delta_direction in {"past", "prior"}
476
+ and easter == "easter"
477
+ and len(days) < 3
478
+ and days.isdigit()
479
+ ):
480
+ return lambda name: self._add_holiday(
481
+ name,
482
+ self._easter_sunday
483
+ + timedelta(days=+int(days) if delta_direction == "past" else -int(days)),
484
+ )
459
485
 
460
486
  # Handle <n> day(s) <past/prior> <last/<nth> <weekday> of <month> patterns (e.g.,
461
487
  # _add_holiday_1_day_past_1st_fri_of_aug() or
462
488
  # _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,
489
+ elif len(tokens) == 10:
490
+ *_, days, unit, delta_direction, number, weekday, of, month = tokens
491
+ if (
492
+ unit in {"day", "days"}
493
+ and delta_direction in {"past", "prior"}
494
+ and of == "of"
495
+ and len(days) < 3
496
+ and days.isdigit()
497
+ and (number == "last" or number[0].isdigit())
498
+ and month in MONTHS
499
+ and weekday in WEEKDAYS
500
+ ):
501
+ return lambda name: self._add_holiday(
502
+ name,
503
+ _get_nth_weekday_of_month(
504
+ -1 if number == "last" else int(number[0]),
505
+ WEEKDAYS[weekday],
506
+ MONTHS[month],
507
+ self._year,
508
+ )
509
+ + timedelta(days=+int(days) if delta_direction == "past" else -int(days)),
482
510
  )
483
- + timedelta(days=+int(days) if delta_direction == "past" else -int(days)),
484
- )
485
511
 
486
512
  # Handle <nth> <weekday> <before/from> <month> <day> patterns (e.g.,
487
513
  # _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
- )
514
+ elif len(tokens) == 8:
515
+ *_, number, weekday, date_direction, month, day = tokens
516
+ if (
517
+ date_direction in {"before", "from"}
518
+ and number[0].isdigit()
519
+ and month in MONTHS
520
+ and weekday in WEEKDAYS
521
+ and day in DAYS
522
+ ):
523
+ return lambda name: self._add_holiday(
524
+ name,
525
+ _get_nth_weekday_from(
526
+ -int(number[0]) if date_direction == "before" else +int(number[0]),
527
+ WEEKDAYS[weekday],
528
+ date(self._year, MONTHS[month], int(day)),
529
+ ),
530
+ )
501
531
 
502
532
  raise e # No match.
503
533
 
@@ -676,9 +706,13 @@ class HolidayBase(Dict[date, str]):
676
706
  .lower()
677
707
  )
678
708
 
679
- @cached_property
709
+ @property
680
710
  def _sorted_categories(self):
681
- return sorted(self.categories)
711
+ return (
712
+ [self.default_category] + sorted(self.categories - {self.default_category})
713
+ if self.default_category in self.categories
714
+ else sorted(self.categories)
715
+ )
682
716
 
683
717
  @classmethod
684
718
  def get_subdivision_aliases(cls) -> Dict[str, List]:
@@ -724,14 +758,14 @@ class HolidayBase(Dict[date, str]):
724
758
  )
725
759
  else: # Substituted holidays.
726
760
  to_month, to_day, from_month, from_day, *optional = data
761
+ from_date = date(optional[0] if optional else self._year, from_month, from_day)
727
762
  self._add_holiday(
728
763
  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)),
764
+ % from_date.strftime(self.tr(self.substituted_date_format)),
732
765
  to_month,
733
766
  to_day,
734
767
  )
768
+ self.weekend_workdays.add(from_date)
735
769
 
736
770
  def _check_weekday(self, weekday: int, *args) -> bool:
737
771
  """
@@ -922,6 +956,36 @@ class HolidayBase(Dict[date, str]):
922
956
 
923
957
  raise AttributeError(f"Unknown lookup type: {lookup}")
924
958
 
959
+ def get_nth_workday(self, key: DateLike, n: int) -> date:
960
+ """Return n-th working day from provided date (if n is positive)
961
+ or n-th working day before provided date (if n is negative).
962
+ """
963
+ direction = +1 if n > 0 else -1
964
+ dt = self.__keytransform__(key)
965
+ for _ in range(abs(n)):
966
+ dt += timedelta(days=direction)
967
+ while not self.is_workday(dt):
968
+ dt += timedelta(days=direction)
969
+ return dt
970
+
971
+ def get_workdays_number(self, key1: DateLike, key2: DateLike) -> int:
972
+ """Return the number of working days between two dates (not including the start date)."""
973
+ dt1 = self.__keytransform__(key1)
974
+ dt2 = self.__keytransform__(key2)
975
+ if dt1 == dt2:
976
+ return 0
977
+ if dt1 > dt2:
978
+ dt1, dt2 = dt2, dt1
979
+
980
+ return sum(
981
+ self.is_workday(dt1 + timedelta(days=n)) for n in range(1, (dt2 - dt1).days + 1)
982
+ )
983
+
984
+ def is_workday(self, key: DateLike) -> bool:
985
+ """Return True if date is a working day (not a holiday or a weekend)."""
986
+ dt = self.__keytransform__(key)
987
+ return dt in self.weekend_workdays if self._is_weekend(dt) else dt not in self
988
+
925
989
  def pop(self, key: DateLike, default: Union[str, Any] = None) -> Union[str, Any]:
926
990
  """If date is a holiday, remove it and return its date, else return
927
991
  default.