Unix 시간 초·밀리초 구분법과 2038년 문제
2026.10.03 업데이트 · 6분 읽기
Unix 시간은 1970년부터 센 초이고 시간대가 없습니다. 자릿수로 초와 밀리초를 구분하는 법, 한국 시간으로 바꿀 때의 함정, 32비트 정수가 넘치는 2038년 문제와 점검할 곳을 정리했습니다.
Unix 타임스탬프 변환 바로 사용하기Unix 시간은 1970-01-01T00:00:00Z부터 센 초이고, 시간대가 없습니다. 지금 시점의 값은 10자리면 초, 13자리면 밀리초이며, 이 둘을 섞는 것이 가장 흔한 타임스탬프 버그입니다. 2038년 문제는 초를 부호 있는 32비트 정수에 담는 곳에만 해당하고, 그 한계는 2038-01-19T03:14:07Z입니다. 값을 Unix 타임스탬프 변환에 붙여 넣으면 어떤 단위로 읽었는지와 UTC·한국 시간 날짜를 바로 볼 수 있습니다.
초·밀리초·마이크로초·나노초
같은 순간(2026-01-01T00:00:00Z)을 네 단위로 적으면 다음과 같습니다.
| 단위 | 값 | 자릿수 | 만나는 곳 |
|---|---|---|---|
| 초 | 1767225600 | 10 | POSIX time(), JWT의 exp·iat |
| 밀리초 | 1767225600000 | 13 | JavaScript Date.now() |
| 마이크로초 | 1767225600000000 | 16 | 마이크로초 정밀도의 로그·DB |
| 나노초 | 1767225600000000000 | 19 | 트레이싱, 고해상도 시계 |
초 단위 값은 2001-09-09T01:46:40Z부터 2286-11-20T17:46:39Z까지 10자리이므로 자릿수로 구분할 수 있습니다. 도구의 자동 판별도 같은 원리입니다. 절댓값이 10^11보다 작으면 초, 10^14보다 작으면 밀리초, 10^17보다 작으면 마이크로초, 그 이상은 나노초로 읽고 어떤 단위로 해석했는지 배지로 보여 줍니다. 그래서 1767225600123은 밀리초로 읽혀 2026-01-01T00:00:00.123Z가 됩니다.
단위를 섞으면 알아보기 쉬운 결과가 나옵니다.
| 상황 | 결과 |
|---|---|
초 1767225600을 밀리초로 읽음 |
1970-01-21T10:53:45.600Z |
밀리초 1767225600000을 초로 읽음 |
서기 57971년의 날짜 |
날짜가 1970년 1월로 나온다면 밀리초를 받는 곳에 초를 넘긴 경우가 대부분입니다. JavaScript에서 new Date(1767225600)은 1970년 1월이고, new Date(1767225600 * 1000)이 의도한 날짜입니다. 반대로 RFC 7519는 JWT의 exp·iat를 초로 정의하므로 Date.now()가 아니라 Math.floor(Date.now() / 1000)과 비교해야 합니다. 이 클레임은 JWT 디코더에서 날짜로 확인할 수 있습니다.
나노초 값은 JavaScript에서 한 가지를 더 조심해야 합니다. Number가 빠짐없이 정확히 표현하는 정수(안전한 정수)는 9,007,199,254,740,991까지인데 위 나노초 값은 이보다 큽니다. Number('1767225600000000001')은 1767225600000000000이 되어 끝자리를 잃습니다. 문자열이나 BigInt로 다루세요.
Unix 시간에는 시간대가 없습니다
POSIX는 기준 시점(Epoch)을 1970-01-01 00:00:00 UTC로 정의합니다. 그래서 타임스탬프 하나는 어디서나 같은 숫자이고, 시간대는 사람이 읽는 날짜로 바꿀 때만 개입합니다.
1767225600 = 2026-01-01T00:00:00Z UTC
= 2026-01-01 09:00:00 한국 시간(KST, UTC+9)
반대로 2026-01-01 09:00:00을 Asia/Seoul 시간대로 변환하면 1767225600으로 돌아옵니다.
흔한 함정은 오프셋이 없는 날짜 문자열입니다. ECMA-262는 UTC 오프셋이 없을 때 날짜만 있는 형식은 UTC로, 날짜와 시간이 있는 형식은 현지 시간으로 해석한다고 정합니다. 시간대가 Asia/Seoul인 컴퓨터에서 실행하면 이렇게 됩니다.
Date.parse('2026-01-01') → 1767225600000 UTC로 해석
Date.parse('2026-01-01T00:00:00') → 1767193200000 현지 시간으로 해석(9시간 이른 시점)
Date.parse('2026-01-01T00:00:00Z') → 1767225600000 UTC 명시
같은 코드가 UTC로 설정된 서버에서는 다른 숫자를 냅니다. 문자열에는 항상 Z나 +09:00 같은 오프셋을 붙이세요. RFC 3339 형식은 오프셋을 필수로 요구합니다.
정의에서 따라 나오는 성질이 하나 더 있습니다. POSIX는 하루를 정확히 86,400초로 계산한다고 정하므로 Unix 시간은 윤초를 세지 않으며, ECMAScript도 같은 방식을 씁니다.
2038년 문제
부호 있는 32비트 정수의 최댓값은 2,147,483,647입니다. 이 값을 초로 읽으면 다음과 같습니다.
2147483647 → 2038-01-19T03:14:07Z (한국 시간 12:14:07)
-2147483648 → 1901-12-13T20:45:52Z 1초 뒤 값이 넘쳐 돌아가는 곳
초를 부호 있는 32비트에 담는 소프트웨어는 그 순간 1901년으로 돌아가거나 오류를 낼 수 있습니다. 영향을 받는지는 값을 어디에 담느냐에 달려 있습니다.
| 저장 방식 | 표현할 수 있는 마지막 시각 |
|---|---|
| 부호 있는 32비트 초 | 2038-01-19T03:14:07Z |
| 부호 없는 32비트 초 | 2106-02-07T06:28:15Z |
| 부호 있는 64비트 초 | 1970년부터 약 2,920억 년 |
JavaScript Date(밀리초) |
275760-09-13T00:00:00Z (±8,640,000,000,000,000 ms) |
MySQL TIMESTAMP 컬럼(MySQL 8.4 매뉴얼) |
‘2038-01-19 03:14:07’ UTC |
문제는 2038년에야 시작되는 것이 아닙니다. 한계를 넘는 미래 날짜를 계산하는 코드는 지금도 걸립니다. 1767225600에 365일짜리 15년을 더하면 2240265600(2040-12-28T00:00:00Z)이 되어 부호 있는 32비트에 들어가지 않습니다. 만료일, 구독 기간, 인증서 유효 기간이 대표적입니다.
점검할 곳은 다음과 같습니다.
- 초를 담는 정수 컬럼: 부호 있는 32비트
INT는 한계가 같습니다. 64비트 정수 타입을 쓰세요. - MySQL
TIMESTAMP: 매뉴얼의 범위는 ‘1970-01-01 00:00:01’ UTC부터 ‘2038-01-19 03:14:07’ UTC까지이고,DATETIME은 ’1000-01-01 00:00:00’부터 ’9999-12-31 23:59:59’까지입니다.TIMESTAMP는 저장할 때 UTC로 변환하고DATETIME은 변환하지 않으므로, 타입을 바꾸면 시간대 동작도 달라집니다. - 32비트 빌드와 바이너리 형식: 32비트
time_t, 시간 필드가 32비트로 고정된 파일 형식과 프로토콜.
ZEKILO Dev로 변환하기
- Unix 타임스탬프 변환에 숫자를 붙여 넣습니다. 단위는 자동으로 판별하고, 초·밀리초·마이크로초·나노초로 직접 정할 수도 있습니다.
- 결과는 ISO 8601(UTC), RFC 2822, 한국어 날짜 표기, 상대 시간으로 나옵니다. 브라우저 시간대와 UTC·KST·JST가 함께 표시되고, 다른 시간대는 IANA 이름으로 고릅니다.
- 날짜를 타임스탬프로 바꿀 때는 ISO 8601,
YYYY-MM-DD HH:mm:ss(선택한 시간대 적용), RFC 2822 형식으로 입력합니다.
2147483647부터는 32비트 time_t 한계에 관한 안내가 함께 표시됩니다. 모든 계산은 브라우저 안에서 이루어집니다.
정리
- Unix 시간은 1970-01-01T00:00:00Z부터 센 초입니다. 시간대가 없고 윤초를 세지 않습니다.
- 지금 날짜라면 10자리는 초, 13자리는 밀리초, 16자리는 마이크로초, 19자리는 나노초입니다.
- 날짜가 1970년 1월로 나오면 초를 밀리초로 읽은 것입니다.
- 저장은 UTC 기준 64비트 정수나
Z가 붙은 문자열로 하고, 한국 시간 변환은 표시할 때만 합니다. - 부호 있는 32비트 초는 2038-01-19T03:14:07Z에 끝납니다.
INT와 MySQLTIMESTAMP컬럼을 확인하세요.