콘텐츠로 이동

@gj-kit/format

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

  • npm 0.1.2
  • 런타임 의존성 0 · peer 0
  • unit 테스트 350개 이상
  • @ts-expect-error 오용 차단 17건
  • 금지 Intl API를 src·dist 양쪽에서 스캔

한 제품의 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’를 냅니다.

  • 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이 정하는 것은 그룹핑·소수점·숫자 글리프까지고, 통화 기호와 % 기호는 정하지 못합니다.

완료 상태: 호출 위치에서 시간대와 구분자가 명시된 안정적인 표시 문자열을 만듭니다.

Terminal window
pnpm add @gj-kit/format

2. 앱이 소유할 경계를 정합니다

섹션 제목: “2. 앱이 소유할 경계를 정합니다”

코드에서 시간대와 구분자를 선택하세요. 영속·운영 값에 기기 기본값을 물려주지 마세요.

먼저 아래 코드를 복사한 뒤, 위에서 언급한 앱 소유 값만 교체하세요.

import { formatDateTime } from '@gj-kit/format';
export const dateLabel = formatDateTime(Date.UTC(2026, 7, 26, 0, 0), {
timeZone: 'Asia/Seoul',
separator: '-',
});

두 @ts-expect-error 줄은 배포되는 dist/index.d.ts에 대해 strict + exactOptionalPropertyTypes로 검증한 결과입니다. fallback: null은 반환 타입을 딱 string | null 만큼만 넓힙니다.

import { formatBytes, formatDateTime, parseIsoInstant } from '@gj-kit/format';
declare const createdAt: string; // app-owned: an ISO string straight off the API
// @ts-expect-error 'GB' labels a decimal divisor, but 'binary' divides by 1024.
formatBytes(1, { system: 'binary', maxUnit: 'GB', unitSpace: true, nonPositive: 'render' });
// @ts-expect-error a wall-clock string never reaches a formatter — parse it first.
formatDateTime(createdAt, { timeZone: 'Asia/Seoul', separator: '-' });
const instant = parseIsoInstant(createdAt, { assumeNoOffset: 'utc' });
export const stamp: string = formatDateTime(instant, { timeZone: 'Asia/Seoul', separator: '-' });
// fallback widens the return type by exactly what you passed, and nothing more.
export const sizeChip: string | null = formatBytes(0, {
system: 'decimal', unitSpace: false, nonPositive: 'fallback', fallback: null,
});

시간대, locale, 단위, 통화 표시 선택을 호출 위치에서 분명히 해야 할 때 사용합니다.

문서화된 계약 밖의 앱 문구, 사용자 locale 선호, 금융 반올림 정책을 소유시키기 위해 사용하지 마세요.

엔진: node >=20

이 패키지는 peer dependency가 없습니다.

Entry Exported symbols
Root entry 31

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

전체 API reference에는 선언 파일에서 생성한 모든 공개 함수, 클래스, 타입, 상수, 오류 계약이 있습니다. 각 항목은 import path, release signature, package version을 표시합니다.

관계 패키지 함께 쓰는 이유
단독 사용 — 다른 GJ Kit 패키지 없이 도입할 수 있습니다.
선택 조합 @gj-kit/expo-workouts 명시적인 표시 정책으로 운동 날짜와 측정값을 보여 줄 때 함께 사용합니다.

npm · GitHub