밀리초 vs 초: 모든 앱을 망가뜨리는 단위 혼동
가장 흔한 타임스탬프 버그는 초가 기대되는 곳에 밀리초를 넘기거나 그 반대입니다. 10자리 대 13자리 규칙, 언어별 기본값, 데이터베이스 영향, 안전한 변환 패턴을 배웁니다.
왜 두 가지 표준이 있나
Unix 시간은 원래 초로 정의되었습니다. 클록 틱마다 1씩 증가하는 시스템에는 정수가 자연스러웠습니다. JavaScript의 Date 객체는 1995년에 도입되어, 브라우저에서 1초 미만 이벤트 타이밍을 지원하기 위해 밀리초를 선택했습니다. 많은 데이터베이스와 백엔드 언어는 Unix 초를 기본값으로 유지했습니다. 이제 두 표준은 JavaScript와 서버 경계를 넘는 모든 코드에 공존하며, 그래서 어떤 값이 유효해 보이면서도 수만 년 뒤의 날짜를 나타낼 수 있습니다.
언어마다 쓰는 단위
구분을 기억하는 가장 안전한 방법: 브라우저 Date API는 보통 밀리초를 원하고, Unix 스타일 서버 API는 보통 초를 노출하며, 최신 시간 라이브러리는 둘 다 제공하는 경우가 많습니다. 서드파티 API 문서를 읽을 때 언어만으로 단위를 추론하지 말고 필드 설명과 예제를 확인하세요.
- Milliseconds: JavaScript Date.now(), Java System.currentTimeMillis(), Java Instant.toEpochMilli(), .NET ToUnixTimeMilliseconds()
- Seconds: Python time.time() (float), PHP time(), Go time.Now().Unix(), Ruby Time.now.to_i, C time(NULL), PostgreSQL EXTRACT(EPOCH)
- Both: Rust — duration.as_secs() for seconds, duration.as_millis() for milliseconds
- Python note: time.time() returns a float, so milliseconds are available as int(time.time() * 1000)
값으로 단위 자동 감지
현대 날짜에 대한 신뢰할 수 있는 휴리스틱: 10자리 숫자는 초, 13자리는 밀리초입니다. 현재 Unix 초는 약 17~21억(10자리)이며 서기 33658년까지 13자리가 되지 않습니다. 현재 Unix 밀리초는 이미 13자리입니다. 이 휴리스틱은 2000년대부터 수천 년 뒤까지 가장 강력합니다. 역사적·음수 날짜나 짧은 테스트 픽스처에서는 추측 대신 명시적 단위를 사용하세요.
- 10자리 값 → 초(예: 1700000000 = 2023-11-14 UTC)
- 13자리 값 → 밀리초(예: 1700000000000 = 2023-11-14 UTC)
- 16자리 값 → 마이크로초(초로 만들려면 1,000,000으로 나눔)
- 음수 값 → 1970년 1월 1일 이전 날짜(초 또는 밀리초)
안전한 변환 패턴
변환 자체는 단순한 산술입니다. 어려운 부분은 변환을 어디서 할지 정하는 것입니다. 시스템 경계에서 변환하고, 변환된 값에 이름을 붙이며, 모호한 원시 숫자를 여러 계층에 그대로 넘기지 마세요.
- Seconds to milliseconds: seconds * 1000
- Milliseconds to whole seconds — JavaScript: Math.floor(ms / 1000)
- Milliseconds to whole seconds — Python: ms // 1000
- Milliseconds to whole seconds — Go: ms / 1000 (integer division)
- Universal guard in JavaScript: const toMs = ts => ts < 1e11 ? ts * 1000 : ts
단위 버그가 프로덕션에서 나타나는 방식
초 대 밀리초 버그는 두 값 모두 그냥 숫자이기 때문에 검증을 통과하기 쉽습니다. 보통 나중에 불가능한 날짜로 나타납니다: 초를 밀리초로 다루면 JavaScript에서 1970년 1월, 백엔드가 밀리초를 초로 다루면 먼 미래 연도가 됩니다.
- JavaScript UI에서 1970년 날짜 → 1000을 곱하지 않고 초를 new Date()에 넘김
- Python·Go·PHP에서 50000년 이상 → 초를 기대하는 API에 밀리초를 넘김
- 만료되지 않는 만료 토큰 → 만료 타임스탬프를 잘못된 단위로 저장
- 즉시 사라지는 캐시 항목 → 밀리초를 두 번 나누거나 초를 두 번 곱함
- 범위가 빈 분석 차트 → 쿼리 경계가 저장된 이벤트 타임스탬프와 다른 단위를 사용
API와 데이터베이스 명명 규칙
작은 명명 규칙이 이런 버그의 대부분을 막습니다. 문서가 매우 명확하지 않은 한 timestamp라는 API 필드를 절대 공개하지 마세요. 의미와 단위를 모두 담은 필드 이름을 선호하세요.
- createdAtMs — Unix milliseconds, best for JavaScript clients
- createdAtSeconds — Unix seconds, common for backend services
- createdAtIso — ISO 8601 string, useful for human-readable API responses
- expiresAtUnixSeconds — explicit enough for auth tokens and signed URLs
- event_time TIMESTAMPTZ — native database time, with conversion handled by the database
밀리초 vs 초 FAQ
- 13자리 타임스탬프는 항상 밀리초인가요?
- 현대의 실제 Unix 타임스탬프에서는 그렇습니다: 13자리는 보통 밀리초를 뜻합니다. 아주 먼 미래의 초 타임스탬프도 13자리에 이를 수 있으니, 중요한 시스템은 명시적 단위 메타데이터를 함께 두어야 합니다.
- 초를 저장할까요, 밀리초를 저장할까요?
- 시스템이 자연스럽게 쓰는 단위를 저장하되 문서화하고 일관되게 유지하세요. JavaScript 중심 시스템은 밀리초를, Unix 스타일 백엔드와 많은 데이터베이스는 초나 네이티브 datetime 열을 자주 씁니다.
- 왜 ms / 1000 대신 Math.floor(ms / 1000)인가요?
- Unix 초는 보통 정수 초입니다. Math.floor는 소수부를 제거하여 정수 초를 기대하는 API가 소수를 받지 않게 합니다.
- 밀리초를 초로 어떻게 변환하나요?
- 1000으로 나누고 소수를 버리세요: JavaScript는 Math.floor(ms / 1000), Python은 ms // 1000, Go는 정수 나눗셈 ms / 1000. 반대로는 초에 1000을 곱하세요.