한양과학기술고등학교

에듀테크 프로젝트 대시보드

타임존과 UTC

타임존과 UTC

같은 순간이라도 서울에서는 오후 9시, 런던에서는 정오 12시다. 컴퓨터끼리는 이 혼란을 피하려고 시각을 UTC(협정 세계시) 하나로 통일해 저장하고, 화면에 보여줄 때만 사용자의 타임존 오프셋을 더해 지역 시각으로 바꾼다. 아래에서 하나의 순간이 도시마다 어떻게 다르게 읽히는지, 그리고 서머타임(DST) 때문에 하루가 23시간 또는 25시간이 되는 함정을 직접 확인해 보자.

인터랙티브 데모 — 하나의 순간, 여덟 개의 시각

epoch: 0

서머타임(DST)의 함정 — 없는 시각과 중복되는 시각

봄 — Spring Forward (뉴욕, 2026-03-08)

02:00 → 03:00으로 시계가 바로 건너뜀 (하루가 23시간)

01:0002:xx
존재 안 함
03:00

새벽 2시 정각에 시계가 3시로 점프한다. 만약 어떤 로그가 "02:30에 발생"이라고 기록돼 있다면, 그 시각은 이 지역에서 실제로 존재한 적이 없는 시각이라는 뜻 — 오프셋만 저장하고 UTC를 같이 남기지 않으면 이런 값을 다시 해석할 방법이 없다.

가을 — Fall Back (뉴욕, 2026-11-01)

01:00~01:59가 두 번 반복됨 (하루가 25시간)

00:xx01:xx
두 번 발생
02:00

새벽 2시가 되면 시계를 1시로 되돌린다. 그 결과 "01:30"이라는 지역 시각은 같은 날 두 번 존재한다 — 첫 번째 01:30인지 두 번째 01:30인지 지역 시각만으로는 구분할 수 없고, UTC 값이 있어야만 어느 쪽인지 정확히 알 수 있다.

코드로 보는 함정

# 나쁜 예 — 서버 지역 시각을 그대로 저장
created_at = datetime.now()          # 서버가 서울에 있으면 KST, 로스앤젤레스면 PST...
db.save(created_at)                     # 서버를 이전하는 순간 과거 기록 전부가 애매해짐

# 좋은 예 — 항상 UTC로 저장하고, 표시할 때만 변환
created_at = datetime.now(timezone.utc)   # 저장은 항상 UTC 기준
db.save(created_at)

# 화면에 보여줄 때 사용자의 IANA 타임존으로 변환 (오프셋이 아니라 지역 이름!)
local = created_at.astimezone(ZoneInfo("Asia/Seoul"))
print(local.strftime("%Y-%m-%d %H:%M %Z"))

핵심 정리

Unix Epoch

1970-01-01 00:00:00 UTC부터 흐른 초(또는 밀리초)를 정수 하나로 표현한 것. 타임존 개념이 전혀 없어서 어느 나라 서버에서 계산해도 항상 같은 값이 나온다 — 그래서 내부 저장·통신용 표준으로 쓰인다.

오프셋 vs IANA 타임존

"UTC+9"는 오프셋일 뿐이지만 "Asia/Seoul"은 지역 이름이다. 오프셋은 서머타임이 있는 지역에서 계절마다 바뀌므로, 정확한 변환을 하려면 오프셋이 아니라 IANA 타임존 DB의 지역 이름을 저장해야 한다.

DST가 있는 지역

미국·유럽 상당수 지역은 여름에 시계를 1시간 앞당긴다(서머타임). 반면 한국·일본·중국은 DST를 쓰지 않아 연중 오프셋이 고정이다 — 그래서 한국 개발자가 놓치기 쉬운 버그이기도 하다.

흔한 실수

서버의 로컬 시각(datetime.now())을 그대로 저장 → 서버 이전·연합 서비스에서 시각이 뒤엉킴. 항상 UTC로 저장, 표시 시점에만 변환하는 것이 원칙이다.