diff --git a/holidays/countries/south_korea.py b/holidays/countries/south_korea.py index 2034f516e..a461a5ab5 100644 --- a/holidays/countries/south_korea.py +++ b/holidays/countries/south_korea.py @@ -13,24 +13,10 @@ from datetime import date from datetime import timedelta as td from gettext import gettext as tr +from typing import Dict, Set from holidays.calendars import _CustomChineseHolidays -from holidays.calendars.gregorian import ( - JAN, - FEB, - MAR, - APR, - MAY, - JUN, - JUL, - AUG, - SEP, - OCT, - NOV, - DEC, - SAT, - SUN, -) +from holidays.calendars.gregorian import JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC from holidays.constants import BANK, PUBLIC from holidays.groups import ( ChineseCalendarHolidays, @@ -38,7 +24,11 @@ InternationalHolidays, StaticHolidays, ) -from holidays.observed_holiday_base import ObservedHolidayBase, SAT_SUN_TO_NEXT_MON +from holidays.observed_holiday_base import ( + ObservedHolidayBase, + SAT_SUN_TO_NEXT_WORKDAY, + SUN_TO_NEXT_WORKDAY, +) class SouthKorea( @@ -95,66 +85,48 @@ def __init__(self, *args, **kwargs): ChristianHolidays.__init__(self) InternationalHolidays.__init__(self) StaticHolidays.__init__(self, cls=SouthKoreaStaticHolidays) - kwargs.setdefault("observed_rule", SAT_SUN_TO_NEXT_MON) + kwargs.setdefault("observed_rule", SAT_SUN_TO_NEXT_WORKDAY) + kwargs.setdefault("observed_since", 2014) super().__init__(*args, **kwargs) - def _add_alt_holiday( - self, dt: date, name: str = "", since: int = 2014, include_sat: bool = True - ) -> None: - """ - Add alternative holiday on first day from the date provided - that's not already a another holiday nor a weekend. - - :param dt: - The date of the holiday. - - :param name: - The name of the holiday. - - :param since: - Year starting from which alt holiday should be added - - :param include_sat: - Whether Saturday is to be considered a weekend in addition to - Sunday. - """ - if not self.observed: - return None - - target_weekday = {SUN} - if include_sat: - target_weekday.add(SAT) - if (dt.weekday() in target_weekday or len(self.get_list(dt)) > 1) and dt.year >= since: - obs_date = dt + td(days=+1) - while obs_date.weekday() in target_weekday or obs_date in self: - obs_date += td(days=+1) - for name in (name,) if name else self.get_list(dt): - if "Alternative holiday" not in name: - self._add_holiday(self.tr(self.observed_label) % self.tr(name), obs_date) - - def _add_three_day_holiday(self, dt: date, name: str) -> None: - """ - Add holiday for the date before and after the given holiday date. - - :param dt: - The date of the holiday. - - :param name: - The name of the holiday. - """ - for dt_alt in ( - # The day preceding %s. - self._add_holiday(self.tr("%s 전날") % self.tr(name), dt + td(days=-1)), - dt, - # The second day of %s. - self._add_holiday(self.tr("%s 다음날") % self.tr(name), dt + td(days=+1)), - ): - self._add_alt_holiday(dt_alt, name=name, include_sat=False) # type: ignore[arg-type] + def _populate_observed(self, dts: Set[date], three_day_holidays: Dict[date, str]) -> None: + for dt in sorted(dts.union(three_day_holidays.keys())): + if not self._is_observed(dt): + continue + dt_observed = self._get_observed_date( + dt, SUN_TO_NEXT_WORKDAY if dt in three_day_holidays else SAT_SUN_TO_NEXT_WORKDAY + ) + if dt_observed != dt or len(self.get_list(dt)) > 1: + if dt_observed == dt: + dt_observed = self._get_next_workday(dt) + names = ( + (three_day_holidays[dt],) if dt in three_day_holidays else self.get_list(dt) + ) + for name in names: + self._add_holiday(self.tr(self.observed_label) % self.tr(name), dt_observed) def _populate_public_holidays(self): + def append_observed(dt: date, since: int): + if self._year >= since: + dts_observed.add(dt) + + def add_three_day_holiday(dt: date, name: str): + name = self.tr(name) + for dt_alt in ( + # The day preceding %s. + self._add_holiday(self.tr("%s 전날") % name, dt + td(days=-1)), + dt, + # The second day of %s. + self._add_holiday(self.tr("%s 다음날") % name, dt + td(days=+1)), + ): + three_days_holidays[dt_alt] = name + if self._year <= 1947: return None + dts_observed = set() + three_days_holidays = {} + # Fixed Date Holidays. # New Year's Day. @@ -175,12 +147,12 @@ def _populate_public_holidays(self): ) korean_new_year = self._add_chinese_new_years_day(name) if self._year >= 1989: - self._add_three_day_holiday(korean_new_year, name) + add_three_day_holiday(korean_new_year, name) # Independence Movement Day. mar_1 = self._add_holiday_mar_1(tr("삼일절")) # mar_1 is used later for Presidential Election Day. - self._add_alt_holiday(mar_1, since=2022) + append_observed(mar_1, 2022) if 1949 <= self._year <= 2005 and self._year != 1960: # Tree Planting Day. @@ -194,34 +166,34 @@ def _populate_public_holidays(self): # Buddha's Birthday. else tr("석가탄신일") ) - self._add_alt_holiday(self._add_chinese_birthday_of_buddha(name), since=2023) + append_observed(self._add_chinese_birthday_of_buddha(name), 2023) if self._year >= 1975: # Children's Day. - self._add_alt_holiday(self._add_holiday_may_5(tr("어린이날")), since=2015) + append_observed(self._add_holiday_may_5(tr("어린이날")), 2015) if self._year >= 1956: # Memorial Day. jun_6 = self._add_holiday_jun_6(tr("현충일")) - # jun_6 is used later for Local Election Day. + # jun_6 is used later for Local Election Day. if self._year <= 2007: # Constitution Day. self._add_holiday_jul_17(tr("제헌절")) # Liberation Day. - self._add_alt_holiday(self._add_holiday_aug_15(tr("광복절")), since=2021) + append_observed(self._add_holiday_aug_15(tr("광복절")), 2021) if 1976 <= self._year <= 1990: # Armed Forces Day. self._add_holiday_oct_1(tr("국군의 날")) # National Foundation Day. - self._add_alt_holiday(self._add_holiday_oct_3(tr("개천절")), since=2021) + append_observed(self._add_holiday_oct_3(tr("개천절")), 2021) if self._year <= 1990 or self._year >= 2013: # Hangul Day. - self._add_alt_holiday(self._add_holiday_oct_9(tr("한글날")), since=2021) + append_observed(self._add_holiday_oct_9(tr("한글날")), 2021) if 1950 <= self._year <= 1975: # United Nations Day. @@ -233,10 +205,10 @@ def _populate_public_holidays(self): if 1986 <= self._year <= 1988: self._add_mid_autumn_festival_day_two(self.tr("%s 다음날") % self.tr(name)) elif self._year >= 1989: - self._add_three_day_holiday(chuseok, name) + add_three_day_holiday(chuseok, name) # Christmas Day. - self._add_alt_holiday(self._add_christmas_day(tr("기독탄신일")), since=2023) + append_observed(self._add_christmas_day(tr("기독탄신일")), 2023) # Election Days since Sep 2006; excluding the 2017 Special Presidential Election Day. @@ -292,6 +264,9 @@ def _populate_public_holidays(self): else: self._add_holiday_1st_wed_of_jun(name) + if self.observed: + self._populate_observed(dts_observed, three_days_holidays) + def _populate_bank_holidays(self): if self._year <= 1947: return None diff --git a/holidays/observed_holiday_base.py b/holidays/observed_holiday_base.py index 00979ece6..fc04a9ce0 100644 --- a/holidays/observed_holiday_base.py +++ b/holidays/observed_holiday_base.py @@ -105,16 +105,20 @@ def __init__(self, observed_rule: ObservedRule, observed_since: int = None, *arg def _is_observed(self, *args, **kwargs) -> bool: return self._observed_since is None or self._year >= self._observed_since + def _get_next_workday(self, dt: date, delta: int = +1) -> date: + dt_work = dt + td(days=delta) + while dt_work.year == self._year: + if dt_work in self or self._is_weekend(dt_work): # type: ignore[operator] + dt_work += td(days=delta) + else: + return dt_work + return dt + def _get_observed_date(self, dt: date, rule: ObservedRule) -> date: delta = rule.get(dt.weekday(), 0) if delta != 0: if abs(delta) == 7: - delta //= 7 - dt += td(days=delta) - while dt.year == self._year and ( - dt in self or self._is_weekend(dt) # type: ignore[operator] - ): - dt += td(days=delta) + dt = self._get_next_workday(dt, delta // 7) else: dt += td(days=delta) return dt diff --git a/snapshots/countries/KR.json b/snapshots/countries/KR.json index 5218ac8c5..0f42e28e9 100644 --- a/snapshots/countries/KR.json +++ b/snapshots/countries/KR.json @@ -1513,8 +1513,8 @@ "2036-04-09": "National Assembly Election Day", "2036-05-01": "Workers' Day", "2036-05-03": "Buddha's Birthday", - "2036-05-05": "Alternative holiday for Buddha's Birthday; Children's Day", - "2036-05-06": "Alternative holiday for Children's Day", + "2036-05-05": "Children's Day", + "2036-05-06": "Alternative holiday for Buddha's Birthday", "2036-06-06": "Memorial Day", "2036-08-15": "Liberation Day", "2036-10-03": "National Foundation Day; The day preceding Chuseok",