Skip to content

Commit

Permalink
Migrate South Korea holidays to ObservedHolidayBase (#1560)
Browse files Browse the repository at this point in the history
  • Loading branch information
KJhellico authored Nov 24, 2023
1 parent aa78057 commit 225a471
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 88 deletions.
135 changes: 55 additions & 80 deletions holidays/countries/south_korea.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,22 @@
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,
ChristianHolidays,
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(
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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.

Expand Down Expand Up @@ -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
Expand Down
16 changes: 10 additions & 6 deletions holidays/observed_holiday_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions snapshots/countries/KR.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 225a471

Please sign in to comment.