// Temporal display helpers: format ISO instants for the viewer's timezone. /** Format an ISO instant in the viewer's timezone. */ export function formatInstant( iso: string, options: Intl.DateTimeFormatOptions, ): string { return Temporal.Instant.from(iso) .toZonedDateTimeISO(Temporal.Now.timeZoneId()) .toLocaleString('fr-FR', options); } /** Short day-month stamp ("8 sept."). */ export function formatDayMonth(iso: string): string { return formatInstant(iso, { day: 'numeric', month: 'short' }); } /** Day-month-year stamp ("8 sept. 2026"). */ export function formatDayMonthYear(iso: string): string { return formatInstant(iso, { day: 'numeric', month: 'short', year: 'numeric', }); } const ONE_DAY_MS = 86_400_000; /** * Stamp to store for a `YYYY-MM-DD` form day: noon UTC — a midnight stamp can * read back as the wrong day in a timezone ahead of UTC. Clamped to `now` when * the day is still ahead of the clock, and null for a malformed day or one * more than a day in the future (timezones ahead of UTC make "today" fuzzy). */ export function instantFromDay( day: string, now: Temporal.Instant, ): string | null { let midnight: Temporal.Instant; try { midnight = Temporal.PlainDate.from(day.trim()) .toZonedDateTime('UTC') .toInstant(); } catch { return null; } const noon = midnight.add({ hours: 12 }); if (noon.epochMilliseconds - now.epochMilliseconds > ONE_DAY_MS) return null; const stamp = Temporal.Instant.compare(noon, now) > 0 ? now : noon; return stamp.toString({ smallestUnit: 'millisecond' }); }