Skip to content

Commit

Permalink
Introduce ObservedHolidays class (#1444)
Browse files Browse the repository at this point in the history
Co-authored-by: Arkadii Yakovets <[email protected]>
  • Loading branch information
KJhellico and arkid15r authored Sep 5, 2023
1 parent 110e3cc commit 14a8cfa
Show file tree
Hide file tree
Showing 96 changed files with 1,169 additions and 1,302 deletions.
58 changes: 25 additions & 33 deletions holidays/countries/albania.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,20 @@
# Website: https://github.com/dr-prodigy/python-holidays
# License: MIT (see LICENSE file)

from datetime import timedelta as td

from holidays.calendars.gregorian import MAR
from holidays.calendars.julian import JULIAN_CALENDAR
from holidays.groups import ChristianHolidays, IslamicHolidays, InternationalHolidays
from holidays.holiday_base import HolidayBase
from holidays.groups import ChristianHolidays, InternationalHolidays, IslamicHolidays
from holidays.observed_holiday_base import ObservedHolidayBase, SAT_SUN_TO_NEXT_WORKDAY


class Albania(HolidayBase, ChristianHolidays, InternationalHolidays, IslamicHolidays):
class Albania(ObservedHolidayBase, ChristianHolidays, InternationalHolidays, IslamicHolidays):
"""
References:
- https://en.wikipedia.org/wiki/Public_holidays_in_Albania
"""

country = "AL"
observed_label = "%s (Observed)"
special_holidays = {
2022: (MAR, 21, "Public Holiday"),
}
Expand All @@ -32,70 +31,63 @@ def __init__(self, *args, **kwargs):
ChristianHolidays.__init__(self)
InternationalHolidays.__init__(self)
IslamicHolidays.__init__(self)
super().__init__(*args, **kwargs)
super().__init__(observed_rule=SAT_SUN_TO_NEXT_WORKDAY, *args, **kwargs)

def _populate(self, year):
super()._populate(year)
observed_dates = set()
dts_observed = set()

# New Year's Day.
name = "New Year's Day"
observed_dates.add(self._add_new_years_day(name))
observed_dates.add(self._add_new_years_day_two(name))
dts_observed.add(self._add_new_years_day(name))
dts_observed.add(self._add_new_years_day_two(name))

# Summer Day.
if year >= 2004:
observed_dates.add(self._add_holiday_mar_14("Summer Day"))
dts_observed.add(self._add_holiday_mar_14("Summer Day"))

# Nevruz.
if year >= 1996:
observed_dates.add(self._add_holiday_mar_22("Nevruz"))
dts_observed.add(self._add_holiday_mar_22("Nevruz"))

# Easter.
observed_dates.add(self._add_easter_sunday("Catholic Easter"))
observed_dates.add(self._add_easter_sunday("Orthodox Easter", JULIAN_CALENDAR))
dts_observed.add(self._add_easter_sunday("Catholic Easter"))
dts_observed.add(self._add_easter_sunday("Orthodox Easter", JULIAN_CALENDAR))

# May Day.
observed_dates.add(self._add_labor_day("May Day"))
dts_observed.add(self._add_labor_day("May Day"))

# Mother Teresa Day.
if 2004 <= year <= 2017:
observed_dates.add(self._add_holiday_oct_19("Mother Teresa Beatification Day"))
dts_observed.add(self._add_holiday_oct_19("Mother Teresa Beatification Day"))
elif year >= 2018:
observed_dates.add(self._add_holiday_sep_5("Mother Teresa Canonization Day"))
dts_observed.add(self._add_holiday_sep_5("Mother Teresa Canonization Day"))

# Independence Day.
observed_dates.add(self._add_holiday_nov_28("Independence Day"))
dts_observed.add(self._add_holiday_nov_28("Independence Day"))

# Liberation Day.
observed_dates.add(self._add_holiday_nov_29("Liberation Day"))
dts_observed.add(self._add_holiday_nov_29("Liberation Day"))

# National Youth Day.
if year >= 2009:
observed_dates.add(self._add_holiday_dec_8("National Youth Day"))
dts_observed.add(self._add_holiday_dec_8("National Youth Day"))

# Christmas Day.
observed_dates.add(self._add_christmas_day("Christmas Day"))
dts_observed.add(self._add_christmas_day("Christmas Day"))

# Eid al-Fitr.
observed_dates.update(self._add_eid_al_fitr_day("Eid al-Fitr"))
dts_observed.update(self._add_eid_al_fitr_day("Eid al-Fitr"))

# Eid al-Adha.
observed_dates.update(self._add_eid_al_adha_day("Eid al-Adha"))
dts_observed.update(self._add_eid_al_adha_day("Eid al-Adha"))

if self.observed:
for dt in sorted(observed_dates):
if not self._is_weekend(dt):
continue
dt_observed = dt + td(days=+1)
while self._is_weekend(dt_observed) or dt_observed in observed_dates:
dt_observed += td(days=+1)
for name in self.get_list(dt):
observed_dates.add(self._add_holiday("%s (Observed)" % name, dt_observed))

# observed holidays special cases
self._populate_observed(dts_observed)

# Observed holidays special cases.
if year == 2007:
self._add_holiday_jan_3("Eid al-Adha (Observed)")
self._add_holiday_jan_3(self.observed_label % "Eid al-Adha")


class AL(Albania):
Expand Down
39 changes: 21 additions & 18 deletions holidays/countries/angola.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,20 @@
# License: MIT (see LICENSE file)

from datetime import date
from datetime import timedelta as td
from gettext import gettext as tr
from typing import Tuple

from holidays.calendars.gregorian import AUG, SEP, DEC
from holidays.groups import ChristianHolidays, InternationalHolidays
from holidays.holiday_base import HolidayBase
from holidays.observed_holiday_base import (
ObservedHolidayBase,
TUE_TO_PREV_MON,
THU_TO_NEXT_FRI,
SUN_TO_NEXT_MON,
)


class Angola(HolidayBase, ChristianHolidays, InternationalHolidays):
class Angola(ObservedHolidayBase, ChristianHolidays, InternationalHolidays):
"""
References:
- https://en.wikipedia.org/wiki/Public_holidays_in_Angola
Expand All @@ -38,6 +43,8 @@ class Angola(HolidayBase, ChristianHolidays, InternationalHolidays):
country = "AO"
default_language = "pt_AO"
supported_languages = ("en_US", "pt_AO", "uk")
# %s (Observed).
observed_label = tr("%s (Ponte)")
special_holidays = {
# General Election Day.
2017: (AUG, 23, tr("Dia de eleições gerais")),
Expand All @@ -46,23 +53,19 @@ class Angola(HolidayBase, ChristianHolidays, InternationalHolidays):
def __init__(self, *args, **kwargs):
ChristianHolidays.__init__(self)
InternationalHolidays.__init__(self)
super().__init__(*args, **kwargs)
super().__init__(observed_rule=TUE_TO_PREV_MON + THU_TO_NEXT_FRI, *args, **kwargs)

def _add_observed(self, dt: date) -> None:
if not self.observed:
return None
# As per Law # #11/18, from 2018/9/10, when public holiday falls on Tuesday or Thursday,
# the Monday or Friday is also a holiday.
if dt >= date(2018, SEP, 10):
if self._is_tuesday(dt):
# Day off for %s.
self._add_holiday(self.tr("%s (Ponte)") % self[dt], dt + td(days=-1))
elif self._is_thursday(dt):
self._add_holiday(self.tr("%s (Ponte)") % self[dt], dt + td(days=+1))
def _is_observed(self, dt: date) -> bool:
# As per Law # 16/96, from 1996/9/27, when public holiday falls on Sunday,
# it rolls over to the following Monday.
elif dt >= date(1996, SEP, 27) and self._is_sunday(dt):
self._add_holiday(self.tr("%s (Ponte)") % self[dt], dt + td(days=+1))
return dt >= date(1996, SEP, 27)

def _add_observed(self, dt: date, *args) -> Tuple[bool, date]:
# As per Law # #11/18, from 2018/9/10, when public holiday falls on Tuesday or Thursday,
# the Monday or Friday is also a holiday.
return super()._add_observed(
dt, rule=SUN_TO_NEXT_MON if dt < date(2018, SEP, 10) else self._observed_rule
)

def _populate(self, year):
# Decree #5/75.
Expand All @@ -78,7 +81,7 @@ def _populate(self, year):
self._add_observed(dt)

if self.observed and self._is_monday(DEC, 31) and year >= 2018:
self._add_holiday(self.tr("%s (Ponte)") % name, DEC, 31)
self._add_holiday_dec_31(self.tr(self.observed_label) % name)

# Law #16/96.
if 1997 <= year <= 2011:
Expand Down
61 changes: 19 additions & 42 deletions holidays/countries/argentina.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,19 @@
# Website: https://github.com/dr-prodigy/python-holidays
# License: MIT (see LICENSE file)

from datetime import date
from gettext import gettext as tr

from holidays.calendars.gregorian import (
JAN,
FEB,
MAR,
APR,
MAY,
JUN,
JUL,
AUG,
SEP,
OCT,
NOV,
DEC,
MON,
_get_nth_weekday_from,
)
from holidays.calendars.gregorian import JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC
from holidays.groups import ChristianHolidays, InternationalHolidays
from holidays.holiday_base import HolidayBase
from holidays.observed_holiday_base import (
ObservedHolidayBase,
THU_TO_NEXT_MON,
TUE_WED_TO_PREV_MON,
THU_FRI_TO_NEXT_MON,
)


class Argentina(HolidayBase, ChristianHolidays, InternationalHolidays):
class Argentina(ObservedHolidayBase, ChristianHolidays, InternationalHolidays):
"""
A subclass of :py:class:`HolidayBase` representing public holidays
in Argentina.
Expand Down Expand Up @@ -69,11 +58,20 @@ class Argentina(HolidayBase, ChristianHolidays, InternationalHolidays):
https://servicios.lanacion.com.ar/app-mobile/feriados/2017
https://servicios.lanacion.com.ar/app-mobile/feriados/2016
https://servicios.lanacion.com.ar/app-mobile/feriados/2015
Movable Holidays Laws:
- Decreto 1584/2010: 2010-11-03
- AUG 17, OCT 12, NOV 20 Holidays will always be on MON
- Decreto 52/2017: 2017-01-23 (Reconfirmed in Ley 27399)
- If TUE/WED - observed on previous MON
- If THU/FRI - observed on next MON
"""

country = "AR"
default_language = "es"
supported_languages = ("en_US", "es", "uk")
# %s (Observed).
observed_label = tr("%s (Observado)")

# Special Bridge Holidays are given upto 3 days a year
# as long as it's declared 50 days before calendar year's end
Expand Down Expand Up @@ -166,27 +164,7 @@ class Argentina(HolidayBase, ChristianHolidays, InternationalHolidays):
def __init__(self, *args, **kwargs):
ChristianHolidays.__init__(self)
InternationalHolidays.__init__(self)
super().__init__(*args, **kwargs)

def _move_holiday(self, dt: date) -> None:
"""
Movable Holidays Laws:
- Decreto 1584/2010: 2010-11-03
- AUG 17, OCT 12, NOV 20 Holidays will always be on MON
- Decreto 52/2017: 2017-01-23 (Reconfirmed in Ley 27399)
- If TUE/WED - observed on previous MON
- If THU/FRI - observed on next MON
"""
if self.observed:
dt_observed = None
if self._is_tuesday(dt) or self._is_wednesday(dt):
dt_observed = _get_nth_weekday_from(-1, MON, dt)
elif self._is_thursday(dt) or self._is_friday(dt):
dt_observed = _get_nth_weekday_from(+1, MON, dt)

if dt_observed:
self._add_holiday(self.tr("%s (Observado)") % self[dt], dt_observed)
self.pop(dt)
super().__init__(observed_rule=TUE_WED_TO_PREV_MON + THU_FRI_TO_NEXT_MON, *args, **kwargs)

def _populate(self, year):
super()._populate(year)
Expand Down Expand Up @@ -301,8 +279,7 @@ def _populate(self, year):
)
# If Jun 17 is Friday, then it should move to Mon, Jun 20
# but Jun 20 is Gen. Belgrano holiday
if not self._is_friday(jun_17):
self._move_holiday(jun_17)
self._move_holiday(jun_17, rule=TUE_WED_TO_PREV_MON + THU_TO_NEXT_MON)

# Status: In-Use.
# Started in 1938 via Ley 12387 on Aug 17.
Expand Down
34 changes: 16 additions & 18 deletions holidays/countries/australia.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,22 @@

from holidays.calendars.gregorian import APR, AUG, SEP, OCT, FRI, _get_nth_weekday_from
from holidays.groups import ChristianHolidays, InternationalHolidays
from holidays.holiday_base import HolidayBase
from holidays.observed_holiday_base import (
ObservedHolidayBase,
SUN_TO_NEXT_MON,
SAT_SUN_TO_NEXT_MON,
SAT_SUN_TO_NEXT_MON_TUE,
)


class Australia(HolidayBase, ChristianHolidays, InternationalHolidays):
class Australia(ObservedHolidayBase, ChristianHolidays, InternationalHolidays):
"""
References:
- https://www.qld.gov.au/recreation/travel/holidays
"""

country = "AU"
observed_label = "%s (Observed)"
special_holidays = {
2022: (SEP, 22, "National Day of Mourning for Queen Elizabeth II"),
}
Expand All @@ -41,15 +47,7 @@ def sovereign_birthday(self) -> str:
def __init__(self, *args, **kwargs):
ChristianHolidays.__init__(self)
InternationalHolidays.__init__(self)
super().__init__(*args, **kwargs)

def _add_observed(self, dt: date, include_sat: bool = True, days: int = +1) -> None:
if not self.observed:
return None
if self._is_sunday(dt) or (include_sat and self._is_saturday(dt)):
self._add_holiday(
"%s (Observed)" % self[dt], dt + td(days=+2 if self._is_saturday(dt) else days)
)
super().__init__(observed_rule=SAT_SUN_TO_NEXT_MON, *args, **kwargs)

def _populate(self, year):
super()._populate(year)
Expand Down Expand Up @@ -98,11 +96,11 @@ def _add_subdiv_holidays(self):
self._add_holiday_apr_25("Anzac Day")

# Christmas Day
self._add_observed(self._add_christmas_day("Christmas Day"), days=+2)
self._add_observed(self._add_christmas_day("Christmas Day"), rule=SAT_SUN_TO_NEXT_MON_TUE)

# Boxing Day
name = "Proclamation Day" if self.subdiv == "SA" else "Boxing Day"
self._add_observed(self._add_christmas_day_two(name), days=+2)
self._add_observed(self._add_christmas_day_two(name), rule=SAT_SUN_TO_NEXT_MON_TUE)

super()._add_subdiv_holidays()

Expand All @@ -120,7 +118,7 @@ def _add_subdiv_act_holidays(self):

# Anzac Day
if self._year >= 1921:
self._add_observed(date(self._year, APR, 25), include_sat=False)
self._add_observed(date(self._year, APR, 25), rule=SUN_TO_NEXT_MON)

# Canberra Day
# Info from https://www.timeanddate.com/holidays/australia/canberra-day
Expand Down Expand Up @@ -217,15 +215,15 @@ def _add_subdiv_qld_holidays(self):

# Anzac Day
if self._year >= 1921:
self._add_observed(date(self._year, APR, 25), include_sat=False)
self._add_observed((APR, 25), rule=SUN_TO_NEXT_MON)

# The Royal Queensland Show (Ekka)
# The Show starts on the first Friday of August - providing this is
# not prior to the 5th - in which case it will begin on the second
# Friday. The Wednesday during the show is a public holiday.
ekka_dates = {
2020: date(2020, AUG, 14),
2021: date(2021, OCT, 29),
2020: (AUG, 14),
2021: (OCT, 29),
}
name = "The Royal Queensland Show"
if self._year in ekka_dates:
Expand All @@ -248,7 +246,7 @@ def _add_subdiv_sa_holidays(self):

# Anzac Day
if self._year >= 1921:
self._add_observed(date(self._year, APR, 25), include_sat=False)
self._add_observed((APR, 25), rule=SUN_TO_NEXT_MON)

# Adelaide Cup
name = "Adelaide Cup"
Expand Down
Loading

0 comments on commit 14a8cfa

Please sign in to comment.