시간은 프로그래밍에서 가장 까다로운 주제 중 하나입니다. 단순해 보이지만 시간대, 서머타임, 윤초 같은 개념이 겹치면 엄청나게 복잡해지거든요. 글로벌 서비스를 만들면서 시간 관련 버그를 여러 번 겪고 나서야 제대로 이해하게 됐습니다. 그 경험을 바탕으로 꼭 알아야 할 핵심만 정리해봤어요.
Unix 타임스탬프란?
1970년 1월 1일 00:00:00 UTC부터 지금까지 경과한 초(seconds)의 수. 이게 전부입니다. 현재 시각이 2026년 3월 28일이라면 대략 1,774,000,000 정도 되는 숫자예요.
1970-01-01 00:00:00 UTC = 0
2000-01-01 00:00:00 UTC = 946684800
2026-03-28 00:00:00 UTC = 1774396800
왜 이런 방식을 쓰느냐고요? "2026년 3월 28일 오후 3시 한국 시간"이라는 표현은 사람에게는 직관적이지만, 컴퓨터가 비교하거나 계산하기에는 불편합니다. 단순한 정수 하나로 시간을 표현하면 크기 비교, 차이 계산, 저장이 훨씬 간단해지거든요.
JavaScript에서는 Date.now()가 밀리초 단위 타임스탬프를 반환합니다. 초 단위 Unix 타임스탬프를 얻으려면 1000으로 나눠야 해요. 이 차이 때문에 API 연동 시 10자리인지 13자리인지 혼동이 오는 경우가 종종 있습니다.
UTC, GMT, KST: 시간대의 기본
UTC(Coordinated Universal Time)는 전 세계의 시간 기준점입니다. 영국 그리니치 천문대의 GMT(Greenwich Mean Time)와 실질적으로 같다고 봐도 되지만, 엄밀히 따지면 UTC는 원자 시계 기반이고 GMT는 천문 관측 기반이라는 차이가 있어요.
한국 표준시(KST)는 UTC+9입니다. UTC 기준 자정이면 한국은 오전 9시라는 뜻이죠. 미국 동부(EST)는 UTC-5, 일본(JST)은 한국과 같은 UTC+9입니다.
데이터베이스에 시간을 저장할 때는 항상 UTC로 저장하는 게 정석입니다. 그리고 사용자에게 보여줄 때만 해당 사용자의 시간대로 변환하세요. 이 규칙만 지키면 시간대 관련 버그의 대부분을 예방할 수 있습니다.
서머타임: 개발자의 악몽
한국은 서머타임을 시행하지 않아서 체감이 안 되실 수 있지만, 미국이나 유럽 사용자가 있는 서비스라면 반드시 고려해야 합니다. 서머타임이 시작되는 날에는 시계가 1시간 앞으로 건너뛰고, 끝나는 날에는 같은 시간이 두 번 존재합니다.
실제 사례를 하나 들어볼게요. 미국 동부 시간 기준으로 매일 오전 2시 30분에 실행되는 배치 작업이 있다고 합시다. 서머타임 시작일에는 오전 2시가 오전 3시로 건너뛰기 때문에 2시 30분이 존재하지 않습니다. 서머타임 종료일에는 1시 59분 다음에 다시 1시가 되기 때문에 배치가 두 번 실행될 수 있어요.
이런 문제를 피하려면 시간 계산에 UTC를 사용하고, 로컬 시간으로의 변환은 라이브러리에 맡기세요. Python의 pytz, JavaScript의 Intl API나 date-fns-tz 같은 도구가 서머타임 전환을 자동으로 처리해줍니다.
ISO 8601: 시간 표기의 국제 표준
API에서 시간을 주고받을 때는 ISO 8601 형식을 사용하는 게 좋습니다. 형태는 이렇습니다:
2026-03-28T15:30:00+09:00 (한국시간)
2026-03-28T06:30:00Z (UTC, Z는 UTC를 의미)
이 두 표현은 같은 시점을 나타냅니다. +09:00이 붙으면 해당 시간대 오프셋이 명시되어 있어서 혼동이 없어요. Z(Zulu time)는 UTC를 의미합니다.
"2026/03/28 3:30 PM" 같은 포맷은 사람이 보기에는 편하지만, 시간대 정보가 빠져있고, 12시간제인지 24시간제인지도 모호합니다. 기계 간 통신에서는 반드시 ISO 8601을 사용하세요.
다국적 서비스에서 시간 처리하기
서울의 사용자가 작성한 게시글을 뉴욕의 사용자가 본다면, "3시간 전"이라는 상대 시간 표시가 가장 깔끔합니다. 절대 시간을 보여줘야 한다면 사용자의 로컬 시간으로 변환해서 보여주세요.
예약 시스템이나 이벤트 시스템에서는 더 주의가 필요합니다. "3월 28일 오후 3시에 웨비나가 있습니다"라고 할 때, 이게 어느 시간대의 오후 3시인지 반드시 명시해야 해요. "오후 3시 KST (오전 6시 UTC)"처럼 두 가지를 병기하거나, 시스템에서 자동으로 사용자의 현지 시간으로 변환해서 보여주는 방식이 좋습니다.
자주 만나는 함정들
날짜 경계 문제: UTC 기준 3월 28일 23시 30분은 한국 시간으로 3월 29일 오전 8시 30분입니다. 일별 통계를 계산할 때 어느 시간대 기준의 "하루"인지에 따라 결과가 달라질 수 있어요.
밀리초 vs 초: JavaScript는 밀리초(13자리), Unix 전통은 초(10자리). API 문서를 꼼꼼히 읽지 않으면 1970년 1월 어느 날로 변환되는 황당한 결과를 만날 수 있습니다.
2038년 문제: 32비트 정수로 표현할 수 있는 Unix 타임스탬프의 최댓값은 2038년 1월 19일입니다. 64비트 시스템에서는 문제없지만, 오래된 임베디드 시스템이나 레거시 데이터베이스에서는 여전히 이슈가 될 수 있어요.
윤초: 지구 자전 속도의 미세한 변화를 보정하기 위해 간헐적으로 추가되는 1초입니다. 대부분의 애플리케이션에서는 무시해도 되지만, 초 단위 정밀도가 필요한 시스템(금융 거래, 과학 계측)에서는 고려해야 합니다.
실무 팁 정리
- 저장은 UTC, 표시는 로컬. 이 원칙을 지키면 대부분의 문제를 피할 수 있습니다.
- 시간 문자열에는 반드시 시간대 정보를 포함하세요. 시간대 없는 시간은 의미가 모호합니다.
- 시간 계산은 라이브러리에 맡기세요. 직접 구현하면 서머타임, 윤년, 시간대 오프셋 변경 등을 빠뜨릴 수 있습니다.
- 테스트할 때는 다양한 시간대에서 테스트하세요. 시스템 시간대를 바꿔가며 테스트하면 숨겨진 버그를 찾을 수 있습니다.
- 사용자의 시간대는 서버가 아니라 클라이언트에서 감지하세요. Intl.DateTimeFormat().resolvedOptions().timeZone으로 브라우저의 시간대를 알 수 있습니다.
정리하며
시간은 단순해 보이지만, 글로벌 환경에서는 예상 밖으로 복잡한 주제입니다. 핵심은 UTC를 기준으로 저장하고 처리하되, 사용자에게 보여줄 때만 로컬 시간으로 변환하는 것. 이 원칙을 기억하면서 좋은 라이브러리와 도구를 활용하면 시간 관련 버그에서 많이 벗어날 수 있습니다.