Skip to content

Commit

Permalink
Add special holidays per subdivisions support (#1520)
Browse files Browse the repository at this point in the history
  • Loading branch information
KJhellico authored Oct 23, 2023
1 parent d707c64 commit ef1cf91
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 53 deletions.
8 changes: 5 additions & 3 deletions holidays/countries/australia.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from datetime import date
from datetime import timedelta as td

from holidays.calendars.gregorian import APR, AUG, SEP, OCT, FRI, _get_nth_weekday_from
from holidays.calendars.gregorian import APR, JUN, AUG, SEP, OCT, FRI, _get_nth_weekday_from
from holidays.groups import ChristianHolidays, InternationalHolidays, StaticHolidays
from holidays.observed_holiday_base import (
ObservedHolidayBase,
Expand Down Expand Up @@ -208,8 +208,6 @@ def _add_subdiv_qld_holidays(self):
self._add_holiday_2nd_mon_of_jun(self.sovereign_birthday)
else:
self._add_holiday_1st_mon_of_oct(self.sovereign_birthday)
if self._year == 2012:
self._add_holiday_jun_11("Queen's Diamond Jubilee")

# Anzac Day
if self._year >= 1921:
Expand Down Expand Up @@ -321,3 +319,7 @@ class AustraliaStaticHolidays:
special_holidays = {
2022: (SEP, 22, "National Day of Mourning for Queen Elizabeth II"),
}

special_qld_holidays = {
2012: (JUN, 11, "Queen's Diamond Jubilee"),
}
60 changes: 33 additions & 27 deletions holidays/countries/canada.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
from datetime import date
from gettext import gettext as tr

from holidays.calendars.gregorian import MAR, APR, JUN, JUL
from holidays.calendars.gregorian import MAR, APR, JUN, JUL, SEP
from holidays.constants import GOVERNMENT, OPTIONAL, PUBLIC
from holidays.groups import ChristianHolidays, InternationalHolidays
from holidays.groups import ChristianHolidays, InternationalHolidays, StaticHolidays
from holidays.observed_holiday_base import (
ObservedHolidayBase,
ALL_TO_NEAREST_MON,
Expand All @@ -25,7 +25,7 @@
)


class Canada(ObservedHolidayBase, ChristianHolidays, InternationalHolidays):
class Canada(ObservedHolidayBase, ChristianHolidays, InternationalHolidays, StaticHolidays):
"""
References:
- https://en.wikipedia.org/wiki/Public_holidays_in_Canada
Expand Down Expand Up @@ -64,6 +64,7 @@ class Canada(ObservedHolidayBase, ChristianHolidays, InternationalHolidays):
def __init__(self, *args, **kwargs):
ChristianHolidays.__init__(self)
InternationalHolidays.__init__(self)
StaticHolidays.__init__(self, CanadaStaticHolidays)
super().__init__(observed_rule=SAT_SUN_TO_NEXT_MON, *args, **kwargs)

def _get_nearest_monday(self, *args) -> date:
Expand Down Expand Up @@ -216,10 +217,6 @@ def _add_subdiv_bc_public_holidays(self):
# British Columbia Day.
self._add_holiday_1st_mon_of_aug(tr("British Columbia Day"))

if self._year == 2022:
# Funeral of Queen Elizabeth II.
self._add_holiday_sep_19(tr("Funeral of Her Majesty the Queen Elizabeth II"))

if self._year >= 2023:
# National Day for Truth and Reconciliation.
self._add_holiday_sep_30(tr("National Day for Truth and Reconciliation"))
Expand Down Expand Up @@ -266,10 +263,6 @@ def _add_subdiv_nb_public_holidays(self):
# New Brunswick Day.
self._add_holiday_1st_mon_of_aug(tr("New Brunswick Day"))

if self._year == 2022:
# Funeral of Queen Elizabeth II.
self._add_holiday_sep_19(tr("Funeral of Her Majesty the Queen Elizabeth II"))

if self._year >= 1931:
# Remembrance Day.
self._add_remembrance_day(tr("Remembrance Day"))
Expand All @@ -292,10 +285,6 @@ def _add_subdiv_nl_public_holidays(self):
if self._year >= 1879:
self._add_observed(self._canada_day)

if self._year == 2022:
# Funeral of Queen Elizabeth II.
self._add_holiday_sep_19(tr("Funeral of Her Majesty the Queen Elizabeth II"))

if self._year >= 1931:
# Remembrance Day.
self._add_observed(self._add_remembrance_day(tr("Remembrance Day")))
Expand Down Expand Up @@ -332,10 +321,6 @@ def _add_subdiv_ns_public_holidays(self):
# Heritage Day.
self._add_holiday_3rd_mon_of_feb(tr("Heritage Day"))

if self._year == 2022:
# Funeral of Queen Elizabeth II.
self._add_holiday_sep_19(tr("Funeral of Her Majesty the Queen Elizabeth II"))

if self._year >= 1981:
# Remembrance Day.
self._add_observed(self._add_remembrance_day(tr("Remembrance Day")))
Expand Down Expand Up @@ -426,10 +411,6 @@ def _add_subdiv_pe_public_holidays(self):
if self._year >= 1879:
self._add_observed(self._canada_day)

if self._year == 2022:
# Funeral of Queen Elizabeth II.
self._add_holiday_sep_19(tr("Funeral of Her Majesty the Queen Elizabeth II"))

if self._year >= 2022:
# National Day for Truth and Reconciliation.
self._add_holiday_sep_30(tr("National Day for Truth and Reconciliation"))
Expand Down Expand Up @@ -498,10 +479,6 @@ def _add_subdiv_yt_public_holidays(self):
# Discovery Day.
self._add_holiday_3rd_mon_of_aug(tr("Discovery Day"))

if self._year == 2022:
# Funeral of Queen Elizabeth II.
self._add_holiday_sep_19(tr("Funeral of Her Majesty the Queen Elizabeth II"))

if self._year >= 2023:
# National Day for Truth and Reconciliation.
self._add_holiday_sep_30(tr("National Day for Truth and Reconciliation"))
Expand All @@ -524,3 +501,32 @@ class CA(Canada):

class CAN(Canada):
pass


class CanadaStaticHolidays:
# Funeral of Queen Elizabeth II.
queen_funeral = tr("Funeral of Her Majesty the Queen Elizabeth II")

special_bc_public_holidays = {
2022: (SEP, 19, queen_funeral),
}

special_nb_public_holidays = {
2022: (SEP, 19, queen_funeral),
}

special_nl_public_holidays = {
2022: (SEP, 19, queen_funeral),
}

special_ns_public_holidays = {
2022: (SEP, 19, queen_funeral),
}

special_pe_public_holidays = {
2022: (SEP, 19, queen_funeral),
}

special_yt_public_holidays = {
2022: (SEP, 19, queen_funeral),
}
60 changes: 37 additions & 23 deletions holidays/holiday_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -649,19 +649,22 @@ def _add_holiday(self, name: str, *args) -> Optional[date]:
self[dt] = self.tr(name)
return dt

def _add_subdiv_category_holidays(self, category: str = None):
"""Populate subdivision holidays by category."""
if self.subdiv is not None:
subdiv = self.subdiv.replace("-", "_").replace(" ", "_").lower()
method_name = f"_add_subdiv_{subdiv}{f'_{category}' if category else ''}_holidays"
def _add_subdiv_holidays(self):
"""Populate subdivision holidays."""
if self.subdiv is None:
return None

subdiv = self.subdiv.replace("-", "_").replace(" ", "_").lower()
method_names = [f"_add_subdiv_{subdiv}_holidays"]

for category in sorted(self.categories):
method_names.append(f"_add_subdiv_{subdiv}_{category}_holidays")

for method_name in method_names:
add_subdiv_holidays = getattr(self, method_name, None)
if add_subdiv_holidays and callable(add_subdiv_holidays):
add_subdiv_holidays()

def _add_subdiv_holidays(self):
"""Populate subdivision holidays."""
self._add_subdiv_category_holidays()

def _add_substituted_holidays(self):
"""Populate substituted holidays."""
if len(self.substituted_holidays) == 0:
Expand Down Expand Up @@ -737,36 +740,47 @@ def _populate(self, year: int) -> None:

self._year = year

# Populate items from the special holidays list.
for month, day, name in _normalize_tuple(self.special_holidays.get(year, ())):
self._add_holiday(name, date(self._year, month, day))
# Populate special holidays.
self._add_special_holidays()

# Populate categories holidays.
self._populate_categories()
# Populate category holidays.
self._add_category_holidays()

# Populate subdivision holidays.
# Populate subdivision non-static holidays.
self._add_subdiv_holidays()

# Populate substituted holidays.
self._add_substituted_holidays()

def _populate_categories(self):
def _add_special_holidays(self):
# Check for general special holidays.
special_holidays_mapping_names = ["special_holidays"]

# Check subdivision specific special holidays.
if self.subdiv is not None:
subdiv = self.subdiv.replace("-", "_").replace(" ", "_").lower()
special_holidays_mapping_names.append(f"special_{subdiv}_holidays")

# Check category specific special holidays (both general and per subdivision).
for category in sorted(self.categories):
# Populate items from the special holidays list for all categories.
special_category_holidays = getattr(self, f"special_{category}_holidays", None)
if special_category_holidays:
special_holidays_mapping_names.append(f"special_{category}_holidays")
if self.subdiv is not None:
special_holidays_mapping_names.append(f"special_{subdiv}_{category}_holidays")

for mapping_name in special_holidays_mapping_names:
special_holidays_mapping = getattr(self, mapping_name, None)
if special_holidays_mapping:
for month, day, name in _normalize_tuple(
special_category_holidays.get(self._year, ())
special_holidays_mapping.get(self._year, ())
):
self._add_holiday(name, date(self._year, month, day))

def _add_category_holidays(self):
for category in sorted(self.categories):
populate_category_holidays = getattr(self, f"_populate_{category}_holidays", None)
if populate_category_holidays and callable(populate_category_holidays):
populate_category_holidays()

# Populate subdivision holidays for all categories.
self._add_subdiv_category_holidays(category)

def append(self, *args: Union[Dict[DateLike, str], List[DateLike], DateLike]) -> None:
"""Alias for :meth:`update` to mimic list type."""
return self.update(*args)
Expand Down

0 comments on commit ef1cf91

Please sign in to comment.