A single format namespace of pure functions. Each takes an unknown value and returns a display string. Locale and timezone are read at call-time — never baked in. Intl.* instances are cached by key and cleared when preferences change.

Shape

format/
  index.ts      ← assembles the namespace
  constants.ts  ← EMPTY sentinel ("—"), FormatOptions type
  locale.ts     ← getLocale(), getTimezone(), formatterCache, cache invalidation
  date.ts
  number.ts
  misc.ts

Namespace

export const format = {
  date, shortDate, dateTime, time, relativeTime,
  number, compactNumber, currency, decimal, percent, fraction, duration,
  boolean, empty,
} as const;

Signature

Every formatter follows the same shape:

function formatFoo(value: unknown, opts?: { fallback?: string }): string

Returns opts.fallback ?? EMPTY ("—") for null, undefined, or unparseable input.

Cache

Expensive Intl.* constructors are created once and reused:

const cache = new Map<string, Intl.DateTimeFormat | Intl.NumberFormat>();

function getDateFormat(): Intl.DateTimeFormat {
  const key = "date";
  if (!cache.has(key)) {
    cache.set(key, new Intl.DateTimeFormat(getLocale(), {
      month: "short", day: "numeric", year: "numeric",
      timeZone: getTimezone(),
    }));
  }
  return cache.get(key) as Intl.DateTimeFormat;
}

Cache key encodes any options that affect output (e.g. "percent:2" for 2 decimal places). Clear the whole cache when locale or timezone changes — all instances must be rebuilt.

Input coercion

Backend date fields arrive as epoch-ms numbers, numeric strings, or ISO strings depending on how the record was written. Normalize before formatting:

function parseDate(value: unknown): Date | null {
  if (value == null) return null;
  if (value instanceof Date) return isNaN(value.getTime()) ? null : value;
  if (typeof value === "number") return isFinite(value) ? new Date(value) : null;
  if (typeof value === "string") {
    if (!value) return null;
    if (/^-?\d+(\.\d+)?$/.test(value)) return new Date(Number(value));
    const d = Date.parse(value);
    return isNaN(d) ? null : new Date(d);
  }
  return null;
}

Return null, never Invalid Date, so every formatter’s fallback path is a simple null check.

Usage

Format at render time, not in hooks or data layers:

<p>{format.date(item.createdAt)}</p>
<p>{format.number(item.count)}</p>
<p>{format.duration(item.elapsedMs)}</p>
<p>{format.percent(item.passing, item.total)}</p>
<p>{format.date(item.updatedAt, { fallback: "Never" })}</p>

Never .toFixed(), .toLocaleString(), or bare String(someNumber) in a view.