Generalize non-Sunday movable feasts into one Easter-offset resolver
Deploy / deploy (push) Successful in 1m21s
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
This commit is contained in:
@@ -2278,6 +2278,11 @@ common.ts`'s `ALWAYS_OVERRIDE_TEMPORAL_IDS` so its own chapter/responsory/hymn/v
|
|||||||
antiphon overrides are actually eligible (a temporal-kind winner is otherwise ignored by
|
antiphon overrides are actually eligible (a temporal-kind winner is otherwise ignored by
|
||||||
`getOfficeOverrideId`).
|
`getOfficeOverrideId`).
|
||||||
|
|
||||||
|
**Superseded the same day** — see "Generic Easter-offset movable-feast mechanism" below:
|
||||||
|
`applyImmaculateHeart` itself no longer exists; the same behavior now runs through
|
||||||
|
`calendar/movable-feasts.ts`'s generic `applyMovableFeasts`, with IHM as its first data-driven
|
||||||
|
entry (`easterOffset: 69` on its own temporal-feasts YAML record).
|
||||||
|
|
||||||
Propers content moved stores, not just files — temporal feasts resolve via a physically
|
Propers content moved stores, not just files — temporal feasts resolve via a physically
|
||||||
different store than sanctoral saints (`getTemporalProper`/`data/propers/temporal/*.yml`, keyed
|
different store than sanctoral saints (`getTemporalProper`/`data/propers/temporal/*.yml`, keyed
|
||||||
`${id}-${suffix}`, vs. sanctoral's `common`/`propers`-field indirection into `data/propers/
|
`${id}-${suffix}`, vs. sanctoral's `common`/`propers`-field indirection into `data/propers/
|
||||||
@@ -2328,6 +2333,41 @@ New tests in `tests/hours/matins.test.ts` cover the fallback across Aug 16-22, i
|
|||||||
Aug 17 edge case (correctly still ferial, since that day is actually governed by St. Lawrence's
|
Aug 17 edge case (correctly still ferial, since that day is actually governed by St. Lawrence's
|
||||||
own octave, not the Assumption's). `npm test` (430 tests) and `tsc --noEmit` both pass.
|
own octave, not the Assumption's). `npm test` (430 tests) and `tsc --noEmit` both pass.
|
||||||
|
|
||||||
|
### Generic Easter-offset movable-feast mechanism (2026-08-22)
|
||||||
|
|
||||||
|
User feedback, same day: a bespoke `calendar/index.ts` function per non-Sunday movable feast
|
||||||
|
(`applyImmaculateHeart`, following the `applyMarianSaturday`/`applyChristTheKing` precedent) was
|
||||||
|
flagged as fragile going forward — real plans exist for several more feasts that are all
|
||||||
|
"weekday N days from Easter Sunday": Ember/Rogation days, more Sacred-Heart-family Marian
|
||||||
|
devotions, St. Joseph's own pre-1955 Eastertide feast, and the Lenten Friday Passion devotions.
|
||||||
|
Writing a new function per feast doesn't scale and invites the same subtle ordering bugs
|
||||||
|
(`applyImmaculateHeart` had to be sequenced *after* `applyMarianSaturday` specifically, or a
|
||||||
|
Saturday-anchored feast would have been silently clobbered by the generic "Our Lady's Saturday"
|
||||||
|
default — easy to get wrong once there are several of these).
|
||||||
|
|
||||||
|
Replaced with a single generic resolver, `calendar/movable-feasts.ts`'s `applyMovableFeasts`,
|
||||||
|
run in `applyImmaculateHeart`'s old spot in `resolveDay`'s chain. No new data table: it scans
|
||||||
|
`calendar/temporal-feasts.ts`'s existing per-feast YAML records (`data/calendar/
|
||||||
|
temporal-feasts/*.yml`, already carrying `id`/`name`/`rank`/`octave`) for a new optional
|
||||||
|
`easterOffset` field, added to the `TemporalFeastRecord` type. Immaculate Heart of Mary's own
|
||||||
|
record gained `easterOffset: 69`; adding the next feast is a new YAML file with `rank` and
|
||||||
|
`easterOffset` set, not a new TypeScript function. Same rank-compared, commemorate-the-loser
|
||||||
|
semantics as before, applied generically instead of duplicated per feast.
|
||||||
|
|
||||||
|
Deliberately doesn't try to unify everything: `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 — they stay as their own small functions in `calendar/index.ts` rather
|
||||||
|
than being force-fit into this table for the sake of a single mechanism. Ember days for the
|
||||||
|
September and Advent quarters are anchored to a *fixed civil date's* nearest Sunday, not Easter,
|
||||||
|
so they'll need a second anchor kind on `TemporalFeastRecord` when they're actually authored —
|
||||||
|
not modeled yet, deliberately: the other two Ember quarters (Lent I, Pentecost) and Rogation
|
||||||
|
days, on the other hand, *are* plain Easter offsets and are already covered by this mechanism
|
||||||
|
today.
|
||||||
|
|
||||||
|
No behavior change — `tests/calendar/immaculate-heart-of-mary.test.ts` (unchanged) still passes
|
||||||
|
end-to-end through the new generic path. `npm test` (430 tests) and `tsc --noEmit` both pass.
|
||||||
|
|
||||||
## Known, deliberate simplifications (not bugs — working as designed)
|
## Known, deliberate simplifications (not bugs — working as designed)
|
||||||
|
|
||||||
- `getDayCollects`: each collect in a multi-collect day renders as its own
|
- `getDayCollects`: each collect in a multi-collect day renders as its own
|
||||||
|
|||||||
+2
-49
@@ -8,6 +8,7 @@ import { resolveCollision } from './collision';
|
|||||||
import { addDays, toIsoDate } from './date-math';
|
import { addDays, toIsoDate } from './date-math';
|
||||||
import { easterSunday } from './easter';
|
import { easterSunday } from './easter';
|
||||||
import { activeOctavesFor, strictestThreshold } from './octaves';
|
import { activeOctavesFor, strictestThreshold } from './octaves';
|
||||||
|
import { applyMovableFeasts } from './movable-feasts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A day's occurrence considered on its own — no awareness of what an
|
* A day's occurrence considered on its own — no awareness of what an
|
||||||
@@ -105,7 +106,7 @@ export function resolveDay(isoDate: string): LiturgicalDay {
|
|||||||
winner = applyMarianSaturday(isoDate, weekday, temporalCategory, winner, commemorations);
|
winner = applyMarianSaturday(isoDate, weekday, temporalCategory, winner, commemorations);
|
||||||
winner = applyChristTheKing(isoDate, winner, commemorations);
|
winner = applyChristTheKing(isoDate, winner, commemorations);
|
||||||
winner = applyChristmasOctaveSunday(isoDate, weekday, winner, commemorations);
|
winner = applyChristmasOctaveSunday(isoDate, weekday, winner, commemorations);
|
||||||
winner = applyImmaculateHeart(isoDate, winner, commemorations);
|
winner = applyMovableFeasts(isoDate, winner, commemorations);
|
||||||
applyEpiphany6Commemoration(isoDate, commemorations);
|
applyEpiphany6Commemoration(isoDate, commemorations);
|
||||||
|
|
||||||
return { date: isoDate, weekday, season, temporalCategory, winner, commemorations };
|
return { date: isoDate, weekday, season, temporalCategory, winner, commemorations };
|
||||||
@@ -245,54 +246,6 @@ function applyChristmasOctaveSunday(
|
|||||||
return winner;
|
return winner;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* User decision, 2026-08-22: relocated off the fixed Aug 22 date (which
|
|
||||||
* collided outright with the Assumption's own octave-closing day —
|
|
||||||
* Pius XII's 1944 decree fixed it there, but this project keeps the
|
|
||||||
* older Tridentine "Octave Day of the Assumption" too) to the *other*
|
|
||||||
* well-documented historical assignment: the Saturday after the Feast
|
|
||||||
* of the Sacred Heart. Real history (not a guess): this was the
|
|
||||||
* feast's actual diocesan date from 1914 until the 1944 fixed-date
|
|
||||||
* decree, and is also the date Paul VI's 1969 reform returned to — so
|
|
||||||
* it has continuity on both sides of the Aug-22 interlude, unlike
|
|
||||||
* inventing a "Saturday after the octave of Sacred Heart" pattern (no
|
|
||||||
* such rule was ever real; Sacred Heart itself doesn't even have an
|
|
||||||
* octave modeled in this codebase). Sacred Heart is Easter+68 (always
|
|
||||||
* a Friday, see data/calendar/easter-offsets.yml's own derivation
|
|
||||||
* comment) — this is the very next day, Easter+69.
|
|
||||||
*
|
|
||||||
* Runs last, after applyMarianSaturday, deliberately: Easter+69 is
|
|
||||||
* always a Saturday, so absent this override the day would otherwise
|
|
||||||
* just fall to the generic "Our Lady's Saturday" default — a specific
|
|
||||||
* named Marian feast should always supersede that generic filler, not
|
|
||||||
* lose to whichever ran first. Any temporal winner reaching here
|
|
||||||
* (marian-saturday or a plain post-Pentecost Sunday-of-the-week id)
|
|
||||||
* has no real standing of its own and is simply superseded, same as
|
|
||||||
* applyChristTheKing's unconditional-when-temporal branch. Rank
|
|
||||||
* compared via compareFeastClass, same as every other override in this
|
|
||||||
* file — a duplex-2-classis feast displaces anything weaker, but a
|
|
||||||
* higher-ranked sanctoral saint who happens to land on the same
|
|
||||||
* Saturday keeps the day and Immaculate Heart is commemorated instead.
|
|
||||||
*/
|
|
||||||
function applyImmaculateHeart(isoDate: string, winner: DayWinner, commemorations: Commemoration[]): DayWinner {
|
|
||||||
const year = Number(isoDate.slice(0, 4));
|
|
||||||
const targetDate = addDays(toIsoDate(easterSunday(year)), 69);
|
|
||||||
if (isoDate !== targetDate) {
|
|
||||||
return winner;
|
|
||||||
}
|
|
||||||
const IMMACULATE_HEART: DayWinner = { kind: 'temporal', id: 'immaculate-heart-of-mary' };
|
|
||||||
if (winner.kind === 'temporal') {
|
|
||||||
commemorations.push({ kind: 'temporal', id: winner.id });
|
|
||||||
return IMMACULATE_HEART;
|
|
||||||
}
|
|
||||||
if (compareFeastClass(winner.rank, 'duplex-2-classis') >= 0) {
|
|
||||||
commemorations.push({ kind: 'temporal', id: 'immaculate-heart-of-mary' });
|
|
||||||
return winner;
|
|
||||||
}
|
|
||||||
commemorations.push({ kind: 'sanctoral', id: winner.id, name: winner.name, rank: winner.rank });
|
|
||||||
return IMMACULATE_HEART;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Layered on top of everything above, same spirit as applyOctaves: never
|
* Layered on top of everything above, same spirit as applyOctaves: never
|
||||||
* changes decideOccurrence's own rules, just relabels the result on a free
|
* changes decideOccurrence's own rules, just relabels the result on a free
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
||||||
@@ -13,6 +13,19 @@ export interface TemporalFeastRecord {
|
|||||||
name: string;
|
name: string;
|
||||||
octave?: OctaveConfig;
|
octave?: OctaveConfig;
|
||||||
rank?: FeastClass;
|
rank?: FeastClass;
|
||||||
|
/** Days from Easter Sunday (0) this feast's own date falls on, for a
|
||||||
|
* feast that ISN'T itself a Sunday -- see calendar/movable-feasts.ts.
|
||||||
|
* `resolveTemporalId`'s own Easter-offset resolution only ever finds
|
||||||
|
* one of the 52 canonical *Sunday* ids (it works by finding the
|
||||||
|
* governing Sunday on-or-before a date, then looking that Sunday's own
|
||||||
|
* offset up); a weekday-anchored feast like Immaculate Heart of Mary
|
||||||
|
* (Easter+69, always a Saturday) is invisible to that lookup no matter
|
||||||
|
* what, so it needs this separate field plus calendar/movable-feasts.ts's
|
||||||
|
* own override pass instead. A feast that *is* a Sunday (Pentecost)
|
||||||
|
* doesn't need this at all -- it's already covered by the ordinary
|
||||||
|
* Sunday lookup, and by EASTER_OFFSET_STARTS below if it also has an
|
||||||
|
* octave to start. */
|
||||||
|
easterOffset?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const temporalFeastModules = import.meta.glob<{ default: TemporalFeastRecord }>(
|
const temporalFeastModules = import.meta.glob<{ default: TemporalFeastRecord }>(
|
||||||
@@ -29,6 +42,16 @@ export function getTemporalFeastRecord(id: string): TemporalFeastRecord | undefi
|
|||||||
return temporalFeastsById.get(id);
|
return temporalFeastsById.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Every temporal feast record carrying its own `easterOffset` -- the
|
||||||
|
* table calendar/movable-feasts.ts's applyMovableFeasts scans. Adding a
|
||||||
|
* new weekday-anchored movable feast (an Ember/Rogation day, a Sacred-
|
||||||
|
* Heart-family Marian feast, a Lenten Friday devotion, ...) is just a new
|
||||||
|
* `data/calendar/temporal-feasts/<id>.yml` file with `rank` and
|
||||||
|
* `easterOffset` set -- no new code. */
|
||||||
|
export function movableTemporalFeasts(): TemporalFeastRecord[] {
|
||||||
|
return [...temporalFeastsById.values()].filter((f) => f.easterOffset !== undefined);
|
||||||
|
}
|
||||||
|
|
||||||
/** Fixed-calendar-date starts (MM-DD -> temporal feast id). Only Christmas
|
/** Fixed-calendar-date starts (MM-DD -> temporal feast id). Only Christmas
|
||||||
* so far; Epiphany/Candlemas would join here if they ever needed an
|
* so far; Epiphany/Candlemas would join here if they ever needed an
|
||||||
* octave modeled too. */
|
* octave modeled too. */
|
||||||
|
|||||||
@@ -2,13 +2,14 @@
|
|||||||
# date collided outright with the Assumption's own octave-closing day,
|
# date collided outright with the Assumption's own octave-closing day,
|
||||||
# which this project also deliberately keeps (see saints/assumption.yml).
|
# which this project also deliberately keeps (see saints/assumption.yml).
|
||||||
# Moved to the Saturday after the Feast of the Sacred Heart (Easter+69,
|
# Moved to the Saturday after the Feast of the Sacred Heart (Easter+69,
|
||||||
# see calendar/index.ts's applyImmaculateHeart) -- the feast's own real
|
# see calendar/movable-feasts.ts) -- the feast's own real diocesan date
|
||||||
# diocesan date from 1914 until Pius XII's 1944 fixed-date decree, and
|
# from 1914 until Pius XII's 1944 fixed-date decree, and also the date
|
||||||
# also the date Paul VI's 1969 reform returned to, so it has continuity
|
# Paul VI's 1969 reform returned to, so it has continuity on both sides
|
||||||
# on both sides of the Aug-22 interlude. Rank/name still sourced from
|
# of the Aug-22 interlude. Rank/name still sourced from
|
||||||
# `web/www/horas/Latin/Sancti/08-22.txt`'s own [Rank] block, same as
|
# `web/www/horas/Latin/Sancti/08-22.txt`'s own [Rank] block, same as
|
||||||
# before the move -- only the date assignment changed, not the office's
|
# before the move -- only the date assignment changed, not the office's
|
||||||
# own content.
|
# own content.
|
||||||
id: immaculate-heart-of-mary
|
id: immaculate-heart-of-mary
|
||||||
name: "The Immaculate Heart of the Blessed Virgin Mary"
|
name: "The Immaculate Heart of the Blessed Virgin Mary"
|
||||||
rank: duplex-2-classis
|
rank: duplex-2-classis
|
||||||
|
easterOffset: 69
|
||||||
|
|||||||
+1
-1
@@ -322,7 +322,7 @@ function ferialPsalmodyThreeNocturns(day: LiturgicalDay): [ResolvedPart[], Resol
|
|||||||
function nocturnReadingIds(day: LiturgicalDay, temporalId: string): string[] {
|
function nocturnReadingIds(day: LiturgicalDay, temporalId: string): string[] {
|
||||||
const ids = new Set<string>();
|
const ids = new Set<string>();
|
||||||
// Not gated to `kind === 'sanctoral'` -- a named temporal override (e.g.
|
// Not gated to `kind === 'sanctoral'` -- a named temporal override (e.g.
|
||||||
// Immaculate Heart of Mary, calendar/index.ts's applyImmaculateHeart)
|
// Immaculate Heart of Mary, calendar/movable-feasts.ts's applyMovableFeasts)
|
||||||
// has its own authored nocturn-readings file keyed by its own id too,
|
// has its own authored nocturn-readings file keyed by its own id too,
|
||||||
// distinct from the plain governing-Sunday `temporalId` added below.
|
// distinct from the plain governing-Sunday `temporalId` added below.
|
||||||
// Harmless to include unconditionally: on an ordinary day `day.winner.id`
|
// Harmless to include unconditionally: on an ordinary day `day.winner.id`
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { getDayCollect, getBenedictusAntiphon, getMagnificatAntiphon } from '../
|
|||||||
// the Sacred Heart (Easter+68, always a Friday), the feast's own real
|
// the Sacred Heart (Easter+68, always a Friday), the feast's own real
|
||||||
// diocesan date from 1914 until Pius XII's 1944 fixed-date decree, and
|
// diocesan date from 1914 until Pius XII's 1944 fixed-date decree, and
|
||||||
// also the date the 1969 reform returned to.
|
// also the date the 1969 reform returned to.
|
||||||
describe('Immaculate Heart of Mary (applyImmaculateHeart)', () => {
|
describe('Immaculate Heart of Mary (calendar/movable-feasts.ts, generic Easter-offset resolver)', () => {
|
||||||
it('wins outright on Easter+69, with real content resolved via the temporal propers store', () => {
|
it('wins outright on Easter+69, with real content resolved via the temporal propers store', () => {
|
||||||
const day = resolveDay('2026-06-13'); // Easter 2026-04-05 + 69 days
|
const day = resolveDay('2026-06-13'); // Easter 2026-04-05 + 69 days
|
||||||
expect(day.winner).toEqual({ kind: 'temporal', id: 'immaculate-heart-of-mary' });
|
expect(day.winner).toEqual({ kind: 'temporal', id: 'immaculate-heart-of-mary' });
|
||||||
|
|||||||
Reference in New Issue
Block a user