Resolve Advent IV landing on Dec 24 as the Vigil, with Advent IV commemorated
Deploy / deploy (push) Successful in 1m25s
Deploy / deploy (push) Successful in 1m25s
When Advent 4's own Sunday lands on Dec 24, the Vigil of Christmas's identity (rank, collect, Lauds antiphons, Matins Gospel nocturn) now governs the day, matching the reference engine's own rule that a privileged Sunday never displaces it. Unlike the reference engine's own "no commemoratio" for this exact collision, Advent 4 is deliberately still commemorated — the Sundays of Advent have real standing of their own. Matins' three-nocturn Sunday psalmody is untouched either way, since it's driven by weekday, not by which id wins. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ME8QNPdEmHSbSx1r6VmDRG
This commit is contained in:
@@ -109,6 +109,7 @@ export function resolveDay(isoDate: string): LiturgicalDay {
|
||||
winner = applyChristmasOctaveSunday(isoDate, weekday, winner, commemorations);
|
||||
winner = applyMovableFeasts(isoDate, winner, commemorations);
|
||||
applyEpiphany6Commemoration(isoDate, commemorations);
|
||||
applyAdventFourVigilCommemoration(isoDate, weekday, commemorations);
|
||||
|
||||
return { date: isoDate, weekday, season, temporalCategory, winner, commemorations };
|
||||
}
|
||||
@@ -141,6 +142,32 @@ function applyEpiphany6Commemoration(isoDate: string, commemorations: Commemorat
|
||||
commemorations.push({ kind: 'temporal', id: 'post-epiphany-6' });
|
||||
}
|
||||
|
||||
/**
|
||||
* When Advent 4's own Sunday lands on Dec 24 (its latest possible date),
|
||||
* `resolveTemporalId` already gives that day the Vigil of Christmas's own
|
||||
* identity (`vigil-of-christmas` — collect, Lauds antiphons, Matins Gospel
|
||||
* nocturn), matching the reference engine's own rule that the Vigil's rank
|
||||
* governs. Per direct instruction (2026-08), unlike the reference engine's
|
||||
* own `no commemoratio` for this exact day, Advent 4 itself is still
|
||||
* commemorated here — the Sundays of Advent have real standing of their
|
||||
* own, distinct from an ordinary saint quietly ceding to a Vigil. Purely
|
||||
* additive, same shape as applyEpiphany6Commemoration just above: no new
|
||||
* `Commemoration` variant needed, `getDayCollects` already resolves any
|
||||
* `{ kind: 'temporal' }` commemoration's own `${id}-collect` for free.
|
||||
* `advent-4` is hardcoded rather than recomputed because Dec 24 always
|
||||
* falls at or after Advent's own 4th Sunday by construction (it's the
|
||||
* latest date Advent's 4th Sunday can ever land on).
|
||||
*/
|
||||
function applyAdventFourVigilCommemoration(
|
||||
isoDate: string,
|
||||
weekday: ReturnType<typeof weekdayOf>,
|
||||
commemorations: Commemoration[],
|
||||
): void {
|
||||
if (isoDate.slice(5) === '12-24' && weekday === 'sunday') {
|
||||
commemorations.push({ kind: 'temporal', id: 'advent-4' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Per direct instruction, scoped to exactly Dec 26-30 — every year
|
||||
* contains exactly one real Sunday somewhere in that 5-day window.
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
// confirmed against that source rather than re-derived by hand.
|
||||
import { resolveSeason, sundayOnOrBefore, firstSundayStrictlyAfter, adventStart, easterOffsetOf } from './temporal';
|
||||
import { daysBetween } from './date-math';
|
||||
import { weekdayOf } from './weekday';
|
||||
|
||||
const EASTER_OFFSET_IDS: [number, string][] = [
|
||||
[-63, 'septuagesima'],
|
||||
@@ -84,13 +83,16 @@ export function resolveTemporalId(isoDate: string): string {
|
||||
// Advent 4's own — see data/propers/temporal/vigil-of-christmas-collect.yml
|
||||
// and data/propers/nocturn-readings/vigil-of-christmas.yml, both
|
||||
// transcribed directly from the reference engine's Sancti/12-24.txt.
|
||||
// Scoped to non-Sunday only for now: when Dec 24 itself is Advent 4's own
|
||||
// Sunday, the reference engine's own 12-24s.txt keeps Advent 4's psalms
|
||||
// and most of its Matins lessons (only swapping in the Vigil's Gospel
|
||||
// nocturn) rather than fully substituting this id — a genuine content
|
||||
// splice this app doesn't model yet, deliberately left as `advent-4` for
|
||||
// that one specific case pending that design decision.
|
||||
if (isoDate.slice(5) === '12-24' && weekdayOf(isoDate) !== 'sunday') {
|
||||
// Applies even when Dec 24 itself is Advent 4's own Sunday — per direct
|
||||
// instruction (2026-08), the Vigil's own identity (rank/collect/Lauds
|
||||
// antiphons) governs that day too, with Advent 4 folded in as a
|
||||
// commemoration instead (calendar/index.ts's
|
||||
// applyAdventFourVigilCommemoration) rather than the reference engine's
|
||||
// own `no commemoratio` rule. Matins' three-nocturn Sunday psalmody is
|
||||
// unaffected either way — it's driven by `day.weekday`, not by this id
|
||||
// (hours/matins.ts's own `threeNocturns` check), so Advent 4's "Psalmi
|
||||
// Dominica" standing survives this id swap for free.
|
||||
if (isoDate.slice(5) === '12-24') {
|
||||
return 'vigil-of-christmas';
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { resolveDay } from '../../src/calendar';
|
||||
import { resolveOrdo } from '../../src/hours';
|
||||
|
||||
// Advent 4's own Sunday lands on Dec 24 (its latest possible date) roughly
|
||||
// every few years -- 2023 and 2028 are the two nearest examples. Per direct
|
||||
// instruction (2026-08): the Vigil of Christmas's own identity governs that
|
||||
// day (matching the reference engine's own rule -- Duplex I classis, never
|
||||
// displaced even by a privileged Sunday), with Advent 4 itself commemorated
|
||||
// alongside it -- a deliberate departure from the reference engine's own
|
||||
// `no commemoratio` for this exact collision, since the Sundays of Advent
|
||||
// have real standing of their own. See calendar/index.ts's
|
||||
// applyAdventFourVigilCommemoration and calendar/temporal-id.ts's own
|
||||
// `vigil-of-christmas` case.
|
||||
describe('Advent 4 Sunday landing on Dec 24 (the Vigil of Christmas)', () => {
|
||||
it('2023-12-24: Vigil wins, Advent 4 commemorated', () => {
|
||||
const day = resolveDay('2023-12-24');
|
||||
expect(day.weekday).toBe('sunday');
|
||||
expect(day.winner).toEqual({ kind: 'temporal', id: 'vigil-of-christmas' });
|
||||
expect(day.commemorations).toEqual([{ kind: 'temporal', id: 'advent-4' }]);
|
||||
});
|
||||
|
||||
it('2028-12-24: same shape, a second real occurrence', () => {
|
||||
const day = resolveDay('2028-12-24');
|
||||
expect(day.weekday).toBe('sunday');
|
||||
expect(day.winner).toEqual({ kind: 'temporal', id: 'vigil-of-christmas' });
|
||||
expect(day.commemorations).toEqual([{ kind: 'temporal', id: 'advent-4' }]);
|
||||
});
|
||||
|
||||
it("Lauds carries both collects (Vigil's own, then Advent 4's commemorated) and the Vigil's own proper antiphons/Benedictus antiphon", () => {
|
||||
const lauds = resolveOrdo('lauds', '2023-12-24');
|
||||
const collects = lauds.parts.filter((p) => p.kind === 'prayer');
|
||||
expect(collects).toHaveLength(2);
|
||||
for (const c of collects) {
|
||||
if (c.kind !== 'prayer') continue;
|
||||
expect(c.text.status.la).toBe('verified');
|
||||
expect(c.text.status.en).toBe('verified');
|
||||
}
|
||||
expect(collects[0]!.kind === 'prayer' ? collects[0]!.text.text.la : undefined).toContain(
|
||||
'Deus, qui nos redemptiónis nostræ',
|
||||
);
|
||||
expect(collects[1]!.kind === 'prayer' ? collects[1]!.text.text.la : undefined).toContain(
|
||||
'Excita, quǽsumus, Dómine',
|
||||
);
|
||||
|
||||
const benedictus = lauds.parts.find((p) => p.kind === 'canticle' && p.canticleId === 'benedictus');
|
||||
expect(benedictus?.kind).toBe('canticle');
|
||||
if (benedictus?.kind === 'canticle') {
|
||||
expect(benedictus.antiphon?.text.la).toContain('Oriétur');
|
||||
expect(benedictus.antiphon?.status.la).toBe('verified');
|
||||
}
|
||||
});
|
||||
|
||||
it("Matins Nocturn 3 carries the Vigil's own Gospel (Matt 1:18-21) with St. Jerome's homily, still using the Sunday's fixed psalm table throughout", () => {
|
||||
const matins = resolveOrdo('matins', '2023-12-24');
|
||||
const gospels = matins.parts.filter((p) => p.kind === 'gospel');
|
||||
expect(gospels).toHaveLength(1);
|
||||
const gospel = gospels[0]!;
|
||||
expect(gospel.kind).toBe('gospel');
|
||||
if (gospel.kind === 'gospel') {
|
||||
expect(gospel.text.citation?.la).toBe('Matt 1:18-21');
|
||||
expect(gospel.text.status.la).toBe('verified');
|
||||
expect(gospel.homily?.source).toContain('Jerome');
|
||||
expect(gospel.homily?.text.status.la).toBe('verified');
|
||||
}
|
||||
// Nocturns 1-2 keep the ordinary Sunday psalm table (Ps 20-31),
|
||||
// unaffected by which id won -- hours/matins.ts's threeNocturns/
|
||||
// sundayPsalmNocturn path is keyed by day.weekday, not day.winner.id.
|
||||
const psalmCount = matins.parts.filter((p) => p.kind === 'psalm').length;
|
||||
expect(psalmCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user