이 글에서 얻는 것
- “시간”을 Instant(절대 시각)과 LocalDateTime(지역 시각)로 구분해, UTC 저장/표시 변환을 안전하게 설계할 수 있습니다.
- DST(서머타임) 때문에 생기는 대표 함정(존재하지 않는 시각, 두 번 존재하는 시각)을 이해하고 스케줄/마감 처리에서 사고를 줄일 수 있습니다.
- Locale/i18n(메시지/포맷/통화)을 백엔드 계약 관점에서 정리하고, API에서 무엇을 고정해야 하는지 기준이 생깁니다.
0) 시간 문제는 대부분 “정의가 섞여서” 생긴다
백엔드에서 다루는 시간은 크게 3종류입니다.
- Instant(절대 시각): “지구상 한 점에서의 실제 순간” (UTC 기준 타임스탬프)
- Local date/time(지역 시각): “그 지역 달력 기준 시각” (타임존 규칙이 필요)
- Duration/Period: “기간”(1시간/1일/1개월) — 달력 기반 기간은 DST 영향을 받을 수 있음
이걸 섞으면 사고가 납니다.
1) 원칙: 저장/전송은 UTC, 표시는 사용자 타임존
실무에서 가장 안전한 기본값:
- DB 저장: UTC 기반(Instant, timestamp with time zone 개념)
- API 전송: ISO8601 + offset/zone 정보를 명확히
- UI 표시: 사용자 타임존/로케일에 맞게 변환
그리고 서버/DB의 기본 타임존도 UTC로 고정하는 편이 안전합니다.
2) DST(서머타임)가 터지는 지점
DST가 있는 지역에서는 “지역 시각”이 다음을 겪습니다.
- 존재하지 않는 시각: 새벽 2시가 3시로 점프(그 2시는 존재하지 않음)
- 두 번 존재하는 시각: 2시가 두 번 반복(어느 2시인지 애매함)
그래서 스케줄/마감은 아래 중 하나로 “정의”를 고정해야 합니다.
- “절대 시각(UTC) 기준”인지
- “지역 달력 기준(매일 9시)”인지
예: “매일 9시에 알림”은 지역 달력 기준입니다. 이 경우 DST가 바뀌면 UTC 시각이 변합니다.
3) 마감/기간 계산의 함정
- “오늘 자정까지”는 사용자 타임존에 따라 다릅니다.
- “1일 뒤”는 24시간 뒤가 아닐 수 있습니다(DST로 23/25시간이 될 수 있음).
기간을 정의할 때:
- 초/분/시간 단위면 Duration(절대 시간)
- 월/년 단위면 Period(달력)
처럼 구분하는 편이 좋습니다.
4) 국제화(i18n): API는 ‘표준 포맷’을 유지하라
백엔드의 역할:
- 저장/전송은 표준(ISO8601, 숫자는 raw value)
- 표시 포맷은 UI/클라이언트에 맡긴다
예외:
- 이메일/푸시 메시지처럼 서버가 사용자에게 직접 렌더링하는 경우엔 locale별 템플릿/메시지 번들이 필요합니다.
5) 운영/테스트: 시간은 반드시 시뮬레이션한다
시간 관련 버그는 재현이 어렵습니다. 그래서 테스트 전략이 중요합니다.
- Clock 주입으로 “현재 시간”을 테스트에서 고정
- DST 전환 날짜를 포함한 케이스(두 번 존재/존재하지 않는 시각)
- 타임존이 다른 사용자(예: 서울/LA)의 “오늘/이번 주” 범위가 달라지는 케이스
연습(추천)
- “매일 9시 알림”을 설계해보기: 저장은 어떤 값으로 할지(UTC? local time + zone?)와 DST 때 동작을 정의해보기
- “오늘 00:00~23:59” 조회 API를 사용자 타임존 기준으로 구현하고, 서버/DB는 UTC인 상태에서 올바르게 동작하는지 테스트해보기
- DST 전환일(해당 지역)을 골라, 존재하지 않는 시각/두 번 존재하는 시각을 처리하는 테스트를 작성해보기
💬 댓글