viewer.csslab.dev

타임스탬프에서 초와 밀리초를 가리는 법

자릿수를 센다. 요즘 날짜라면 초 단위는 10자리, 밀리초는 13자리, 마이크로초는 16자리다. 값 자체에는 단위를 알리는 표시가 없고 모든 시스템이 따르는 관행도 없어서 크기가 유일한 신호다. 그래서 천 배 큰 값이 오류가 아니라 서기 55000년쯤의 날짜로 나온다.

애초에 왜 단위가 셋인가

유닉스 시간은 초로 정의됐고, POSIX·대부분의 C API·JWT 클레임·데이터베이스 에포크 컬럼이 지금도 그것을 쓴다. 자바스크립트의 Date.now() 는 밀리초를 돌려주므로 브라우저나 Node 를 거친 값은 대개 밀리초다. 로깅과 트레이싱 시스템은 더 잘게 나누고 싶어서 마이크로초나 나노초를 쓴다.

셋 다 틀린 게 아니다. 문제는 셋을 모두 "타임스탬프"라고 부르는데 값에는 어느 것인지가 담겨 있지 않다는 것이다. 수는 그냥 수다.

실제로 어디서 깨지나

JWT 가 흔한 경우다. RFC 7519 는 exp·iat·nbf 를 NumericDate 로 정의하고 그건 초다. Date.now() 로 토큰을 발급하면 만료가 천 배 멀어진다 — 토큰이 영영 만료되지 않고, 폐기한 세션이 왜 아직 되는지 누가 확인하기 전까지는 깨진 것처럼 보이지도 않는다.

반대 방향은 더 눈에 띄게 실패한다. 어딘가에서 나눠진 밀리초 값을 초로 읽으면 1970년에 떨어진다. 레코드의 1970-01-01 은 실제 데이터인 경우가 거의 없다. 0 이거나, 빈 값이거나, 단위가 어긋난 것이다.

정렬이 조용한 쪽이다. 한 컬럼에 초와 밀리초가 섞이면, 1700000000000 이 1799999999 보다 크기 때문에 언제 일어난 일인지와 무관하게 밀리초 값이 전부 초 값 뒤로 정렬된다.

그래서 어떻게 하나

내가 정하는 경계라면 이름에 단위를 넣는다 — expires_at_seconds, created_at_ms. 비용이 없고 질문이 영구히 사라진다.

더 나은 것은 ISO 8601 문자열을 보내는 것이다 — 2026-09-05T12:00:00Z. 형식 자체가 그 값이 무엇인지 말하고, 어떤 산술도 그것을 조용히 다시 해석할 수 없다. 비교하거나 작게 저장해야 하는 자리에는 에포크 정수를, 사람이나 다른 시스템이 읽어야 하는 자리에는 문자열을 쓴다.

내가 고르지 않은 값을 받는 쪽이라면 무언가 하기 전에 자릿수부터 확인하고, 그럴듯한 범위를 벗어나는 값은 변환하지 말고 거부한다. 1970년이나 서기 5만 년의 타임스탬프는 날짜가 아니라 버그 리포트다.

관련 도구

이 도구가 따르는 규격

고친 날 2026-09-05