Skip to content

@gj-kit/format core concepts

Timestamps drift between screens only when someone typed them that way — timeZone has no default.

One product’s admin and mobile apps carried three separate formatters: the same timestamp rendered nine hours apart, the same amount showed as ₩1,000 on one screen and 1,000원 on another, and null silently became 0. The root cause is that each of those choices had a default — new Date(‘2026-06-08T09:05:00’) resolves against the device zone before the formatter ever sees the value, and Intl’s currency path renders ‘1000 KRW’ on an es-ES device even when the call site asked for currencyDisplay: ‘symbol’.

Timezone, locale, and separators are user-visible policy. Requiring them prevents a device default from changing an operational timestamp or shared label.

Currency, bytes, and empty values need a declared representation; a formatter must not guess whether zero means absence or an available measurement.

  • timeZone has no default — formatDateTime(instant) does not compile, and neither does a call supplying only one of timeZone and separator — both are required, and 'device' is a token you type rather than a default you inherit.
  • Date strings cannot reach formatters — FormatDateInput is Date | number, so an API string must pass parseIsoInstant, whose assumeNoOffset policy (‘utc’ | ‘device’ | ‘reject’) is required; Date.parse is never called.
  • Byte labels cannot lie — { system: 'binary', maxUnit: 'GB' } does not compile — as a literal or through a const variable — since the two unit systems are separate union members.
  • Relative time takes an explicit clock — now: Date is required, and maxDays/onOverflow exist only as a pair, so the library never invents an absolute rendering past your cutoff.
  • The ₩ glyph never moves with the locale — formatKrw composes ₩ and 원 over a plain decimal formatter, and style: ‘currency’ / ‘percent’ are scanned out of both src/ and dist/; locale still decides grouping and digit glyphs.

Do not rely on implicit device timezone or locale defaults for persisted or operational values.

Continue with the two verified recipes.