Skip to content

Commit

Permalink
Refactor some holidays with uncommon rules (#1807)
Browse files Browse the repository at this point in the history
Co-authored-by: Arkadii Yakovets <2201626+arkid15r@users.noreply.github.com>
  • Loading branch information
KJhellico and arkid15r committed May 29, 2024
1 parent f541513 commit c2f31db
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 105 deletions.
28 changes: 19 additions & 9 deletions holidays/countries/botswana.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@

from holidays.calendars.gregorian import JUL
from holidays.groups import ChristianHolidays, InternationalHolidays, StaticHolidays
from holidays.observed_holiday_base import ObservedHolidayBase, SUN_TO_NEXT_MON, SUN_TO_NEXT_TUE
from holidays.observed_holiday_base import (
ObservedHolidayBase,
SAT_TO_NEXT_MON,
SUN_TO_NEXT_MON,
SUN_TO_NEXT_TUE,
)


class Botswana(ObservedHolidayBase, ChristianHolidays, InternationalHolidays, StaticHolidays):
Expand Down Expand Up @@ -41,16 +46,19 @@ def _populate_public_holidays(self):
self._add_observed(self._add_new_years_day("New Year's Day"), rule=SUN_TO_NEXT_TUE)
self._add_observed(self._add_new_years_day_two("New Year's Day Holiday"))

# Easter and easter related calculations
self._add_good_friday("Good Friday")

self._add_holy_saturday("Holy Saturday")

self._add_easter_monday("Easter Monday")
self._add_ascension_thursday("Ascension Day")

may_1 = self._add_labor_day("Labour Day")
self._add_observed(may_1)
if self.observed and self._year >= 2016 and self._is_saturday(may_1):
self._add_labor_day_three("Labour Day Holiday")
self._add_observed(may_1 := self._add_labor_day("Labour Day"))
if self._year >= 2016:
self._add_observed(
may_1, name="Labour Day Holiday", rule=SAT_TO_NEXT_MON, show_observed_label=False
)

self._add_ascension_thursday("Ascension Day")

self._add_observed(self._add_holiday_jul_1("Sir Seretse Khama Day"))

Expand All @@ -63,8 +71,10 @@ def _populate_public_holidays(self):
self._add_observed(self._add_christmas_day("Christmas Day"), rule=SUN_TO_NEXT_TUE)
self._add_observed(dec_26 := self._add_christmas_day_two("Boxing Day"))

if self.observed and self._year >= 2016 and self._is_saturday(dec_26):
self._add_holiday_dec_28("Boxing Day Holiday")
if self._year >= 2016:
self._add_observed(
dec_26, name="Boxing Day Holiday", rule=SAT_TO_NEXT_MON, show_observed_label=False
)


class BW(Botswana):
Expand Down
25 changes: 14 additions & 11 deletions holidays/countries/chile.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@
from holidays.groups import ChristianHolidays, InternationalHolidays, StaticHolidays
from holidays.observed_holiday_base import (
ObservedHolidayBase,
MON_ONLY,
MON_FRI_ONLY,
TUE_TO_PREV_FRI,
WED_TO_NEXT_FRI,
FRI_ONLY,
WORKDAY_TO_NEAREST_MON,
)

Expand Down Expand Up @@ -84,9 +87,9 @@ def _populate_public_holidays(self):
return None

# New Year's Day.
jan_1 = self._add_new_years_day(tr("Año Nuevo"))
if self._year >= 2017 and self._is_sunday(jan_1):
self._add_new_years_day_two(tr("Feriado nacional"))
self._add_new_years_day(tr("Año Nuevo"))
if self._year >= 2017:
self._add_observed(self._add_new_years_day_two(tr("Feriado nacional")), rule=MON_ONLY)

# Good Friday.
self._add_good_friday(tr("Viernes Santo"))
Expand Down Expand Up @@ -141,21 +144,21 @@ def _populate_public_holidays(self):
tr("Día de la Unidad Nacional")
)

if self._year >= 2017 and self._is_saturday(SEP, 18):
# National Holiday.
self._add_holiday_sep_17(tr("Fiestas Patrias"))

if self._year >= 2007 and self._is_tuesday(SEP, 18):
self._add_holiday_sep_17(tr("Fiestas Patrias"))
if self._year >= 2007:
self._add_observed(
# National Holiday.
self._add_holiday_sep_17(tr("Fiestas Patrias")),
rule=MON_FRI_ONLY if self._year >= 2017 else MON_ONLY,
)

# Independence Day.
self._add_holiday_sep_18(tr("Día de la Independencia"))

# Army Day.
self._add_holiday_sep_19(tr("Día de las Glorias del Ejército"))

if self._year >= 2008 and self._is_thursday(SEP, 19):
self._add_holiday_sep_20(tr("Fiestas Patrias"))
if self._year >= 2008:
self._add_observed(self._add_holiday_sep_20(tr("Fiestas Patrias")), rule=FRI_ONLY)

if 1932 <= self._year <= 1944:
self._add_holiday_sep_20(tr("Fiestas Patrias"))
Expand Down
11 changes: 3 additions & 8 deletions holidays/countries/isle_of_man.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@
# Website: https://github.com/vacanza/python-holidays
# License: MIT (see LICENSE file)

from datetime import date

from holidays.calendars.gregorian import JUL
from holidays.countries.united_kingdom import UnitedKingdom, UnitedKingdomStaticHolidays
from holidays.groups import ChristianHolidays, InternationalHolidays, StaticHolidays
from holidays.observed_holiday_base import ObservedHolidayBase, SAT_SUN_TO_NEXT_MON
Expand Down Expand Up @@ -50,11 +47,9 @@ def _populate_public_holidays(self) -> None:

# Tynwald Day
# Move to the next Monday if falls on a weekend.
dt = date(self._year, JUL, 5)
self._add_holiday(
"Tynwald Day",
self._get_observed_date(dt, SAT_SUN_TO_NEXT_MON) if self._year >= 1992 else dt,
)
jul_5 = self._add_holiday_jul_5("Tynwald Day")
if self._year >= 1992:
self._move_holiday(jul_5, show_observed_label=False)


class IM(IsleOfMan):
Expand Down
16 changes: 9 additions & 7 deletions holidays/countries/jersey.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
ObservedHolidayBase,
SAT_SUN_TO_NEXT_WORKDAY,
SUN_TO_NEXT_WORKDAY,
SAT_TO_NONE,
SUN_TO_NONE,
)


Expand Down Expand Up @@ -139,14 +141,12 @@ def _populate_public_holidays(self) -> None:
# Liberation Day.
# Started in 1952. This has no in-lieus.
# Counts as Public Holiday when fall on the weekdays, also on Saturday from 2010 onwards.
# Specially held in 2010 on Sunday for the 65th Anniversary.

# Liberation Day
liberation_day = self._add_holiday_may_9("Liberation Day")
if (self._is_sunday(liberation_day) and self._year != 2010) or (
self._is_saturday(liberation_day) and self._year <= 2010
):
self.pop(liberation_day)
self._add_observed(
# Liberation Day
self._add_holiday_may_9("Liberation Day"),
rule=SAT_TO_NONE + SUN_TO_NONE if self._year <= 2010 else SUN_TO_NONE,
)


class JE(Jersey):
Expand Down Expand Up @@ -207,6 +207,8 @@ class JerseyStaticHolidays:
1999: (DEC, 31, "Millennium Celebrations"),
2001: (JUL, 13, elizabeth_2_royal_visit),
2002: (JUN, 3, "Golden Jubilee of Elizabeth II"),
# Specially held in 2010 on Sunday for the 65th Anniversary.
2010: (MAY, 9, "Liberation Day"),
2011: (APR, 29, "Wedding of William and Catherine"),
2012: (JUN, 5, "Diamond Jubilee of Elizabeth II"),
2020: (MAY, 8, "75th Anniversary of VE Day"),
Expand Down
10 changes: 4 additions & 6 deletions holidays/countries/monaco.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from gettext import gettext as tr

from holidays.calendars.gregorian import JAN, DEC
from holidays.calendars.gregorian import JAN
from holidays.groups import ChristianHolidays, InternationalHolidays, StaticHolidays
from holidays.observed_holiday_base import ObservedHolidayBase, SUN_TO_NEXT_MON

Expand Down Expand Up @@ -68,11 +68,9 @@ def _populate_public_holidays(self):
self._add_observed(self._add_holiday_nov_19(tr("La Fête du Prince")))

# Immaculate Conception.
name = tr("L'Immaculée Conception")
if self._year >= 2019 and self._is_sunday(DEC, 8):
self._add_holiday_dec_9(name)
else:
self._add_holiday_dec_8(name)
dec_8 = self._add_immaculate_conception_day(tr("L'Immaculée Conception"))
if self._year >= 2019:
self._move_holiday(dec_8, show_observed_label=False)

# Christmas Day.
self._add_observed(self._add_christmas_day(tr("Noël")))
Expand Down
28 changes: 14 additions & 14 deletions holidays/countries/switzerland.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
from holidays.calendars.gregorian import APR, THU, _timedelta, _get_nth_weekday_of_month
from holidays.constants import HALF_DAY, OPTIONAL, PUBLIC
from holidays.groups import ChristianHolidays, InternationalHolidays
from holidays.holiday_base import HolidayBase
from holidays.observed_holiday_base import ObservedHolidayBase, MON_ONLY, TUE_TO_NONE, SAT_TO_NONE


class Switzerland(HolidayBase, ChristianHolidays, InternationalHolidays):
class Switzerland(ObservedHolidayBase, ChristianHolidays, InternationalHolidays):
"""
References:
- https://www.bj.admin.ch/dam/bj/de/data/publiservice/service/zivilprozessrecht/kant-feiertage.pdf # noqa: E501
Expand Down Expand Up @@ -115,8 +115,9 @@ def _populate_subdiv_ar_public_holidays(self):

self._add_whit_monday(tr("Pfingstmontag"))

if not self._is_monday(self._christmas_day) and not self._is_friday(self._christmas_day):
self._add_christmas_day_two(tr("Stephanstag"))
self._add_observed(
self._add_christmas_day_two(tr("Stephanstag")), rule=TUE_TO_NONE + SAT_TO_NONE
)

def _populate_subdiv_ai_public_holidays(self):
self._add_good_friday(tr("Karfreitag"))
Expand All @@ -133,8 +134,9 @@ def _populate_subdiv_ai_public_holidays(self):

self._add_immaculate_conception_day(tr("Mariä Empfängnis"))

if not self._is_monday(self._christmas_day) and not self._is_friday(self._christmas_day):
self._add_christmas_day_two(tr("Stephanstag"))
self._add_observed(
self._add_christmas_day_two(tr("Stephanstag")), rule=TUE_TO_NONE + SAT_TO_NONE
)

def _populate_subdiv_bl_public_holidays(self):
self._add_good_friday(tr("Karfreitag"))
Expand Down Expand Up @@ -267,10 +269,8 @@ def _populate_subdiv_lu_public_holidays(self):
self._add_christmas_day_two(tr("Stephanstag"))

def _populate_subdiv_ne_public_holidays(self):
dt = self._add_new_years_day_two(tr("Berchtoldstag"))
# Jan 2 is public holiday only when it falls on Monday (Jan 1 falls on Sunday).
if not self._is_monday(dt):
self.pop(dt)
# Jan 2 is public holiday only when it falls on Monday.
self._add_observed(self._add_new_years_day_two(tr("Berchtoldstag")), rule=MON_ONLY)

# Republic Day.
self._add_holiday_mar_1(tr("Jahrestag der Ausrufung der Republik"))
Expand All @@ -281,8 +281,7 @@ def _populate_subdiv_ne_public_holidays(self):

self._add_corpus_christi_day(tr("Fronleichnam"))

if self._is_sunday(self._christmas_day):
self._add_christmas_day_two(tr("Stephanstag"))
self._add_observed(self._add_christmas_day_two(tr("Stephanstag")), rule=MON_ONLY)

def _populate_subdiv_nw_public_holidays(self):
# St. Joseph's Day.
Expand Down Expand Up @@ -445,8 +444,9 @@ def _populate_subdiv_ur_public_holidays(self):

self._add_immaculate_conception_day(tr("Mariä Empfängnis"))

if not self._is_monday(self._christmas_day) and not self._is_friday(self._christmas_day):
self._add_christmas_day_two(tr("Stephanstag"))
self._add_observed(
self._add_christmas_day_two(tr("Stephanstag")), rule=TUE_TO_NONE + SAT_TO_NONE
)

def _populate_subdiv_vd_public_holidays(self):
self._add_new_years_day_two(tr("Berchtoldstag"))
Expand Down
67 changes: 42 additions & 25 deletions holidays/observed_holiday_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ def __add__(self, other):
# Observance calculation rules: +7 - next workday, -7 - previous workday.
# Single days.
MON_TO_NEXT_TUE = ObservedRule({MON: +1})
MON_ONLY = ObservedRule({TUE: None, WED: None, THU: None, FRI: None, SAT: None, SUN: None})

TUE_TO_PREV_MON = ObservedRule({TUE: -1})
TUE_TO_PREV_FRI = ObservedRule({TUE: -4})
TUE_TO_NONE = ObservedRule({TUE: None})

WED_TO_PREV_MON = ObservedRule({WED: -2})
WED_TO_NEXT_FRI = ObservedRule({WED: +2})
Expand All @@ -45,6 +47,7 @@ def __add__(self, other):
FRI_TO_NEXT_TUE = ObservedRule({FRI: +4})
FRI_TO_NEXT_SAT = ObservedRule({FRI: +1})
FRI_TO_NEXT_WORKDAY = ObservedRule({FRI: +7})
FRI_ONLY = ObservedRule({MON: None, TUE: None, WED: None, THU: None, SAT: None, SUN: None})

SAT_TO_PREV_THU = ObservedRule({SAT: -2})
SAT_TO_PREV_FRI = ObservedRule({SAT: -1})
Expand All @@ -59,6 +62,7 @@ def __add__(self, other):
SUN_TO_NEXT_TUE = ObservedRule({SUN: +2})
SUN_TO_NEXT_WED = ObservedRule({SUN: +3})
SUN_TO_NEXT_WORKDAY = ObservedRule({SUN: +7})
SUN_TO_NONE = ObservedRule({SUN: None})

# Multiple days.
ALL_TO_NEAREST_MON = ObservedRule({TUE: -1, WED: -2, THU: -3, FRI: +3, SAT: +2, SUN: +1})
Expand All @@ -70,6 +74,8 @@ def __add__(self, other):
WORKDAY_TO_NEXT_MON = ObservedRule({TUE: +6, WED: +5, THU: +4, FRI: +3})
WORKDAY_TO_NEXT_WORKDAY = ObservedRule({MON: +7, TUE: +7, WED: +7, THU: +7, FRI: +7})

MON_FRI_ONLY = ObservedRule({TUE: None, WED: None, THU: None, SAT: None, SUN: None})

TUE_WED_TO_PREV_MON = ObservedRule({TUE: -1, WED: -2})
TUE_WED_THU_TO_PREV_MON = ObservedRule({TUE: -1, WED: -2, THU: -3})

Expand All @@ -96,10 +102,11 @@ class ObservedHolidayBase(HolidayBase):

observed_label = "%s"

def __init__(self, observed_rule: ObservedRule, observed_since: int = None, *args, **kwargs):
self._observed_rule = observed_rule
def __init__(
self, observed_rule: ObservedRule = None, observed_since: int = None, *args, **kwargs
):
self._observed_rule = observed_rule or ObservedRule()
self._observed_since = observed_since

super().__init__(*args, **kwargs)

def _is_observed(self, *args, **kwargs) -> bool:
Expand Down Expand Up @@ -129,7 +136,11 @@ def _get_observed_date(self, dt: date, rule: ObservedRule) -> Optional[date]:
return dt

def _add_observed(
self, dt: DateArg, name: Optional[str] = None, rule: Optional[ObservedRule] = None
self,
dt: DateArg,
name: Optional[str] = None,
rule: Optional[ObservedRule] = None,
show_observed_label: bool = True,
) -> Tuple[bool, Optional[date]]:
dt = dt if isinstance(dt, date) else date(self._year, *dt)

Expand All @@ -145,34 +156,40 @@ def _add_observed(
self.pop(dt)
return False, None

estimated_label = self.tr(getattr(self, "estimated_label", ""))
observed_label = self.tr(
getattr(
self,
"observed_label_before" if dt_observed < dt else "observed_label",
self.observed_label,
if show_observed_label:
estimated_label = self.tr(getattr(self, "estimated_label", ""))
observed_label = self.tr(
getattr(
self,
"observed_label_before" if dt_observed < dt else "observed_label",
self.observed_label,
)
)
)

estimated_label_text = estimated_label.strip("%s ()")
# Use observed_estimated_label instead of observed_label for estimated dates.
for name in (name,) if name else self.get_list(dt):
holiday_name = self.tr(name)
observed_estimated_label = None
if len(estimated_label_text) > 0 and estimated_label_text in holiday_name:
holiday_name = holiday_name.replace(f"({estimated_label_text})", "").strip()
observed_estimated_label = self.tr(getattr(self, "observed_estimated_label"))

super()._add_holiday(
(observed_estimated_label or observed_label) % holiday_name, dt_observed
)
estimated_label_text = estimated_label.strip("%s ()")
# Use observed_estimated_label instead of observed_label for estimated dates.
for name in (name,) if name else self.get_list(dt):
holiday_name = self.tr(name)
observed_estimated_label = None
if len(estimated_label_text) > 0 and estimated_label_text in holiday_name:
holiday_name = holiday_name.replace(f"({estimated_label_text})", "").strip()
observed_estimated_label = self.tr(getattr(self, "observed_estimated_label"))

super()._add_holiday(
(observed_estimated_label or observed_label) % holiday_name, dt_observed
)
else:
for name in (name,) if name else self.get_list(dt):
super()._add_holiday(name, dt_observed)

return True, dt_observed

def _move_holiday(
self, dt: date, rule: Optional[ObservedRule] = None
self, dt: date, rule: Optional[ObservedRule] = None, show_observed_label: bool = True
) -> Tuple[bool, Optional[date]]:
is_observed, dt_observed = self._add_observed(dt, rule=rule)
is_observed, dt_observed = self._add_observed(
dt, rule=rule, show_observed_label=show_observed_label
)
if is_observed:
self.pop(dt)
return is_observed, dt_observed if is_observed else dt
Expand Down
Loading

0 comments on commit c2f31db

Please sign in to comment.