calendar: move Sunday-governance arithmetic from day-label.ts into temporal.ts
Deploy / deploy (push) Successful in 37s

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.
This commit is contained in:
2026-08-10 07:30:21 -04:00
parent 226fff2ce6
commit ad87d8dd18
3 changed files with 26 additions and 17 deletions
+5
View File
@@ -25,3 +25,8 @@ export function daysBetween(fromIsoDate: string, toIsoDate: string): number {
const to = new Date(`${toIsoDate}T00:00:00Z`).getTime(); const to = new Date(`${toIsoDate}T00:00:00Z`).getTime();
return Math.round((to - from) / 86_400_000); 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();
}
+1 -15
View File
@@ -11,7 +11,7 @@
// forever — just not built yet. // forever — just not built yet.
import type { LiturgicalDay } from './types'; import type { LiturgicalDay } from './types';
import { easterSunday } from './easter'; import { easterSunday } from './easter';
import { adventStart } from './temporal'; import { adventStart, firstSundayStrictlyAfter, sundayOnOrBefore } from './temporal';
import { addDays, daysBetween, toIsoDate } from './date-math'; import { addDays, daysBetween, toIsoDate } from './date-math';
function capitalize(text: string): string { 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 { interface OrdinalSeason {
/** ISO date of the season's own anchor day, for a given calendar year. */ /** ISO date of the season's own anchor day, for a given calendar year. */
anchorDate(year: number): string; anchorDate(year: number): string;
+20 -2
View File
@@ -13,7 +13,7 @@
// separate, larger project. // separate, larger project.
import type { Season, TemporalCategory, Weekday } from './types'; import type { Season, TemporalCategory, Weekday } from './types';
import { easterSunday } from './easter'; 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 fixedDateData from '../data/calendar/fixed-date-calendar.yml';
import easterOffsetsData from '../data/calendar/easter-offsets.yml'; import easterOffsetsData from '../data/calendar/easter-offsets.yml';
import temporalCategoriesData from '../data/calendar/temporal-categories.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. */ /** The Sunday nearest Nov 30 (St. Andrew's Day) — Advent's real start rule. */
export function adventStart(year: number): string { export function adventStart(year: number): string {
const nov30 = `${year}-11-30`; 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; const delta = dow <= 3 ? -dow : 7 - dow;
return addDays(nov30, delta); 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 { function seasonFromEasterOffset(offset: number): Season {
const dayOverride = easterOffsets.days?.[String(offset)]; const dayOverride = easterOffsets.days?.[String(offset)];
if (dayOverride) { if (dayOverride) {