calendar: First/Second Vespers anticipation for Compline

Compline (and eventually Vespers) now resolves off whichever day's
identity actually governs tonight's office, not always today's — real
practice anticipates a higher-ranking tomorrow (Alma Redemptoris Mater
starting the Saturday evening before Advent I, Ave Regina Caelorum
starting the eve of Candlemas, Regina Caeli starting at the Easter
Vigil), which the previous date-only resolution missed entirely.

calendar/vespers.ts's resolveEveningDay compares today's and tomorrow's
precedence (reusing commemorations.ts's FeastClass machinery) to decide.
One deliberate absolute exception, caught by a failing test: a short,
explicit list of supreme fixed/movable feasts (Easter, Christmas,
Epiphany, Candlemas, Ascension, Corpus Christi, Sacred Heart) always wins
tomorrow's Vespers before checking whether today would otherwise keep its
own — without it, Holy Saturday's privileged-feria status incorrectly
blocked Easter's anticipation, the textbook case this whole thing exists
to get right.

hours/compline.ts now resolves its whole ordo through resolveEveningDay
instead of resolveDay.
This commit is contained in:
2026-08-10 07:05:41 -04:00
parent 2ea21f2c89
commit eaaca05ad8
4 changed files with 161 additions and 3 deletions
+40
View File
@@ -0,0 +1,40 @@
import { describe, expect, it } from 'vitest';
import { resolveEveningDay } from '../../src/calendar/vespers';
describe('resolveEveningDay', () => {
it('anticipates tomorrow when tomorrow is a Sunday and today is a plain feria (Saturday before Advent I)', () => {
// Nov 30, 2025 is Advent I Sunday.
const day = resolveEveningDay('2025-11-29');
expect(day.date).toBe('2025-11-30');
expect(day.season).toBe('advent');
});
it('does not anticipate between two plain ferias', () => {
const day = resolveEveningDay('2026-06-16'); // Tuesday, ordinary trinitytide
expect(day.date).toBe('2026-06-16');
});
it('anticipates Easter from Holy Saturday', () => {
// Easter 2026 is Apr 5, so Holy Saturday is Apr 4.
const day = resolveEveningDay('2026-04-04');
expect(day.date).toBe('2026-04-05');
expect(day.season).toBe('eastertide');
});
it("a Sunday always keeps its own Second Vespers, even before a higher-ranked weekday feast", () => {
// Aug 10, 2026 (Monday) is St. Lawrence, Duplex II classis — well above
// the "has First Vespers" threshold — but Sunday never cedes.
const day = resolveEveningDay('2026-08-09');
expect(day.date).toBe('2026-08-09');
});
it('an ordinary feria before a real high-ranked feast anticipates it', () => {
// Aug 14, 2026 (Friday) is a plain feria; Aug 15 is the Assumption,
// Duplex I classis.
const day = resolveEveningDay('2026-08-14');
expect(day.date).toBe('2026-08-15');
const winner = day.occurring.find((f) => !f.commemorated);
expect(winner?.id).toBe('assumption');
expect(winner?.vespersFrom).toBe('firstVespersOfTomorrow');
});
});