dayjs 날짜 표기 카탈로그

1. 문제 — 날짜 표기를 한곳으로 모으고 싶다

화면마다 dayjs().format('…')이 흩어져 같은 날짜가 다르게 찍힌다. 날짜 표기를 받는 인자를 타입으로 닫아 허용된 표기만 쓰게 하고 싶다.

2. 첫 발상 — 매핑 객체

표기 이름과 포맷 문자열을 짝짓는 객체({ DATE: 'YYYY-MM-DD', … })부터 떠올랐다. i18n 습관이다 — i18n은 이름표(인사)와 내용(Hello)이 달라서 짝지어 주는 객체가 필수였다.

그런데 dayjs는 포맷 문자열이 곧 키이자 값이다.1 이름을 따로 붙이면 같은 정보를 두 번 들고 있게 된다. 매핑 객체는 필요 없다.

3. 해결 — 배열 하나에서 파생

허용 표기를 as const 배열 하나에 두고, 타입과 진입점을 거기서 파생한다.

import dayjs from 'dayjs'

export const DATE_FORMATS = [
  'YYYY-MM-DD',
  'YYYY-MM-DDTHH:mm:ss',
  'HH:mm',
  'YYYY년 M월',
  'YY년 M월 D일',
  'YYYY.MM.DD',
  'YY.MM.DD',
  'YYYY.MM.DD(ddd)',
  'YYYY.MM.DD HH:mm',
] as const

export type DateFormat = (typeof DATE_FORMATS)[number]

export const formatDate = (
  date: dayjs.ConfigType,
  format: DateFormat,
): string => dayjs(date).format(format)
  • DATE_FORMATS — 허용 표기의 단일 출처
  • DateFormat — 배열에서 파생된 닫힌 union. 따로 손댈 곳이 없다
  • formatDate — 유일한 진입점

4. 새 표기가 필요해지면

  1. 여러 곳에서 통일이 필요한 표기인지 먼저 본다. 일회성('M.D' 등)이면 raw dayjs().format()으로 둔다 — 이 기준이 없으면 배열이 잡동사니 enum이 된다.
  2. DATE_FORMATS에 추가한다. 그게 전부다.
  3. ddd가 들어 있으면 진입점에 dayjs.locale('ko')가 있는지 본다.2

5. 남은 구멍

  • raw 우회는 타입이 못 막는다. 카탈로그에 있는 표기를 formatDate 없이 raw로 다시 써도 오류가 없다. no-raw-date-format(no-restricted-syntax)이 필요한데 아직 안 썼다. 그 규칙 전까지 단일 출처는 관행일 뿐이다.
  • ddd는 조용히 샌다. dayjs.locale('ko') 없이 쓰면 Mon·Tue처럼 영어로 나오고, 예외도 타입 오류도 없다. YYYY.MM.DD(ddd)만 런타임 전제를 가진 항목이다.

Footnotes

  1. dayjs — Format ↩

  2. dayjs — Loading locale ↩

#576