5e79fb4edf
Deploy / deploy (push) Successful in 1m21s
The bespoke applyImmaculateHeart function (following the applyMarianSaturday/ applyChristTheKing precedent of one hand-written override function per feast) doesn't scale: several more feasts are planned that all reduce to "weekday N days from Easter Sunday" (Ember/Rogation days, more Sacred-Heart-family Marian devotions, St. Joseph's pre-1955 Eastertide feast, Lenten Friday Passion devotions), and one-off functions invite subtle ordering bugs -- applyImmaculateHeart had to be sequenced after applyMarianSaturday specifically or it would have been silently clobbered. Replaced with calendar/movable-feasts.ts's generic applyMovableFeasts, which scans calendar/temporal-feasts.ts's existing per-feast YAML records for a new optional `easterOffset` field. Adding the next movable feast is now a new data/calendar/temporal-feasts/<id>.yml file with `rank` and `easterOffset` set, not a new TypeScript function. Same rank-compared, commemorate-the-loser semantics as before, just centralized instead of duplicated per feast. christ-the-king, marian-saturday, and christmas-octave-sunday stay as their own functions -- they're structurally different rules (a fixed month-position search, a date-less fallback default, and a fixed-date- range Sunday search, respectively), not Easter offsets, so force-fitting them into this table wouldn't actually simplify anything. No behavior change -- the existing Immaculate Heart of Mary 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
64 lines
3.3 KiB
TypeScript
64 lines
3.3 KiB
TypeScript
// A generic resolver for feasts pegged to a fixed day-offset from Easter
|
|
// Sunday that AREN'T themselves a Sunday, so calendar/temporal-id.ts's own
|
|
// governing-Sunday-based lookup can never find them -- e.g. Immaculate
|
|
// Heart of Mary (Easter+69, always a Saturday). Every entry is just a
|
|
// `data/calendar/temporal-feasts/<id>.yml` file with `rank` and
|
|
// `easterOffset` set (temporal-feasts.ts's own `movableTemporalFeasts`
|
|
// scans for these) -- adding the next one (an Ember/Rogation day, a
|
|
// Sacred-Heart-family Marian feast, a Lenten Friday devotion, St.
|
|
// Joseph's own Eastertide feast, ...) needs no new function here or in
|
|
// calendar/index.ts.
|
|
//
|
|
// Deliberately doesn't try to cover every movable-date pattern in this
|
|
// codebase: `christ-the-king` (last Sunday of October), `marian-saturday`
|
|
// (a generic "nothing else assigned" fallback, not date-anchored at all),
|
|
// and `christmas-octave-sunday` (whichever of Dec 26-29 is a Sunday) are
|
|
// structurally different rules, not Easter-offset ones, and stay as their
|
|
// own small functions in calendar/index.ts. A future Ember/Rogation day
|
|
// anchored to a *fixed civil date's* nearest Sunday (September/Advent
|
|
// Ember weeks, not Easter-anchored) would need a second anchor kind, not
|
|
// modeled yet -- extend `TemporalFeastRecord`/this file's own resolver
|
|
// when that's actually needed, rather than guessing at its shape now.
|
|
import type { Commemoration, DayWinner } from './types';
|
|
import { compareFeastClass } from './commemorations';
|
|
import { easterSunday } from './easter';
|
|
import { addDays, toIsoDate } from './date-math';
|
|
import { movableTemporalFeasts } from './temporal-feasts';
|
|
|
|
function dateForOffset(year: number, offset: number): string {
|
|
return addDays(toIsoDate(easterSunday(year)), offset);
|
|
}
|
|
|
|
/**
|
|
* Run last in resolveDay's override chain (see calendar/index.ts) so a
|
|
* specific named movable feast always supersedes whatever generic default
|
|
* (marian-saturday, a plain Sunday-of-week id) ran before it -- same
|
|
* reasoning as applyChristTheKing/applyMarianSaturday's own ordering.
|
|
* Same rank-compared, commemorate-the-loser shape those two already use:
|
|
* a temporal winner (no real standing of its own) is always superseded; a
|
|
* sanctoral winner keeps the day if its own rank is at least as strong as
|
|
* the movable feast's, which is commemorated instead; otherwise the
|
|
* movable feast wins and the displaced saint is commemorated.
|
|
*/
|
|
export function applyMovableFeasts(isoDate: string, winner: DayWinner, commemorations: Commemoration[]): DayWinner {
|
|
const year = Number(isoDate.slice(0, 4));
|
|
let resolvedWinner = winner;
|
|
for (const feast of movableTemporalFeasts()) {
|
|
if (isoDate !== dateForOffset(year, feast.easterOffset!)) {
|
|
continue;
|
|
}
|
|
const rank = feast.rank ?? 'simplex';
|
|
const FEAST_WINNER: DayWinner = { kind: 'temporal', id: feast.id };
|
|
if (resolvedWinner.kind === 'temporal') {
|
|
commemorations.push({ kind: 'temporal', id: resolvedWinner.id });
|
|
resolvedWinner = FEAST_WINNER;
|
|
} else if (compareFeastClass(resolvedWinner.rank, rank) >= 0) {
|
|
commemorations.push({ kind: 'temporal', id: feast.id });
|
|
} else {
|
|
commemorations.push({ kind: 'sanctoral', id: resolvedWinner.id, name: resolvedWinner.name, rank: resolvedWinner.rank });
|
|
resolvedWinner = FEAST_WINNER;
|
|
}
|
|
}
|
|
return resolvedWinner;
|
|
}
|