@gj-kit/format core concepts
Timestamps drift between screens only when someone typed them that way — timeZone has no default.
Why this boundary exists
Section titled “Why this boundary exists”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’.
Display policy belongs at the call site
Section titled “Display policy belongs at the call site”Timezone, locale, and separators are user-visible policy. Requiring them prevents a device default from changing an operational timestamp or shared label.
Values keep their display semantics
Section titled “Values keep their display semantics”Currency, bytes, and empty values need a declared representation; a formatter must not guess whether zero means absence or an available measurement.
What the package enforces
Section titled “What the package enforces”- timeZone has no default —
formatDateTime(instant)does not compile, and neither does a call supplying only one oftimeZoneandseparator— 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.
What remains application-owned
Section titled “What remains application-owned”Do not rely on implicit device timezone or locale defaults for persisted or operational values.
Continue with the two verified recipes.