핵심 요약
날짜/시간 버그의 90%는 "언제 어느 타임존으로 다루는지"가 뒤섞여 생긴다. 정석은 하나다 — 저장·연산은 UTC로, 표시할 때만 사용자 타임존으로 변환. 서버·DB·API는 UTC로 통일하고, 지역 시간은 오직 화면에서만 만든다.
1. 원칙
| 계층 | 다루는 방식 |
|---|---|
| DB 저장 | UTC (timestamptz 권장) |
| 서버 연산 | UTC |
| API 전송 | ISO 8601 (…Z) |
| 화면 표시 | 사용자 타임존으로 변환 |
2. 저장과 전송
// 항상 UTC ISO 문자열로 주고받기
new Date().toISOString() // "2026-07-05T04:00:00.000Z"
// PostgreSQL: timestamptz는 UTC로 정규화 저장
// timestamp(타임존 없음)는 피할 것 — 해석이 모호
3. 표시할 때 변환
// 사용자 로캘·타임존으로 포맷
new Intl.DateTimeFormat('ko-KR', {
dateStyle: 'medium', timeStyle: 'short',
timeZone: 'Asia/Seoul',
}).format(new Date(isoString))
// → 2026. 7. 5. 오후 1:00
4. 흔한 함정
new Date("2026-07-05")는 UTC 자정으로 해석 — 한국에선 하루 밀려 보일 수 있다- 서버 로컬 타임존에 의존하지 말 것(
TZ=UTC로 고정) - DST(서머타임)가 있는 지역은 오프셋이 계절마다 바뀐다 — 고정 "+9" 하드코딩 금지, 지역명(Asia/Seoul)을 쓴다
- "오늘"의 경계도 타임존마다 다르다 — 일별 집계는 어느 타임존 기준인지 명시
5. 실무 팁
- 날짜만 필요한 값(생일 등)은 시간·타임존 없는
DATE로 저장 - 복잡한 타임존 계산은
date-fns-tz·Luxon·Temporal같은 라이브러리에 맡긴다 - 서버·DB·컨테이너 타임존을 모두 UTC로 통일하면 사고가 급감한다
자주 묻는 질문
왜 KST로 바로 저장하면 안 되나요?
사용자가 여러 지역에 있거나 서버가 이전되면 기준이 흔들립니다. UTC로 저장하면 절대 기준점이 하나로 고정돼 어느 타임존으로든 정확히 변환할 수 있습니다. 지역 시간은 표시 순간에만 만드는 게 안전합니다.
"+9:00"으로 오프셋을 저장하면 되지 않나요?
서머타임이 있는 지역은 오프셋이 계절마다 바뀌어 고정 숫자로는 틀립니다. 한국은 DST가 없지만, 이식성과 미래 대비를 위해 오프셋 숫자 대신 Asia/Seoul 같은 IANA 지역명을 쓰는 게 정석입니다.
날짜만 필요한데도 UTC 시간까지 저장해야 하나요?
생일·기념일처럼 시간 개념이 없는 값은 타임존 없는 DATE 타입으로 저장하는 게 맞습니다. 여기에 시간을 붙이면 타임존 변환 과정에서 하루가 밀리는 버그가 생깁니다. 값의 성격에 맞는 타입을 고르세요.

댓글 0