유닉스 타임스탬프 완벽 가이드: 초·밀리초·마이크로초 변환과 시간대 베스트 프랙티스
유닉스 타임스탬프(Unix timestamp)는 ‘유닉스 에폭(1970년 1월 1일 00:00:00 UTC)으로부터 경과한 시간’을 나타냅니다. 전 세계 웹 서버와 데이터베이스 대부분이 내부적으로 이 표현을 사용합니다. 정밀도 차이, 언어별 구현, 시간대 처리, 일광 절약 시간제 주의점을 정리했습니다.
유닉스 타임스탬프의 기원과 정의
유닉스 에폭은 1970년 1월 1일 00:00:00 UTC에 시작합니다. 유닉스 타임스탬프 0은 바로 이 순간을 가리키고, 1262304000은 2010-01-01 00:00:00 UTC를 나타냅니다. 유닉스 타임스탬프는 기본적으로 윤초(leap second)를 반영하지 않습니다.
원래는 32비트 부호 있는 정수로 저장했기 때문에 Y2038 문제가 생겼습니다. 최댓값이 2038년 1월 19일 03:14:07 UTC에 오버플로됩니다. 최신 시스템은 64비트 정수를 사용하므로 표현 가능한 시간 범위가 크게 확장되었습니다.
시간 정밀도 비교
| 단위 | 1초당 개수 | 자릿수 | 대표 용도 |
|---|---|---|---|
| 초 (s) | 1 | 10자리 | 전통적인 Unix/Linux 시스템 |
| 밀리초 (ms) | 1,000 | 13자리 | JavaScript, Java, 로깅 |
| 마이크로초 (μs) | 1,000,000 | 16자리 | 분산 트레이싱, 데이터베이스 |
| 나노초 (ns) | 1,000,000,000 | 19자리 | Go 언어, 성능 분석 |
실용 판별법: 10자리는 초, 13자리는 밀리초, 16자리는 마이크로초, 19자리는 나노초입니다.
JavaScript의 타임스탬프
JavaScript(자바스크립트)는 기본 단위로 밀리초를 사용합니다. Date() 생성자는 입력 숫자를 밀리초로 간주하므로, 초 단위 타임스탬프를 넘길 때는 1000을 곱해야 합니다.
// 현재 유닉스 타임스탬프 가져오기 (밀리초)
const timestampMs = Date.now();
console.log(timestampMs); // 출력 예시: 1692268800123
// 초 단위 타임스탬프: 밀리초를 1000으로 나누고 내림
const timestampSec = Math.floor(Date.now() / 1000);
console.log(timestampSec); // 출력 예시: 1692268800
// 유닉스 타임스탬프를 Date 객체로 변환
let ts = 1692268800;
let date = new Date(ts * 1000);
console.log(date.toISOString()); // "2023-08-17T16:00:00.000Z"
Python의 타임스탬프
Python의 time.time()은 초 단위의 유닉스 타임스탬프를 부동소수점 값으로 반환합니다.
import time
from datetime import datetime, timezone
# 현재 유닉스 타임스탬프 가져오기 (초, 부동소수점)
now_sec = time.time()
print(now_sec) # 출력 예시: 1692268800.123456
# 밀리초 가져오기 (정수)
now_millis = int(time.time() * 1000)
print(now_millis) # 예시: 1692268800123
# 나노초 가져오기 (Python 3.7 이상)
now_nanos = time.time_ns()
print(now_nanos) # 예시: 1692268800123456789
# datetime으로 변환
ts = 1692268800
dt_local = datetime.fromtimestamp(ts)
dt_utc = datetime.fromtimestamp(ts, timezone.utc)
print(dt_local.strftime("%Y-%m-%d %H:%M:%S")) # 로컬 시각
print(dt_utc.strftime("%Y-%m-%d %H:%M:%S")) # UTC 시각
Go 언어의 타임스탬프
Go의 time 패키지는 다양한 정밀도 수준을 지원합니다.
package main
import (
"fmt"
"time"
)
func main() {
// 현재 유닉스 타임스탬프 가져오기
sec := time.Now().Unix() // 초
msec := time.Now().UnixMilli() // 밀리초 (Go 1.17 이상)
nsec := time.Now().UnixNano() // 나노초
fmt.Println(sec) // 예시: 1692268800
fmt.Println(msec) // 예시: 1692268800123
fmt.Println(nsec) // 예시: 1692268800123456789
// 타임스탬프를 Time 객체로 변환
t := time.Unix(sec, 0)
fmt.Println(t.UTC())
fmt.Println(t)
}
흔한 실수와 베스트 프랙티스
실수 예시: 단위가 모호함
// ✗ 오류: 타임스탬프 단위가 모호함
const timestamp = 1692268800;
const date = new Date(timestamp); // 오류: 밀리초로 해석되어 1970년이 표시됨
// ✓ 올바름: 단위를 명확히 구분
const timestampSec = 1692268800;
const timestampMs = 1692268800000;
const dateFromSec = new Date(timestampSec * 1000);
필드 네이밍 베스트 프랙티스
log_entry = {
"timestamp_ms": 1692268800123, # 밀리초 단위
"timestamp_iso": "2023-08-17T16:00:00Z", # ISO 8601 UTC
"event_type": "user_login",
"user_id": 12345
}
시간대 처리의 함정
시간대를 다룰 때 주의할 네 가지입니다.
- 로컬 시각과 UTC 시각의 혼동 — 유닉스 타임스탬프는 언제나 UTC 기준입니다. 로컬 시각 변환은 표시 시점에만 하세요.
- 시간대 정보를 저장하지 않음 — 모호성이 생깁니다. 항상 UTC로 저장하거나 오프셋을 함께 기록하세요.
- 시스템 간 시간대 불일치 — 모든 시스템이 동일한 시간대 기준(UTC 권장)을 사용해야 합니다.
- DST 오프셋의 수동 계산 — 내장 라이브러리에 맡기고 하드코딩은 피하세요.
권장 접근은 **‘저장은 표준화, 표시는 현지화’**입니다. 저장과 전송 단계에서는 UTC 또는 유닉스 타임스탬프를 쓰고, 표시 시점에는 사용자의 시간대에 맞춰 포매팅합니다. 한국 서비스라면 KST 변환은 표시 레이어에서만 수행하는 편이 안전합니다.
일광 절약 시간제(Daylight Saving Time, DST) 이슈
DST는 문제가 되는 두 구간을 만듭니다.
- 건너뛰는 시간: DST 시작 시 시계가 앞으로 건너뜁니다. 02:00이 곧장 03:00으로 넘어가면서 02:30은 존재하지 않습니다.
- 겹치는 시간: DST 종료 시 01:00–01:59가 두 번 나타나 모호성이 생깁니다.
유닉스 타임스탬프 자체는 연속적이며 DST의 영향을 받지 않지만, 로컬 시각으로 변환할 때는 세심한 처리가 필요합니다. 한국(KST)은 DST를 채택하지 않지만, 해외 서버나 사용자를 다룰 때는 DST 대응이 필요합니다.
로깅, 데이터베이스, API 베스트 프랙티스
로깅 시스템
UTC 시간대 + ISO 8601 포맷으로 통일하세요. 필요에 따라 밀리초 또는 마이크로초 정밀도의 타임스탬프를 함께 기록합니다.
데이터베이스
시간대를 포함한 네이티브 날짜·시각 타입을 먼저 고려하세요. 숫자형 타임스탬프에는 BIGINT를 쓰고, 필드 이름에 단위를 명시합니다(예: *_epoch_ms).
API
포맷과 단위를 명확히 정의하세요. 외부 인터페이스에는 ISO 8601(가독성이 높고 시간대 정보 포함)을, 내부 시스템에서는 숫자형 타임스탬프를 써도 됩니다. 단 문서에 단위를 반드시 기재하세요.
자주 발생하는 오류 시나리오
- 13자리 파싱 실패: 밀리초 타임스탬프를 초 단위를 기대하는 함수에 넣으면 오버플로가 발생합니다. 먼저 자릿수로 단위를 확인하세요.
- 포맷 불일치: 잘못된 날짜, 시간대 누락, DST 전환 시점이 파싱 오류의 원인이 됩니다.
- 시간대 오프셋 어긋남: 정수 시간 단위로 어긋난다면(예: ±8시간) 시간대 통일이 누락되었을 가능성이 높습니다. 내부에서는 UTC를 쓰세요.
- 숫자 오버플로: 32비트 시스템은 2038년 제약을 받으므로 64비트 정수를 쓰세요.
자주 묻는 질문
왜 하필 1970년 1월 1일인가요?
유닉스 운영체제 개발이 1970년에 시작되었고, 10년 단위로 끊어지는 해라 기억하고 계산하기 쉬웠습니다. 32비트 정수로 1970년부터 2038년까지 표현할 수 있었고, 당시에는 그 정도로도 충분했습니다.
초와 밀리초는 어떻게 구분하나요?
자릿수로 판단합니다(10자리=초, 13자리=밀리초, 16자리=마이크로초, 19자리=나노초). 혹은 파싱 후 결과가 타당한지 역검산하거나 온라인 변환 도구를 씁니다.
Y2038 문제는 아직 유효한가요?
64비트 정수를 쓰는 최신 시스템에서는 대부분 해소되었습니다. 주요 프로그래밍 언어는 이미 64비트 타임스탬프를 지원합니다. 다만 레거시 32비트 시스템이나 임베디드 기기는 여전히 주의가 필요합니다.
DST가 문제를 일으키는 이유는 무엇인가요?
건너뛰는 시간대의 불연속성과 겹치는 시간대의 중복이 파싱 모호성을 일으킵니다. 시스템마다 모호한 시각을 다르게 처리합니다. 해결책은 내부에서 UTC를 쓰고 표시 시점에만 로컬 시각으로 변환하는 것입니다.
JavaScript와 Python의 타임스탬프가 다른 이유는 무엇인가요?
JavaScript의 Date.now()는 밀리초(13자리)를 반환하고, Python의 time.time()은 초(마이크로초 정밀도의 부동소수점)를 반환하기 때문입니다. 변환하려면 1000으로 나누거나 곱해야 합니다.
최신 동향과 앞으로의 전망
- 고정밀화: 금융 거래, IoT, 실시간 시스템은 나노초 수준의 정확도를 요구합니다
- 분산 동기화: NTP와 PTP 프로토콜로 정확도가 개선되고 있습니다
- 블록체인 타임스탬프: 암호 자산은 변조 방지와 고정밀도를 함께 갖춘 타임스탬프가 필요합니다
- 성능 최적화: SIMD 명령어, 메모리 캐싱, 병렬 처리로 변환 속도가 빨라지고 있습니다
직접 해보기
유닉스 타임스탬프 변환기는 초·밀리초·마이크로초를 자동 감지하며, 100% 브라우저 안에서 동작합니다.
PostgreSQL을 쓴다면 PostgreSQL의 timestamp 컬럼에는 정확히 무엇이 저장될까?에서 Postgres 특유의 시간대 함정을 확인하세요.
요약
핵심 한 줄: 저장은 UTC로, 표시는 로컬 시각으로, 타임스탬프 단위는 언제나 명시적으로. 인코딩, 해싱, 데이터 변환 등은 개발자 필수 도구 가이드에서 함께 정리했습니다.