Implement the resumed-post-Epiphany-Sunday temporal-id algorithm
Deploy / deploy (push) Successful in 1m6s
Deploy / deploy (push) Successful in 1m6s
Post-Pentecost overflow years (early Easter, fewer than 6 Sundays after Epiphany fit before Septuagesima) previously clamped every excess Sunday to post-pentecost-24 instead of resuming the skipped post-Epiphany Sundays' own content, per the traditional rubric. Ported directly from the reference engine's own DivinumOfficium/Date.pm getweek(). Also adds the Epiphany VI Saturday-before-Septuagesima commemoration (only when Epiphany V was the last Sunday to actually occur), and fixes day-label.ts's on-screen display to track the same resumed-Sunday content instead of a raw elapsed-week count — including a correction so the fixed final Sunday of the year always reads "23rd Sunday after Trinity", not a per-year-varying number. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,10 +22,6 @@ across every hour and content type in this app.
|
|||||||
- Pre-1955-but-post-1617 saints' differently-structured Matins readings — source confirmed
|
- Pre-1955-but-post-1617 saints' differently-structured Matins readings — source confirmed
|
||||||
(Tridentine/Divino Afflatu, combine-where-sensible applies the same way), but the actual
|
(Tridentine/Divino Afflatu, combine-where-sensible applies the same way), but the actual
|
||||||
sourcing/modeling pass hasn't started — see "Matins ordo" below.
|
sourcing/modeling pass hasn't started — see "Matins ordo" below.
|
||||||
- The "which Sunday governs this date" temporal-id algorithm (23rd-Sunday-before-Advent
|
|
||||||
rule, Epiphany-Sunday transfer to the end of the liturgical year, the
|
|
||||||
Saturday-before-Septuagesima 6th-Epiphany-Sunday commemoration edge case) — tabled, not
|
|
||||||
started.
|
|
||||||
- Easter's own octave — tabled, "a whole different discussion."
|
- Easter's own octave — tabled, "a whole different discussion."
|
||||||
- What "a Gospel reading is its own kind" should mean structurally/in the UI, beyond the
|
- What "a Gospel reading is its own kind" should mean structurally/in the UI, beyond the
|
||||||
`isGospel` flag on a plain Matins `lesson` — open; deferred until there's a UI to
|
`isGospel` flag on a plain Matins `lesson` — open; deferred until there's a UI to
|
||||||
@@ -1460,17 +1456,72 @@ seasonally variable — what's stored is whichever form rendered live on
|
|||||||
each one's already-chosen clean query date, not a season-aware
|
each one's already-chosen clean query date, not a season-aware
|
||||||
substitution. Same class of gap as the Paschaltide alleluia suffix
|
substitution. Same class of gap as the Paschaltide alleluia suffix
|
||||||
elsewhere in this file, flagged not fixed.
|
elsewhere in this file, flagged not fixed.
|
||||||
- **The "which Sunday governs this date" temporal-id algorithm** — the fixed
|
|
||||||
23rd-Sunday-before-Advent rule, Epiphany Sundays transferring to the end
|
|
||||||
of the liturgical year when they don't fit before Septuagesima, and the
|
|
||||||
Saturday-before-Septuagesima edge case (also commemorate the 6th Sunday
|
|
||||||
of Epiphany that day, in overflow years). Described in detail in an
|
|
||||||
earlier conversation, explicitly tabled, not started. Distinct from
|
|
||||||
Christ the King, which didn't need it (last-Sunday-of-October is direct
|
|
||||||
date math).
|
|
||||||
- **Easter's own octave** — explicitly tabled ("easter needs a whole
|
- **Easter's own octave** — explicitly tabled ("easter needs a whole
|
||||||
different discussion").
|
different discussion").
|
||||||
|
|
||||||
|
### The "which Sunday governs this date" temporal-id algorithm — done (2026-08)
|
||||||
|
|
||||||
|
Overflow years (Easter early enough that fewer than 6 Sundays after Epiphany occur before
|
||||||
|
Septuagesima cuts in) previously just clamped every excess post-Pentecost Sunday to
|
||||||
|
`post-pentecost-24` — wrong for every week between XXIII and the real last Sunday of the
|
||||||
|
year, only accidentally right for that final Sunday itself.
|
||||||
|
|
||||||
|
`calendar/temporal-id.ts`'s post-Pentecost branch is now ported directly from the reference
|
||||||
|
engine's own `getweek()` (`DivinumOfficium/Date.pm`) rather than re-derived by hand: once the
|
||||||
|
governing Sunday's Pentecost week reaches XXIII, a `wdist` (weeks until Advent) calculation
|
||||||
|
decides the id — the fixed final Sunday (`wdist < 2`) always resolves to `post-pentecost-24`
|
||||||
|
regardless of the true elapsed count, `post-pentecost-23` covers its own normal week, and
|
||||||
|
everything else resumes as `post-epiphany-${8 - wdist}` — the skipped post-Epiphany Sundays,
|
||||||
|
in their own ascending order, immediately before the fixed final Sunday. No separate tracking
|
||||||
|
of "how many post-Epiphany Sundays occurred" is needed — it falls out of the Advent-distance
|
||||||
|
arithmetic alone, confirmed against the reference engine's source rather than assumed. Trinity
|
||||||
|
Sunday = `post-pentecost-01` (this app's existing convention) also confirmed correct against
|
||||||
|
the same source (`$n=1` at `easter+56`, matching `TemporaM/Pent01-0.txt`'s own Trinity-Sunday
|
||||||
|
content).
|
||||||
|
|
||||||
|
Separately, `calendar/index.ts` gained `applyEpiphany6Commemoration`, an `apply*`-pass
|
||||||
|
(same shape as `applyChristTheKing`) that commemorates `post-epiphany-6` on the Saturday
|
||||||
|
immediately before Septuagesima — but only in years where Epiphany V (not IV, III, ...) was
|
||||||
|
the last post-Epiphany Sunday to actually occur, per direct clarification (a narrower rule
|
||||||
|
than "any skipped Sunday gets a Saturday commemoration" — only VI specifically, and only in
|
||||||
|
that one adjacency case). No new `Commemoration` variant was needed: the existing
|
||||||
|
`{ kind: 'temporal'; id }` shape already round-trips through `getDayCollects`'s
|
||||||
|
`${id}-collect` lookup for free, so this is purely additive to `commemorations`, coexisting
|
||||||
|
with whatever else that Saturday already resolved to (consistent with this app's generous,
|
||||||
|
non-single-winner commemoration design elsewhere).
|
||||||
|
|
||||||
|
Verified live against real years, not just unit-tested: 2026 (`post-epiphany-5` is the last
|
||||||
|
Sunday reached, so `2026-11-14`/`-21` resume as Epiphany V/VI before `2026-11-28`'s fixed
|
||||||
|
`post-pentecost-24`), and 1943/1962 (Easter late enough that all 6 Epiphany Sundays occur, so
|
||||||
|
no resumption and no Saturday commemoration ever fires). New test file
|
||||||
|
`tests/calendar/temporal-id.test.ts` covers both the overflow resumption and the Saturday
|
||||||
|
commemoration's on/off cases (2026, 2022, 2025, 1943). `npm test` (363 tests, 4 new) and
|
||||||
|
`npm run build` both pass.
|
||||||
|
|
||||||
|
**Follow-up done same day:** `calendar/day-label.ts`'s on-screen display now also tracks the
|
||||||
|
resumed-Sunday content, via a `trinitytideOverrideLabel` check ahead of the ordinary
|
||||||
|
Trinity-counted branch — it calls `resolveTemporalId` directly (the same mechanism the content
|
||||||
|
layer trusts) rather than re-deriving the wdist arithmetic a second time, so the label can
|
||||||
|
never disagree with what's actually rendered underneath. A resumed Sunday/weekday now reads
|
||||||
|
"The 5th Sunday after Epiphany" / "Monday in the 5th week after Epiphany" instead of continuing
|
||||||
|
the Trinity count past its real bound.
|
||||||
|
|
||||||
|
**Correction, same pass**: the first cut of this left the fixed final Sunday of the year
|
||||||
|
(`post-pentecost-24`) on the old raw "weeks since Trinity" arithmetic, on the assumption that
|
||||||
|
was an unrelated, already-correct convention — wrong. Scanning every year 1900-2100 showed
|
||||||
|
*every single year's* last Sunday before Advent resolves to `post-pentecost-24`, and the raw
|
||||||
|
elapsed-week count for that Sunday varies year to year (22nd, 23rd, 25th, ... depending on
|
||||||
|
Easter's date) even though the content is always the same fixed formulary. Since this app's
|
||||||
|
Trinity-counted display is one week off from the Pentecost-counted id (Trinity Sunday itself =
|
||||||
|
`post-pentecost-01` = the display's own uncounted anchor), the correct display ordinal for
|
||||||
|
that fixed Sunday is XXIV − 1 = **23, always** — not derived from elapsed weeks at all.
|
||||||
|
`trinitytideOverrideLabel` now handles this as its own branch (`FIXED_LAST_SUNDAY_ORDINAL`),
|
||||||
|
same "ask `resolveTemporalId`, don't re-derive" principle as the resumed-Epiphany branch.
|
||||||
|
Live-verified across 1943/1962/2015/2026/2033 — all five now read "The 23rd Sunday after
|
||||||
|
Trinity" for their own last Sunday, where before the fix they'd shown four different numbers.
|
||||||
|
Tests in `tests/calendar/day-label.test.ts` updated accordingly. `npm test` (365 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
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { adventStart, firstSundayStrictlyAfter, sundayOnOrBefore } from './tempo
|
|||||||
import { addDays, daysBetween, toIsoDate } from './date-math';
|
import { addDays, daysBetween, toIsoDate } from './date-math';
|
||||||
import { getTemporalFeastRecord } from './temporal-feasts';
|
import { getTemporalFeastRecord } from './temporal-feasts';
|
||||||
import { resolveActiveOctave, type ActiveOctave } from './octaves';
|
import { resolveActiveOctave, type ActiveOctave } from './octaves';
|
||||||
|
import { resolveTemporalId } from './temporal-id';
|
||||||
|
|
||||||
function capitalize(text: string): string {
|
function capitalize(text: string): string {
|
||||||
return text.charAt(0).toUpperCase() + text.slice(1);
|
return text.charAt(0).toUpperCase() + text.slice(1);
|
||||||
@@ -109,7 +110,66 @@ function anchorDayName(day: LiturgicalDay): string | undefined {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The fixed final Sunday of the liturgical year (temporal-id.ts's
|
||||||
|
* `post-pentecost-24`) always carries "Sunday XXIV after Pentecost"'s own
|
||||||
|
* formulary, regardless of how many Sundays actually elapsed since
|
||||||
|
* Trinity — confirmed by scanning every year 1900-2100, every single one
|
||||||
|
* lands on this id for its own last Sunday before Advent, never a raw
|
||||||
|
* elapsed-week count. This app's Trinity-counted display is one week off
|
||||||
|
* from that Pentecost-counted id (Trinity Sunday itself = post-pentecost-
|
||||||
|
* 01 = the display's own uncounted anchor day), so the fixed display
|
||||||
|
* ordinal here is XXIV - 1 = 23, not whatever a plain "weeks since
|
||||||
|
* Trinity" calculation would produce. */
|
||||||
|
const FIXED_LAST_SUNDAY_ORDINAL = 23;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trinitytide's ordinal display can't be pure "weeks since Trinity's own
|
||||||
|
* first Sunday" arithmetic once the season gets late enough — see
|
||||||
|
* temporal-id.ts's own post-Pentecost-XXIII-plus branch. Two cases, both
|
||||||
|
* driven by `resolveTemporalId` directly (the same mechanism the content
|
||||||
|
* layer already trusts, rather than re-deriving the wdist arithmetic here
|
||||||
|
* a second time, so the label can never disagree with what's actually
|
||||||
|
* rendered underneath):
|
||||||
|
* - A resumed post-Epiphany Sunday/week (an overflow year's skipped
|
||||||
|
* Epiphany Sundays, reappearing here) — reads "after Epiphany", not
|
||||||
|
* the next Trinity-counted number in line.
|
||||||
|
* - The fixed final Sunday/week of the year itself — always the fixed
|
||||||
|
* 23rd-after-Trinity ordinal (see FIXED_LAST_SUNDAY_ORDINAL above),
|
||||||
|
* every year, not just overflow ones.
|
||||||
|
* Only ever fires within trinitytide, since that's the only season
|
||||||
|
* resolveTemporalId can return either of these ids for a date outside
|
||||||
|
* their own native season.
|
||||||
|
*/
|
||||||
|
function trinitytideOverrideLabel(day: LiturgicalDay): string | undefined {
|
||||||
|
if (day.season !== 'trinitytide') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const id = resolveTemporalId(day.date);
|
||||||
|
const weekdayName = capitalize(day.weekday);
|
||||||
|
|
||||||
|
const epiphanyMatch = id.match(/^post-epiphany-(\d)$/);
|
||||||
|
if (epiphanyMatch) {
|
||||||
|
const n = Number(epiphanyMatch[1]);
|
||||||
|
return day.weekday === 'sunday'
|
||||||
|
? `The ${ordinal(n)} Sunday after Epiphany`
|
||||||
|
: `${weekdayName} in the ${ordinal(n)} week after Epiphany`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id === 'post-pentecost-24') {
|
||||||
|
return day.weekday === 'sunday'
|
||||||
|
? `The ${ordinal(FIXED_LAST_SUNDAY_ORDINAL)} Sunday after Trinity`
|
||||||
|
: `${weekdayName} in the ${ordinal(FIXED_LAST_SUNDAY_ORDINAL)} week after Trinity`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
function temporalLabel(day: LiturgicalDay): string {
|
function temporalLabel(day: LiturgicalDay): string {
|
||||||
|
const trinitytideOverride = trinitytideOverrideLabel(day);
|
||||||
|
if (trinitytideOverride) {
|
||||||
|
return trinitytideOverride;
|
||||||
|
}
|
||||||
|
|
||||||
const weekdayName = capitalize(day.weekday);
|
const weekdayName = capitalize(day.weekday);
|
||||||
const config = ORDINAL_SEASONS[day.season];
|
const config = ORDINAL_SEASONS[day.season];
|
||||||
if (!config) {
|
if (!config) {
|
||||||
|
|||||||
+31
-1
@@ -5,7 +5,8 @@ import { resolveTemporalId } from './temporal-id';
|
|||||||
import { getSanctoralCandidatesFor } from './feasts';
|
import { getSanctoralCandidatesFor } from './feasts';
|
||||||
import { decideOccurrence, isAtLeast, type OccurrenceResult } from './commemorations';
|
import { decideOccurrence, isAtLeast, type OccurrenceResult } from './commemorations';
|
||||||
import { resolveCollision } from './collision';
|
import { resolveCollision } from './collision';
|
||||||
import { addDays } from './date-math';
|
import { addDays, toIsoDate } from './date-math';
|
||||||
|
import { easterSunday } from './easter';
|
||||||
import { activeOctavesFor, strictestThreshold } from './octaves';
|
import { activeOctavesFor, strictestThreshold } from './octaves';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,10 +105,39 @@ 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);
|
||||||
|
applyEpiphany6Commemoration(isoDate, commemorations);
|
||||||
|
|
||||||
return { date: isoDate, weekday, season, temporalCategory, winner, commemorations };
|
return { date: isoDate, weekday, season, temporalCategory, winner, commemorations };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Real rubric, confirmed 2026-08: when the 6th Sunday after Epiphany is
|
||||||
|
* bumped by Septuagesima — i.e. only in years where the 5th Sunday after
|
||||||
|
* Epiphany was the last one to actually occur — it's commemorated on the
|
||||||
|
* Saturday immediately before Septuagesima, on top of whatever else that
|
||||||
|
* Saturday already resolved to. Distinct from (and doesn't imply) the
|
||||||
|
* later "resumed post-Epiphany Sunday" transfer handled in
|
||||||
|
* temporal-id.ts's post-Pentecost overflow branch — that one applies to
|
||||||
|
* *every* skipped post-Epiphany Sunday, not just VI; this Saturday
|
||||||
|
* commemoration is VI's alone. Purely additive: no `Commemoration` variant
|
||||||
|
* needed beyond the existing `{ kind: 'temporal' }` one, since
|
||||||
|
* hours/resolve-common.ts's getDayCollects already resolves any temporal
|
||||||
|
* commemoration's own `${id}-collect` for free.
|
||||||
|
*/
|
||||||
|
function applyEpiphany6Commemoration(isoDate: string, commemorations: Commemoration[]): void {
|
||||||
|
const year = Number(isoDate.slice(0, 4));
|
||||||
|
const septuagesimaStart = addDays(toIsoDate(easterSunday(year)), -63);
|
||||||
|
const saturdayBeforeSeptuagesima = addDays(septuagesimaStart, -1);
|
||||||
|
if (isoDate !== saturdayBeforeSeptuagesima) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lastEpiphanySunday = addDays(septuagesimaStart, -7);
|
||||||
|
if (resolveTemporalId(lastEpiphanySunday) !== 'post-epiphany-5') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
commemorations.push({ kind: 'temporal', id: 'post-epiphany-6' });
|
||||||
|
}
|
||||||
|
|
||||||
/** The Sunday on or before Oct 31 — always lands in October since Oct 31
|
/** 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. */
|
* is at most 6 days after the month's last Sunday. */
|
||||||
function lastSundayOfOctober(year: number): string {
|
function lastSundayOfOctober(year: number): string {
|
||||||
|
|||||||
+38
-12
@@ -11,16 +11,23 @@
|
|||||||
// header and propers/index.ts's getTemporalProper for the fuller version
|
// header and propers/index.ts's getTemporalProper for the fuller version
|
||||||
// of this reasoning.
|
// of this reasoning.
|
||||||
//
|
//
|
||||||
// Two known, deliberately unfixed gaps: the ferias between Christmas Day
|
// One known, deliberately unfixed gap: the ferias between Christmas Day
|
||||||
// and the Sunday within its octave, and between Epiphany and its own
|
// and the Sunday within its octave, and between Epiphany and its own
|
||||||
// first Sunday, fall back to that season's first named Sunday a few days
|
// first Sunday, fall back to that season's first named Sunday a few days
|
||||||
// early — Christmas Day's and Epiphany's own collects aren't authored as
|
// early — Christmas Day's and Epiphany's own collects aren't authored as
|
||||||
// separate temporal-propers entries (only Sunday collects were pulled), so
|
// separate temporal-propers entries (only Sunday collects were pulled), so
|
||||||
// this is a deliberate approximation, not an oversight. Likewise, a real
|
// this is a deliberate approximation, not an oversight.
|
||||||
// overflow year (early Easter, more than 24 weeks between Trinity and
|
//
|
||||||
// Advent) would traditionally reuse the unused post-Epiphany Sundays'
|
// Overflow years (Easter early enough that fewer than 6 Sundays after
|
||||||
// collects for the excess weeks — not modeled; this just clamps at
|
// Epiphany occur before Septuagesima cuts in) are handled below: the
|
||||||
// post-pentecost-24.
|
// skipped post-Epiphany Sundays "resume" after Sunday XXIII after
|
||||||
|
// Pentecost, in their own numeric order, right before the fixed final
|
||||||
|
// Sunday of the year (always Sunday XXIV's own formulary, regardless of
|
||||||
|
// how many Sundays actually elapsed since Pentecost). Ported directly
|
||||||
|
// from the reference engine's own `getweek()`
|
||||||
|
// (DivinumOfficium/Date.pm) — same rubric, same arithmetic (weeks-until-
|
||||||
|
// Advent, not a separately-tracked count of skipped Epiphany Sundays);
|
||||||
|
// confirmed against that source rather than re-derived by hand.
|
||||||
import { resolveSeason, sundayOnOrBefore, firstSundayStrictlyAfter, adventStart, easterOffsetOf } from './temporal';
|
import { resolveSeason, sundayOnOrBefore, firstSundayStrictlyAfter, adventStart, easterOffsetOf } from './temporal';
|
||||||
import { daysBetween } from './date-math';
|
import { daysBetween } from './date-math';
|
||||||
|
|
||||||
@@ -43,12 +50,29 @@ const EASTER_OFFSET_IDS: [number, string][] = [
|
|||||||
[42, 'sunday-after-ascension'],
|
[42, 'sunday-after-ascension'],
|
||||||
[49, 'pentecost-sunday'],
|
[49, 'pentecost-sunday'],
|
||||||
];
|
];
|
||||||
for (let n = 1; n <= 24; n++) {
|
// Sundays I-XXII after Pentecost only — XXIII and XXIV are handled by
|
||||||
|
// resolvePostPentecost23Plus below, since (unlike I-XXII) which id a given
|
||||||
|
// week gets can depend on distance-to-Advent, not just its own offset.
|
||||||
|
for (let n = 1; n <= 22; n++) {
|
||||||
EASTER_OFFSET_IDS.push([56 + 7 * (n - 1), `post-pentecost-${String(n).padStart(2, '0')}`]);
|
EASTER_OFFSET_IDS.push([56 + 7 * (n - 1), `post-pentecost-${String(n).padStart(2, '0')}`]);
|
||||||
}
|
}
|
||||||
const EASTER_OFFSET_ID_MAP = new Map(EASTER_OFFSET_IDS);
|
const EASTER_OFFSET_ID_MAP = new Map(EASTER_OFFSET_IDS);
|
||||||
const MAX_EASTER_OFFSET = EASTER_OFFSET_IDS[EASTER_OFFSET_IDS.length - 1]![0];
|
|
||||||
const MAX_EASTER_OFFSET_ID = EASTER_OFFSET_IDS[EASTER_OFFSET_IDS.length - 1]![1];
|
/**
|
||||||
|
* A governing Sunday at or beyond Pentecost week XXIII. `pentecostWeek` is
|
||||||
|
* the reference engine's own `$n` (1 = Trinity Sunday, verified against
|
||||||
|
* DivinumOfficium/Date.pm and its Tempora/PentNN file naming).
|
||||||
|
*/
|
||||||
|
function resolvePostPentecost23Plus(governingSunday: string, year: number, pentecostWeek: number): string {
|
||||||
|
const wdist = Math.floor((daysBetween(governingSunday, adventStart(year)) + 6) / 7);
|
||||||
|
if (wdist < 2) {
|
||||||
|
return 'post-pentecost-24';
|
||||||
|
}
|
||||||
|
if (pentecostWeek === 23) {
|
||||||
|
return 'post-pentecost-23';
|
||||||
|
}
|
||||||
|
return `post-epiphany-${8 - wdist}`;
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveTemporalId(isoDate: string): string {
|
export function resolveTemporalId(isoDate: string): string {
|
||||||
const season = resolveSeason(isoDate);
|
const season = resolveSeason(isoDate);
|
||||||
@@ -78,9 +102,11 @@ export function resolveTemporalId(isoDate: string): string {
|
|||||||
// regardless of which `season` bucket the feria itself falls in (the
|
// regardless of which `season` bucket the feria itself falls in (the
|
||||||
// ferias right after Ash Wednesday genuinely reuse Quinquagesima's
|
// ferias right after Ash Wednesday genuinely reuse Quinquagesima's
|
||||||
// collect, crossing what resolveSeason calls two different seasons).
|
// collect, crossing what resolveSeason calls two different seasons).
|
||||||
const offset = easterOffsetOf(sundayOnOrBefore(isoDate));
|
const governingSunday = sundayOnOrBefore(isoDate);
|
||||||
if (offset > MAX_EASTER_OFFSET) {
|
const offset = easterOffsetOf(governingSunday);
|
||||||
return MAX_EASTER_OFFSET_ID;
|
const pentecostWeek = Math.round((offset - 49) / 7);
|
||||||
|
if (pentecostWeek >= 23) {
|
||||||
|
return resolvePostPentecost23Plus(governingSunday, year, pentecostWeek);
|
||||||
}
|
}
|
||||||
return EASTER_OFFSET_ID_MAP.get(offset) ?? 'septuagesima';
|
return EASTER_OFFSET_ID_MAP.get(offset) ?? 'septuagesima';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,29 @@ describe('getDayLabel — ordinal temporal label', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getDayLabel — resumed post-Epiphany Sunday (overflow years)', () => {
|
||||||
|
it('labels a resumed Sunday/weekday as "after Epiphany", not the next Trinity-counted number', () => {
|
||||||
|
// 2026: post-epiphany-5 is Nov 8, post-epiphany-6 is Nov 15 (both
|
||||||
|
// Sundays) -- see calendar/temporal-id.test.ts for the id-level
|
||||||
|
// coverage this label check builds on.
|
||||||
|
expect(getDayLabel(resolveDay('2026-11-08'))).toBe('The 5th Sunday after Epiphany');
|
||||||
|
expect(getDayLabel(resolveDay('2026-11-09'))).toBe('Monday in the 5th week after Epiphany');
|
||||||
|
expect(getDayLabel(resolveDay('2026-11-15'))).toBe('The 6th Sunday after Epiphany');
|
||||||
|
expect(getDayLabel(resolveDay('2026-11-16'))).toBe('Monday in the 6th week after Epiphany');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('the fixed final Sunday of the year always shows the fixed 23rd-after-Trinity ordinal, not a raw elapsed-week count', () => {
|
||||||
|
// 2026-11-22 is the fixed post-pentecost-24 Sunday (Sunday XXIV after
|
||||||
|
// Pentecost's own fixed formulary; this app's Trinity-counted display
|
||||||
|
// is one week off from that Pentecost-counted id, so XXIV - 1 = 23).
|
||||||
|
// Every year 1900-2100 lands on this same id for its own last Sunday
|
||||||
|
// before Advent, so this ordinal is fixed, not overflow-year-specific
|
||||||
|
// -- confirmed against a non-overflow year (1943) too.
|
||||||
|
expect(getDayLabel(resolveDay('2026-11-22'))).toBe('The 23rd Sunday after Trinity');
|
||||||
|
expect(getDayLabel(resolveDay('1943-11-21'))).toBe('The 23rd Sunday after Trinity');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('getDayLabel — active octave', () => {
|
describe('getDayLabel — active octave', () => {
|
||||||
it('shows the octave day itself as the primary label, plus a lesser saint commemorated alongside it', () => {
|
it('shows the octave day itself as the primary label, plus a lesser saint commemorated alongside it', () => {
|
||||||
// St. Lawrence's own octave (Aug 10-17); Aug 12 is day 3, and also St.
|
// St. Lawrence's own octave (Aug 10-17); Aug 12 is day 3, and also St.
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { resolveTemporalId } from '../../src/calendar/temporal-id';
|
||||||
|
import { resolveDay } from '../../src/calendar';
|
||||||
|
|
||||||
|
describe('resolveTemporalId — post-Pentecost overflow', () => {
|
||||||
|
// 2026: Easter 2026-04-05 -> only post-epiphany-1..5 occur before
|
||||||
|
// Septuagesima, so post-pentecost-24 through 27 (5 real weeks between
|
||||||
|
// XXIII and the last Sunday) resume as Epiphany V and VI before landing
|
||||||
|
// on the fixed final Sunday. Confirmed against the reference engine's
|
||||||
|
// own DivinumOfficium/Date.pm getweek() (Pent/Epi arithmetic), not just
|
||||||
|
// hand-derived.
|
||||||
|
it('resumes the skipped post-Epiphany Sundays in order before the fixed last Sunday', () => {
|
||||||
|
expect(resolveTemporalId('2026-11-07')).toBe('post-pentecost-23');
|
||||||
|
expect(resolveTemporalId('2026-11-14')).toBe('post-epiphany-5');
|
||||||
|
expect(resolveTemporalId('2026-11-21')).toBe('post-epiphany-6');
|
||||||
|
expect(resolveTemporalId('2026-11-28')).toBe('post-pentecost-24');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not resume anything in a non-overflow year (all 6 post-Epiphany Sundays occurred)', () => {
|
||||||
|
// 1943: Easter 1943-04-25, latest possible-ish Easter -> all 6
|
||||||
|
// post-Epiphany Sundays occur, so post-pentecost climbs past 24
|
||||||
|
// without ever touching Epiphany content, capping at the fixed id.
|
||||||
|
expect(resolveTemporalId('1943-11-21')).toBe('post-pentecost-24');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('post-Epiphany VI commemoration on the Saturday before Septuagesima', () => {
|
||||||
|
it('fires only when Epiphany V was the last Sunday to occur (2022, 2025)', () => {
|
||||||
|
const day2022 = resolveDay('2022-02-12');
|
||||||
|
expect(day2022.commemorations).toContainEqual({ kind: 'temporal', id: 'post-epiphany-6' });
|
||||||
|
|
||||||
|
const day2025 = resolveDay('2025-02-15');
|
||||||
|
expect(day2025.commemorations).toContainEqual({ kind: 'temporal', id: 'post-epiphany-6' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not fire when all 6 post-Epiphany Sundays occurred (1943)', () => {
|
||||||
|
const day = resolveDay('1943-02-20');
|
||||||
|
expect(day.commemorations).not.toContainEqual({ kind: 'temporal', id: 'post-epiphany-6' });
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user