From ad87d8dd18ff8ca9f7003096793eb9f4828ced41 Mon Sep 17 00:00:00 2001 From: Will Estes Date: Mon, 10 Aug 2026 07:30:21 -0400 Subject: [PATCH] calendar: move Sunday-governance arithmetic from day-label.ts into temporal.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sundayOnOrBefore and firstSundayStrictlyAfter are calendar facts ("which Sunday does this feria belong to"), not presentation logic — day-label.ts was the only consumer so far, but a future temporal-propers content resolver (PropersRef's still-unused source: 'temporal' branch) will need the exact same arithmetic, just keyed for content ids instead of display strings. Moving it now, while the shape is still small and well understood, rather than duplicating it later. day-label.ts keeps the actual display config (which anchor, "Trinity" vs "Pentecost" wording) — that part stays presentation-only on purpose, since it's also where a future counting-convention setting would hook in. No behavior change: 88/88 tests still pass. --- src/calendar/date-math.ts | 5 +++++ src/calendar/day-label.ts | 16 +--------------- src/calendar/temporal.ts | 22 ++++++++++++++++++++-- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/calendar/date-math.ts b/src/calendar/date-math.ts index e2b0438..9596635 100644 --- a/src/calendar/date-math.ts +++ b/src/calendar/date-math.ts @@ -25,3 +25,8 @@ export function daysBetween(fromIsoDate: string, toIsoDate: string): number { const to = new Date(`${toIsoDate}T00:00:00Z`).getTime(); return Math.round((to - from) / 86_400_000); } + +/** 0 = Sunday, ..., 6 = Saturday. */ +export function dayOfWeek(isoDate: string): number { + return new Date(`${isoDate}T00:00:00Z`).getUTCDay(); +} diff --git a/src/calendar/day-label.ts b/src/calendar/day-label.ts index f436377..a9d192e 100644 --- a/src/calendar/day-label.ts +++ b/src/calendar/day-label.ts @@ -11,7 +11,7 @@ // forever — just not built yet. import type { LiturgicalDay } from './types'; import { easterSunday } from './easter'; -import { adventStart } from './temporal'; +import { adventStart, firstSundayStrictlyAfter, sundayOnOrBefore } from './temporal'; import { addDays, daysBetween, toIsoDate } from './date-math'; function capitalize(text: string): string { @@ -35,20 +35,6 @@ function ordinal(n: number): string { } } -function weekdayOf(isoDate: string): number { - return new Date(`${isoDate}T00:00:00Z`).getUTCDay(); // 0 = Sunday -} - -/** The first Sunday strictly after `isoDate` (even if `isoDate` is itself a Sunday). */ -function firstSundayStrictlyAfter(isoDate: string): string { - const dow = weekdayOf(isoDate); - return addDays(isoDate, dow === 0 ? 7 : 7 - dow); -} - -function sundayOnOrBefore(isoDate: string): string { - return addDays(isoDate, -weekdayOf(isoDate)); -} - interface OrdinalSeason { /** ISO date of the season's own anchor day, for a given calendar year. */ anchorDate(year: number): string; diff --git a/src/calendar/temporal.ts b/src/calendar/temporal.ts index 90294c6..38a028f 100644 --- a/src/calendar/temporal.ts +++ b/src/calendar/temporal.ts @@ -13,7 +13,7 @@ // separate, larger project. import type { Season, TemporalCategory, Weekday } from './types'; import { easterSunday } from './easter'; -import { addDays, daysBetween, toIsoDate } from './date-math'; +import { addDays, daysBetween, dayOfWeek, toIsoDate } from './date-math'; import fixedDateData from '../data/calendar/fixed-date-calendar.yml'; import easterOffsetsData from '../data/calendar/easter-offsets.yml'; import temporalCategoriesData from '../data/calendar/temporal-categories.yml'; @@ -45,11 +45,29 @@ const EPIPHANY = fixedDateFor('epiphany'); /** The Sunday nearest Nov 30 (St. Andrew's Day) — Advent's real start rule. */ export function adventStart(year: number): string { const nov30 = `${year}-11-30`; - const dow = new Date(`${nov30}T00:00:00Z`).getUTCDay(); // 0 = Sunday + const dow = dayOfWeek(nov30); const delta = dow <= 3 ? -dow : 7 - dow; return addDays(nov30, delta); } +/** + * The Sunday on or before `isoDate` — "which week does this feria belong + * to." Shared by calendar/day-label.ts (display) and, eventually, whatever + * resolves temporal-propers content ids (calendar/types.ts's `PropersRef` + * `source: 'temporal'` branch) — both need the same "which Sunday governs + * this feria" answer, just for different purposes, so it lives here rather + * than in either consumer. + */ +export function sundayOnOrBefore(isoDate: string): string { + return addDays(isoDate, -dayOfWeek(isoDate)); +} + +/** The first Sunday strictly after `isoDate`, even if `isoDate` is itself a Sunday. */ +export function firstSundayStrictlyAfter(isoDate: string): string { + const dow = dayOfWeek(isoDate); + return addDays(isoDate, dow === 0 ? 7 : 7 - dow); +} + function seasonFromEasterOffset(offset: number): Season { const dayOverride = easterOffsets.days?.[String(offset)]; if (dayOverride) {