UNPKG

@burglekitt/gmt

Version:

Temporal-based date and time utilities with timezone support and polyfill integration

66 lines (65 loc) 3 kB
import { Temporal } from "@js-temporal/polyfill"; import { joinDateTimeConnector, normalizeDateTime } from "../../internal/index.js"; import { isValidUtc } from "../../utc/validate/index.js"; import { isValidZonedDateTime } from "../validate/index.js"; const ABS_DAY_THRESHOLD = 6; /** * Format a zoned date-time as a relative day label plus time-of-day, e.g. * "Tomorrow at 2:30 PM" — the zoned counterpart of `formatCalendar`. See * that function's JSDoc for the day-label/threshold/connector design; this * variant differs only in reading `value`'s IANA timezone for both the * calendar-day comparison and the rendered clock time. * * @param value ZonedDateTime ISO string to format * @param locale optional: BCP 47 locale tag * @param options optional: { reference, timeStyle } * @returns the formatted calendar string, or "" on invalid input * * @example formatCalendarZoned("2026-03-16T14:30:00-04:00[America/New_York]", "en-US", { reference: "2026-03-15T09:00:00-04:00[America/New_York]" }) // "tomorrow at 2:30 PM" * @example formatCalendarZoned(value, "de-DE") // "morgen um 14:30" * @example formatCalendarZoned("not-a-date") // "" */ export function formatCalendarZoned(value, locale, options = {}) { if (!isValidZonedDateTime(value)) return ""; if (typeof options.reference === "string" && !isValidZonedDateTime(options.reference) && !isValidUtc(options.reference)) return ""; if (typeof options.reference === "number" && !Number.isFinite(options.reference)) return ""; try { const target = Temporal.ZonedDateTime.from(value); const timeZone = target.timeZoneId; let reference; if (options.reference == null) { reference = Temporal.Now.zonedDateTimeISO(timeZone); } else if (typeof options.reference === "string") { reference = isValidUtc(options.reference) ? Temporal.Instant.from(options.reference).toZonedDateTimeISO(timeZone) : Temporal.ZonedDateTime.from(options.reference).withTimeZone(timeZone); } else { reference = Temporal.Instant.fromEpochMilliseconds(options.reference).toZonedDateTimeISO(timeZone); } const diffDays = target.toPlainDate().since(reference.toPlainDate()).days; const timeStyle = options.timeStyle ?? "short"; const epochMilliseconds = target.epochMilliseconds; if (Math.abs(diffDays) > ABS_DAY_THRESHOLD) { return normalizeDateTime(new Intl.DateTimeFormat(locale, { dateStyle: "long", timeStyle, timeZone, }).format(epochMilliseconds)); } const dayLabel = new Intl.RelativeTimeFormat(locale, { numeric: "auto", }).format(diffDays, "day"); return normalizeDateTime(joinDateTimeConnector(epochMilliseconds, timeZone, locale, dayLabel, timeStyle)); } catch { return ""; } }