Generalize movable-feast anchors beyond Easter offsets, migrate Christ the King
Deploy / deploy (push) Successful in 1m18s

Extends TemporalFeastRecord's date anchor from a single easterOffset
number into a MovableAnchor union: 'easter' (unchanged), plus
'nth-sunday-of-month' (nth: -1 for "last") and 'nth-sunday-of-advent' --
both new anchor kinds resolve to "Nth Sunday counting from an anchor
date," just with a different anchor (first-of-month vs. Advent's own
start, which is itself always a Sunday). Adds calendar/temporal.ts's
sundayOnOrAfter to support the month case.

Migrates christ-the-king.yml onto the new nth-sunday-of-month anchor
(month: 10, nth: -1) with a new `unconditional: true` flag replacing its
old always-wins-no-rank-check bespoke function -- retiring
applyChristTheKing and its lastSundayOfOctober helper from
calendar/index.ts entirely. Down to two bespoke override functions left
(marian-saturday's date-less fallback, christmas-octave-sunday's
fixed-date-range Sunday search), both structurally different from the
Nth-Sunday-from-anchor shape this table now covers.

No behavior change -- the full existing Christ the King test suite
passes unchanged through the new generic path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZSAgRi4QE4XRTqVto93zA
This commit is contained in:
2026-08-22 07:01:28 -04:00
parent 5e79fb4edf
commit 879d4d607c
6 changed files with 119 additions and 83 deletions
+1 -32
View File
@@ -1,6 +1,6 @@
import type { Commemoration, DayWinner, LiturgicalDay, SanctoralIdentity, TemporalCategory } from './types';
import { weekdayOf } from './weekday';
import { resolveSeason, resolveTemporalCategory, sundayOnOrBefore } from './temporal';
import { resolveSeason, resolveTemporalCategory } from './temporal';
import { resolveTemporalId } from './temporal-id';
import { getSanctoralCandidatesFor } from './feasts';
import { decideOccurrence, compareFeastClass, type OccurrenceResult } from './commemorations';
@@ -104,7 +104,6 @@ export function resolveDay(isoDate: string): LiturgicalDay {
winner = applyOctaves(isoDate, winner, commemorations);
winner = applyMarianSaturday(isoDate, weekday, temporalCategory, winner, commemorations);
winner = applyChristTheKing(isoDate, winner, commemorations);
winner = applyChristmasOctaveSunday(isoDate, weekday, winner, commemorations);
winner = applyMovableFeasts(isoDate, winner, commemorations);
applyEpiphany6Commemoration(isoDate, commemorations);
@@ -140,36 +139,6 @@ function applyEpiphany6Commemoration(isoDate: string, commemorations: Commemorat
commemorations.push({ kind: 'temporal', id: 'post-epiphany-6' });
}
/** The Sunday on or before Oct 31 — always lands in October since Oct 31
* is at most 6 days after the month's last Sunday. */
function lastSundayOfOctober(year: number): string {
return sundayOnOrBefore(`${year}-10-31`);
}
/**
* Per direct instruction: the last Sunday of October is always Christ the
* King, full stop — unlike every other layer in this file, nothing here
* loses gracefully or gets a rank check; whatever was winning (a plain
* numbered Sunday after Pentecost in every ordinary year, but modeled
* generally in case a high-ranked sanctoral candidate is ever assigned to
* that date too) is demoted straight to a commemoration. Confirmed live
* (see data/calendar/temporal-feasts/christ-the-king.yml): Duplex I.
* classis, well above anything that could contest it under this
* project's own calendar.
*/
function applyChristTheKing(isoDate: string, winner: DayWinner, commemorations: Commemoration[]): DayWinner {
const year = Number(isoDate.slice(0, 4));
if (isoDate !== lastSundayOfOctober(year)) {
return winner;
}
if (winner.kind === 'temporal') {
commemorations.push({ kind: 'temporal', id: winner.id });
} else {
commemorations.push({ kind: 'sanctoral', id: winner.id, name: winner.name, rank: winner.rank });
}
return { kind: 'temporal', id: 'christ-the-king' };
}
/**
* Per direct instruction, scoped to exactly Dec 26-30 — every year
* contains exactly one real Sunday somewhere in that 5-day window.