콘텐츠로 이동

@gj-kit/format 핵심 개념

timestamp가 화면마다 달라지려면 누군가 그렇게 적어야 합니다. timeZone에는 기본값이 없어서, 생략하면 컴파일이 되지 않습니다.

한 제품의 admin 앱과 mobile 앱에 포매터가 세 벌 있었습니다. 같은 timestamp가 화면마다 9시간씩 어긋났고, 같은 금액이 한쪽에서는 ₩1,000, 다른 쪽에서는 1,000원으로 찍혔으며, null은 조용히 0이 됐습니다. 원인은 그 선택마다 기본값이 있었다는 것입니다. new Date(‘2026-06-08T09:05:00’)은 포매터가 값을 보기 전에 기기 시간대로 해석해 버리고, Intl의 currency 경로는 호출부가 currencyDisplay: ’symbol’을 적어도 es-ES 기기에서는 ’1000 KRW’를 냅니다.

표시 정책은 호출 위치에 둡니다

섹션 제목: “표시 정책은 호출 위치에 둡니다”

시간대, locale, separator는 사용자에게 보이는 정책입니다. 이를 요구하면 기기 기본값이 운영 timestamp나 공유 label을 바꾸지 못합니다.

통화, byte, 빈 값에는 선언된 표현이 필요합니다. formatter가 0이 부재인지 사용할 수 있는 측정값인지 추측해서는 안 됩니다.

  • timeZone에 기본값이 없습니다 — formatDateTime(instant)은 물론, timeZone과 separator 중 하나만 넘긴 호출도 컴파일 에러입니다. 둘 다 필수이고, 기기 시간에 기대려면 'device'라고 직접 적어야 하므로 그 의존이 호출부에 글자로 남습니다.
  • 날짜 문자열은 포매터에 닿지 못합니다 — FormatDateInput은 Date | number입니다. API가 준 문자열은 assumeNoOffset(‘utc’ | ‘device’ | ‘reject’)이 필수인 parseIsoInstant를 거쳐야 하고, 이 파서는 정규식과 Date.UTC 산술만 씁니다 — 엔진마다 결과가 갈리는 Date.parse·new Date(문자열) 경로를 아예 타지 않습니다.
  • 바이트 라벨은 거짓말하지 못합니다 — { system: 'binary', maxUnit: 'GB' }는 컴파일되지 않습니다. 리터럴에서도, as const 변수를 거쳐도 막힙니다 — 두 단위 체계가 서로 다른 union 멤버이기 때문입니다.
  • 상대시간은 시계를 인자로 받습니다 — now가 필수라 함수가 몰래 new Date()를 부르지 않습니다. maxDays와 onOverflow는 쌍으로만 존재해서, 컷오프 뒤 절대시각 표기를 라이브러리가 임의로 정하는 일이 타입상 불가능합니다.
  • ₩ 기호는 locale을 따라 움직이지 않습니다 — formatKrw는 decimal formatter 위에 ₩과 원을 직접 붙이고, style: ‘currency’·’percent’는 src와 dist 양쪽 스캔에서 걸립니다. locale이 정하는 것은 그룹핑·소수점·숫자 글리프까지고, 통화 기호와 % 기호는 정하지 못합니다.

영속 데이터나 운영 값에 암묵적인 기기 시간대 또는 locale 기본값을 의존하지 마세요.

다음은 검증된 레시피 두 개입니다.