diff --git a/cloudflare/src/calendar.js b/cloudflare/src/calendar.js index f2718a6..880d41b 100644 --- a/cloudflare/src/calendar.js +++ b/cloudflare/src/calendar.js @@ -121,18 +121,24 @@ export function buildEntries(config, startYear, endYear) { date = getNthWeekday(year, holiday.month, holiday.weekday, holiday.nth); } + const holidayEntries = [{ name: holiday.name, date }]; if (holiday.observed) { - date = adjustForObservance(date); + const observedDate = adjustForObservance(date); + if (observedDate.getTime() !== date.getTime()) { + holidayEntries.push({ name: `${holiday.name} (Observed)`, date: observedDate }); + } } - if (!isDateInRange(date, startDate, endDate)) { - continue; - } + for (const entry of holidayEntries) { + if (!isDateInRange(entry.date, startDate, endDate)) { + continue; + } - const dedupeKey = `${holiday.name}:${formatIsoDate(date)}`; - if (!seen.has(dedupeKey)) { - seen.add(dedupeKey); - entries.push({ name: holiday.name, date }); + const dedupeKey = `${entry.name}:${formatIsoDate(entry.date)}`; + if (!seen.has(dedupeKey)) { + seen.add(dedupeKey); + entries.push(entry); + } } } diff --git a/devbox.json b/devbox.json index 7e9d565..91d4fc5 100644 --- a/devbox.json +++ b/devbox.json @@ -7,7 +7,7 @@ "shell": { "init_hook": [ "poetry install", - "poetry shell" + "eval \"$(poetry env activate)\"" ] } } diff --git a/devbox.lock b/devbox.lock index 7700030..eb99d6a 100644 --- a/devbox.lock +++ b/devbox.lock @@ -6,7 +6,7 @@ }, "poetry@latest": { "last_modified": "2025-03-22T01:19:59Z", - "plugin_version": "0.0.4", + "plugin_version": "0.0.5", "resolved": "github:NixOS/nixpkgs/0740f6f238767d4caf9afe774d3e88105766dfc6#poetry", "source": "devbox-search", "version": "2.1.1", @@ -71,7 +71,7 @@ }, "python@latest": { "last_modified": "2025-03-11T17:52:14Z", - "plugin_version": "0.0.4", + "plugin_version": "0.0.5", "resolved": "github:NixOS/nixpkgs/0d534853a55b5d02a4ababa1d71921ce8f0aee4c#python313", "source": "devbox-search", "version": "3.13.2", diff --git a/package-lock.json b/package-lock.json index fe6f854..974c6c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1412,7 +1412,6 @@ "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "pathe": "^2.0.3" } @@ -1424,7 +1423,6 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "bin": { "workerd": "bin/workerd" }, diff --git a/src/generate_calendar/__init__.py b/src/generate_calendar/__init__.py index d832af0..0fa37d0 100644 --- a/src/generate_calendar/__init__.py +++ b/src/generate_calendar/__init__.py @@ -248,19 +248,25 @@ def build_holiday_entries( for holiday in year_holidays: holiday_name = cast(str, holiday["name"]) holiday_date = cast(calendar_date, holiday["date"]) + entries = [(holiday_name, holiday_date)] if holiday.get("observed", False): - holiday_date = adjust_for_observance(holiday_date) - - if holiday_date < range_start or holiday_date > range_end: - continue - - holiday_key = (holiday_name, holiday_date) - if holiday_key in seen: - logger.warning("Skipping duplicate holiday definition for %s on %s", *holiday_key) - continue - - seen.add(holiday_key) - holidays.append({"name": holiday_name, "date": holiday_date}) + observed_date = adjust_for_observance(holiday_date) + if observed_date != holiday_date: + entries.append((f"{holiday_name} (Observed)", observed_date)) + + for entry_name, entry_date in entries: + if entry_date < range_start or entry_date > range_end: + continue + + holiday_key = (entry_name, entry_date) + if holiday_key in seen: + logger.warning( + "Skipping duplicate holiday definition for %s on %s", *holiday_key + ) + continue + + seen.add(holiday_key) + holidays.append({"name": entry_name, "date": entry_date}) return holidays diff --git a/tests/test_generate_calendar.py b/tests/test_generate_calendar.py index 75721e7..2317258 100644 --- a/tests/test_generate_calendar.py +++ b/tests/test_generate_calendar.py @@ -85,17 +85,35 @@ def test_build_holiday_entries_rejects_inverted_year_range() -> None: build_holiday_entries(2026, 2025) -def test_build_holiday_entries_observes_juneteenth() -> None: - holidays = build_holiday_entries(2027, 2027) +@pytest.mark.parametrize( + ("holiday_name", "start_year", "end_year", "actual_date", "observed_date"), + [ + ("New Year's Day", 2021, 2022, date(2022, 1, 1), date(2021, 12, 31)), + ("Juneteenth", 2027, 2027, date(2027, 6, 19), date(2027, 6, 18)), + ("Independence Day", 2026, 2026, date(2026, 7, 4), date(2026, 7, 3)), + ("Veterans Day", 2023, 2023, date(2023, 11, 11), date(2023, 11, 10)), + ("Christmas Day", 2022, 2022, date(2022, 12, 25), date(2022, 12, 26)), + ], +) +def test_build_holiday_entries_keeps_actual_holiday_when_observed_date_differs( + holiday_name: str, + start_year: int, + end_year: int, + actual_date: date, + observed_date: date, +) -> None: + holidays = build_holiday_entries(start_year, end_year) - assert {"name": "Juneteenth", "date": date(2027, 6, 18)} in holidays + assert {"name": holiday_name, "date": actual_date} in holidays + assert {"name": f"{holiday_name} (Observed)", "date": observed_date} in holidays + assert {"name": holiday_name, "date": observed_date} not in holidays def test_build_holiday_entries_filters_observed_dates_by_actual_calendar_year() -> None: holidays = build_holiday_entries(2021, 2021) assert {"name": "New Year's Day", "date": date(2021, 1, 1)} in holidays - assert {"name": "New Year's Day", "date": date(2021, 12, 31)} in holidays + assert {"name": "New Year's Day (Observed)", "date": date(2021, 12, 31)} in holidays def test_build_holiday_entries_skips_february_29_on_non_leap_years(tmp_path: Path) -> None: