@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이 정하는 것은 그룹핑·소수점·숫자 글리프까지고, 통화 기호와 % 기호는 정하지 못합니다.
Golden path
섹션 제목: “Golden path”완료 상태: 호출 위치에서 시간대와 구분자가 명시된 안정적인 표시 문자열을 만듭니다.
1. 설치
섹션 제목: “1. 설치”pnpm add @gj-kit/format2. 앱이 소유할 경계를 정합니다
섹션 제목: “2. 앱이 소유할 경계를 정합니다”코드에서 시간대와 구분자를 선택하세요. 영속·운영 값에 기기 기본값을 물려주지 마세요.
3. 최소 연결부터 시작합니다
섹션 제목: “3. 최소 연결부터 시작합니다”먼저 아래 코드를 복사한 뒤, 위에서 언급한 앱 소유 값만 교체하세요.
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 선호, 금융 반올림 정책을 소유시키기 위해 사용하지 마세요.
환경과 peer
섹션 제목: “환경과 peer”엔진: node >=20
이 패키지는 peer dependency가 없습니다.
공개 subpath
섹션 제목: “공개 subpath”| Entry | Exported symbols |
|---|---|
Root entry |
31 |
안전 경계
섹션 제목: “안전 경계”영속 데이터나 운영 값에 암묵적인 기기 시간대 또는 locale 기본값을 의존하지 마세요.
API reference
섹션 제목: “API reference”전체 API reference에는 선언 파일에서 생성한 모든 공개 함수, 클래스, 타입, 상수, 오류 계약이 있습니다. 각 항목은 import path, release signature, package version을 표시합니다.
패키지 관계
섹션 제목: “패키지 관계”| 관계 | 패키지 | 함께 쓰는 이유 |
|---|---|---|
| 단독 사용 | — | 다른 GJ Kit 패키지 없이 도입할 수 있습니다. |
| 선택 조합 | @gj-kit/expo-workouts |
명시적인 표시 정책으로 운동 날짜와 측정값을 보여 줄 때 함께 사용합니다. |