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) {