ISO 8601 UTC string
Kalyx의 모든 값(value, defaultValue, onChange의 인자, DateRange의 각 항목)은 ISO 8601 UTC string 이거나 null 입니다. Date 객체는 절대 쓰지 않습니다.
type ISODateString = string; // 예: "2026-04-15T00:00:00.000Z"
왜 Date가 아닌가?
Date 객체는 가변(mutable)이고, timezone이 모호하며, 직렬화 결과가 일관되지 않습니다. 전형적인 버그는 이렇습니다.
// 서울에 있는 사용자가 4월 15일을 선택
const picked = new Date(2026, 3, 15); // KST에서는 "2026-04-14T15:00:00.000Z" 🤦
// DB에 저장
await save(picked);
// UTC 서버에서 다시 읽음
new Date(picked.toISOString()).getDate(); // 14: 하루 어긋남
ISO string은 이 모호함을 경계에서 제거합니다.
<DatePicker
value="2026-04-15T00:00:00.000Z" // 항상 UTC 자정
onChange={(iso) => save(iso)} // 항상 UTC string
/>
Kalyx가 보장하는 것:
- 시간을 명시적으로 지정하지 않는 한 날짜는 UTC 자정(
T00:00:00.000Z)으로 저장됩니다. onChange는Date를 넘기지 않습니다. string 아니면null뿐입니다.- 내부 연산은 전 구간 UTC 안전한
DateAdapter를 통해 이루어집니다.
로컬 시간으로 표시하기
값은 UTC입니다. 표시는 렌더링 관심사이며, 각 root의 displayFormat과 locale이 담당합니다.
<DatePicker
value={iso}
onChange={setIso}
displayFormat="yyyy-MM-dd"
locale="en-US"
>
<DatePicker.Input />
<DatePicker.Popover>
<DatePicker.Calendar />
</DatePicker.Popover>
</DatePicker>
displayFormat 은 date-fns 포맷 문자열이 아니다. 어댑터는 정확히 여덟 개 토큰
(yyyy, MM, dd, HH, mm, ss, M, d)만 문자열 치환하고 나머지는 그대로 둔다.
월 이름 토큰은 없다. "MMM d, yyyy" 는 "044 15, 2026" 이 된다. MMM 안에서
MM 과 M 이 각각 치환되기 때문이다. 배포된 어댑터 3종 모두 같은 제약을 공유한다.
지역화된 월 이름이 필요하면 Intl.DateTimeFormat 으로 직접 포맷해 트리거 텍스트를
그리는 편이 낫다.
locale 이 결정하는 것은 캘린더의 월·요일 이름(그리고 TimePicker 의 AM/PM 라벨)이지
displayFormat 이 아니다.
내부적으로 어댑터가 요청된 locale로 UTC instant를 포매팅합니다.
Date와 상호 변환하기
레거시 폼 라이브러리처럼 Date로 다리를 놓아야 한다면, 반드시 경계에서 처리하세요.
// Date → ISO
const iso = new Date(2026, 3, 15).toISOString();
// → "2026-04-14T15:00:00.000Z" (여전히 KST의 영향을 받는다!)
// 더 안전한 방법: UTC 자정을 직접 만든다
const iso = new Date(Date.UTC(2026, 3, 15)).toISOString();
// → "2026-04-15T00:00:00.000Z"
// ISO → Date
const date = new Date(iso);
더 세밀한 변환이 필요하면 Kalyx가 제공하는 헬퍼를 쓰세요.
import { normalizeISO, parseInputValue } from '@kalyx/core';
import { DateFnsAdapter } from '@kalyx/adapter-date-fns';
normalizeISO('2026-04-15'); // "2026-04-15T00:00:00.000Z"
parseInputValue('2026-04-15', DateFnsAdapter); // "2026-04-15T00:00:00.000Z"
parseInputValue('nope', DateFnsAdapter); // null
이 두 함수는 @kalyx/react가 아니라 @kalyx/core에 있습니다. React 패키지가 다시 export 하는 것은 DateFnsAdapter 뿐입니다. parseInputValue는 두 번째 인자로 어댑터를 받아 거기서 입력 형식을 읽습니다. 별도의 format 파라미터는 없습니다.
시간과 timezone
TimePicker와 DateTimePicker도 ISO string을 반환합니다. 순수 TimePicker 값의 날짜 부분은 고정된 자리표시자이므로, 시간 필드만 소비하세요(@kalyx/core의 getTime(iso)가 도와줍니다).
IANA timezone을 인식하는 표시·입력("2026-04-15T00:00:00Z"를 "2026-04-15 09:00 KST"로 렌더하거나, 캘린더 클릭이 사용자 존의 civil 자정을 내보내게 하는 것)이 필요하면 displayTimezone prop을 쓰세요. 전용 페이지인 Timezone 개념 문서를 참고하세요.