유닉스 타임스탬프 변환
입력한 내용은 저장, 기록, 공유되지 않습니다.
유닉스 타임스탬프를 읽기 쉬운 날짜로, 또는 그 반대로 변환합니다. 단위는 자동으로 감지되며 모든 결과에 시간대가 함께 표시됩니다.
사용 방법
- 타임스탬프 또는 2025-01-01T00:00:00 같은 날짜를 붙여넣습니다.
- 다른 시간대가 필요하면 선택합니다.
- 필요한 형식을 복사합니다.
초, 밀리초, 그리고 더 작은 단위
유닉스 시간은 초 단위로 정의됩니다. JavaScript의 Date는 밀리초 단위입니다. 여러 언어가 섞인 코드베이스의 날짜 버그는 거의 모두 이 경계에서 시작됩니다.
알고 나면 증상을 알아볼 수 있습니다. 밀리초를 기대하는 곳에 초 값을 넘기면 1970년 1월 1일 새벽이 됩니다. 화면에 날짜가 1970년으로 보인다면 사실상 항상 이것이 원인입니다. 밀리초 값을 초로 읽으면 대략 5만 5천 년쯤이 되는데, 보통은 발견되지만 아무 불평 없이 표시하는 코드도 있어서 항상 그런 것은 아닙니다.
여기서는 자릿수로 단위를 추정합니다. 경험칙이긴 하지만 예측 가능하고 설명할 수 있는 방식입니다. 현재 시점의 타임스탬프는 초 단위면 10자리, 밀리초면 13자리, 마이크로초면 16자리, 나노초면 19자리이며, 현실적으로 다루는 어떤 날짜에서도 범위가 겹치지 않습니다. 추정한 단위는 가정하지 않고 화면에 보여 주며, 직접 바꿀 수도 있습니다. 마이크로초 타임스탬프는 Postgres와 Chrome 트레이스에서, 나노초는 Go, Prometheus, InfluxDB에서 볼 수 있습니다. 나노초 값은 JavaScript 숫자가 정확히 담을 수 있는 범위를 넘으므로 BigInt로 변환합니다.
타임스탬프에 담기지 않는 것
유닉스 타임스탬프는 한 순간일 뿐 그 밖의 아무것도 아닙니다. 시간대도, 오프셋도, 달력도 담고 있지 않습니다. 도쿄와 리스본에서 같은 타임스탬프를 읽는 두 사람은 같은 순간을 보고 있으며, 각자 다른 현지 시각을 올바르게 적게 됩니다. 이는 장점입니다. 순간은 현지 시각이 결코 가질 수 없는 명확함을 지닙니다.
그 결과 타임스탬프만으로는 현지에서 며칠이었는지 알 수 없고, 날짜로 바꾸려면 시간대를 골라야 합니다. 여기의 모든 출력에 시간대를 붙이는 것도 그 때문이며, 바로 비교할 수 있도록 현지 시각 옆에 항상 UTC를 함께 보여 줍니다.
이는 본질적으로 현지 기준인 것을 저장하는 데 타임스탬프가 맞지 않는다는 뜻이기도 합니다. 매주 오전 9시 회의, 생일, 가게 영업시간은 시간대와 그 규칙에 묶인 현지 시각이며, 순간으로 저장하면 해당 시간대의 오프셋이 다음에 바뀔 때 어긋납니다. 현지 시각과 시간대 이름을 저장하고, 필요할 때 순간을 계산하세요.
윤초
유닉스 시간은 하루가 정확히 86,400초인 것처럼 정의됩니다. 지구는 그렇게 협조해 주지 않기 때문에, UTC를 지구 자전에 맞추려고 윤초를 넣어 왔고 1972년 이후 27번 삽입되었습니다.
정의에 윤초가 들어갈 자리가 없으므로, 유닉스 시간은 윤초를 한 값을 더하는 대신 같은 값을 반복하는 방식으로 처리합니다. 윤초 자체를 뜻하는 유닉스 타임스탬프는 없으며, 그래서 유닉스 시간과 TAI(국제원자시)의 차이는 고정되지 않고 수십 년에 걸쳐 커져 왔습니다. 두 유닉스 타임스탬프를 빼서 경과 시간을 계산하는 코드는 그 사이에 들어간 윤초 수만큼 조용히 틀립니다. 거의 문제가 되지 않지만 가끔은 크게 문제가 됩니다.
대형 사업자는 스미어링으로 이 불연속을 피합니다. Google과 AWS는 추가된 1초를 윤초 전후의 시간 구간에 나눠 퍼뜨려 시계가 같은 값을 반복하지 않게 하며, 그 대가로 하루 동안 아주 조금 느리게 갑니다. 그 구간에는 스미어링한 시계와 하지 않은 시계가 서로 다른 값을 보이므로, 사업자를 넘나들며 타임스탬프를 비교한다면 알아 둘 만합니다.
2038년과 그 밖의 경계
부호 있는 32비트 정수는 2038년 1월 19일 03:14:07 UTC에 한계에 도달하고, 다음 1초에 1901년 12월로 되돌아갑니다. 임베디드 시스템, 오래된 파일 형식, 한 번도 넓히지 않은 데이터베이스 열처럼 여전히 32비트 필드에 유닉스 시간을 저장하는 것은 그때 실패합니다. 이 도구가 그 시점 이후의 타임스탬프를 표시해 주는 이유입니다. 64비트로 저장하면 한계가 우주의 나이를 훨씬 넘어가며, 현재 시스템은 이 방식을 씁니다.
부호 없는 32비트로 저장하면 경계가 2106년으로 옮겨지지만, 1970년 이전은 나타낼 수 없습니다. 음수 타임스탬프도 여기서 동작합니다. -86400은 1969년 12월 31일이며, 그레고리력이 시작된 시점까지 거슬러 올라가는 날짜도 문제없이 변환됩니다. JavaScript의 Date에도 에포크 앞뒤로 각각 8.64e15밀리초, 약 27만 3천 년이라는 자체 한계가 있으며, 이를 넘는 값은 Invalid Date로 보여 주지 않고 범위를 벗어났다고 알려 줍니다.
텍스트 형식과, 무엇을 내보낼 것인가
ISO 8601은 날짜와 시간을 표기하는 국제 표준이고, RFC 3339는 인터넷 프로토콜이 쓰는 더 엄격한 하위 규격입니다. API에서 내보낼 때는 RFC 3339 형태, 즉 2026-03-14T09:30:00Z나 +09:00 같은 오프셋이 붙은 형태를 쓰십시오. 문자열 그대로 정렬해도 순서가 맞고, 해석이 모호하지 않으며, 어디서나 파싱되기 때문입니다.
끝의 Z는 UTC를 뜻하고, +09:00 같은 오프셋은 UTC에서 고정된 만큼 떨어져 있다는 뜻입니다. 둘 다 타임존이 아닙니다. 타임존은 Asia/Tokyo 같은 것으로, 특정 날짜에 오프셋이 얼마인지를 변경 이력까지 포함해 규정한 규칙 집합입니다. 오프셋은 그때 시계가 무엇을 가리켰는지 알려 주지만, 앞으로 언제 그 값을 가리킬지 알려 주는 것은 타임존 이름뿐입니다.
데이터에서 피해야 할 형식은 두 자리 연도를 쓰는 것, 슬래시로 구분하는 것, 그리고 메일 헤더에서 온 RFC 2822 날짜입니다. RFC 2822는 이미 낡은 타임존 약어를 그대로 끌고 다닙니다. 그중에서도 약어가 가장 나쁜데, CST는 북미의 Central Standard Time이기도 하고 China Standard Time이기도 하며 Cuba Standard Time이기도 합니다. IST는 인도와 아일랜드와 이스라엘에 모두 쓰입니다.
데이터베이스에 시간을 저장하기
PostgreSQL의 timestamptz는 대부분의 애플리케이션이 원하는 타입입니다. 이름과 달리 타임존을 저장하지는 않습니다. 쓸 때 입력을 UTC로 변환하고 읽을 때 세션의 타임존으로 표현하는데, 어떤 순간을 나타내는 값이라면 바로 이 동작이 필요합니다. 반면 그냥 timestamp는 넘겨받은 벽시계 문자열을 타임존 없이 그대로 저장하기 때문에, 출처가 다른 두 값을 비교하면 아무 경고 없이 무의미한 결과가 나옵니다.
MySQL의 DATETIME은 이 평범한 타입처럼 동작하고, TIMESTAMP는 UTC로 변환하지만 2038년에 끝나는 32비트 범위에 갇혀 있습니다. 미래 날짜를 저장하는 시스템에는 실제로 걸림돌이 되는 제약입니다. SQLite에는 날짜 타입 자체가 없어서, 여러분이 고른 표기를 그대로 저장합니다. 보통은 ISO 8601 텍스트이거나 정수입니다.
이 모든 차이를 견디는 원칙은 하나입니다. 순간은 UTC로 저장하고, 변환은 경계에서 하며, 사용자를 위해 표시하거나 일정을 잡아야 한다면 사용자의 타임존을 별도 컬럼으로 두는 것입니다. 값이 정말로 현지의 벽시계 시각인 경우, 예컨대 가게의 영업시간이나 매일 09:00에 열리는 정기 회의라면 현지 시각과 타임존 이름을 저장해 두었다가 필요한 시점에 순간을 계산하십시오. 오프셋은 법이 바뀌면 달라질 수 있기 때문입니다.
시계는 서로 어긋나고, 어떤 것은 거꾸로 간다
기계의 벽시계는 NTP가 조정하므로 앞으로 건너뛰기도 하고 뒤로 물러나기도 합니다. 그래서 벽시계 값 두 개를 빼서 경과 시간을 재면 가끔은 음수가 나오고, 자주 틀린 값이 나옵니다. 단조 시계는 이 때문에 존재합니다. 앞으로만 움직이며, performance.now와 CLOCK_MONOTONIC을 쓴 clock_gettime, 그리고 모든 벤치마크 라이브러리가 쓰는 것이 바로 이것입니다.
기계를 넘나들면 문제는 더 심각해집니다. 서버 두 대의 시계가 정확히 동기화되는 일은 없기 때문입니다. 분산 시스템에서 이벤트를 타임스탬프 순으로 늘어놓으면 인과 관계와 어긋나는 순서, 이를테면 답장이 원래 메시지보다 먼저 기록된 순서가 나옵니다. 논리 시계와 시퀀스 번호가 존재하는 이유가 여기에 있습니다.
보통의 애플리케이션에 필요한 조언은 훨씬 소박합니다. 타임스탬프를 고유 식별자로 쓰지 말 것, 타임스탬프가 같은 두 이벤트가 동시에 일어났다고 가정하지 말 것, 그리고 서로 다른 기계에서 기록된 값을 비교할 때는 최소 몇 초의 시계 오차를 예상할 것입니다. 토큰 검증에 오차 허용치가 있는 것도 정확히 이 때문입니다.
사람에게 시간을 보여 주기
저장된 순간은 읽는 사람의 타임존에서, 그 사람의 로캘 형식으로, 그 사람의 언어로 표시되어야 합니다. JavaScript에서는 Intl.DateTimeFormat API가 브라우저 설정을 바탕으로 이 셋을 한꺼번에 처리하고, 직접 손으로 하면 틀리기 쉬운 세부까지 챙겨 줍니다. 월 이름을 알맞은 언어로 쓰는 것, 일과 월의 순서를 맞추는 것, 그리고 12시간제와 24시간제를 취향이 아니라 관습에 따라 고르는 것 말입니다.
'세 시간 전', '이틀 뒤' 같은 상대 표기는 최근 사건에는 친근하지만 오래된 사건에는 오히려 나쁩니다. '431일 전'을 보고 무언가 판단할 수 있는 사람은 없기 때문입니다. 흔한 방식은 하루 이틀까지는 상대 표기를 쓰고 그 뒤로는 절대 날짜를 보여 주면서, 마우스를 올리거나 길게 누르면 전체 타임스탬프를 볼 수 있게 하는 것입니다.
눈에 띄는 차이를 만드는 두 가지가 있습니다. 하나는 날짜를 서버의 타임존이 아니라 사용자의 타임존으로 표시하는 것인데, 이는 서버에서 문자열로 만들어 보내는 대신 순간을 보내고 클라이언트에서 형식을 입힌다는 뜻입니다. 다른 하나는 조금이라도 모호한 값에는 타임존을 함께 적는 것입니다. 타임존 없는 회의 시각만큼 누군가를 한 시간 늦게 만드는 확실한 방법은 없습니다.
존재하지 않는 날짜와 그 밖의 경계 사례
서머타임이 있는 타임존에서 02:30에 무언가를 예약하면 해마다 사고가 납니다. 봄 전환 때 그 시각은 존재하지 않고, 가을 전환 때는 두 번 지나가기 때문입니다. 정확히 한 번만 실행되어야 하는 작업은 UTC에서 돌리거나, 전환이 일어나는 1시에서 3시 구간 바깥으로 옮기십시오.
달력 계산에도 고유한 함정이 있습니다. 1월 31일에 한 달을 더한 답은 하나로 정해지지 않고, 라이브러리에 따라 2월 28일이 되기도 하고 3월 3일이 되기도 합니다. 전환을 가로지를 때 24시간을 더하는 것은 하루를 더하는 것과 같지 않습니다. 윤년은 규칙 전체가 필요합니다. 4로 나누어떨어지는 해, 다만 100년 단위 해는 빼고, 그중 400으로 나누어떨어지는 해는 다시 넣는다는 규칙이고, 2월 29일은 4년마다 기념일 계산을 망가뜨립니다.
경계도 실재합니다. 부호 있는 32비트 시간은 2038년 1월 19일에 끝나고, 이는 임베디드 기기와 오래된 데이터베이스 컬럼에서 여전히 문제가 됩니다. JavaScript의 Date는 1970년을 기준으로 앞뒤 약 273,000년을 다루고, 그 바깥은 무효로 보고합니다. 주택담보대출, 연금, 기록 보존 시스템처럼 아주 먼 미래의 날짜가 들어오는 데이터라면 둘 다 시험해 볼 만합니다.
자주 묻는 질문
- 제 타임스탬프는 초인가요, 밀리초인가요?
- 자릿수로 알 수 있습니다. 현재 시점 기준으로 초는 10자리, 밀리초는 13자리, 마이크로초는 16자리, 나노초는 19자리입니다. 이 도구는 단위를 감지하고 어떤 것으로 처리했는지 알려줍니다. 둘을 혼동하는 것은 날짜 처리에서 가장 흔한 버그로, 초를 밀리초로 읽으면 1970년 1월이 되고 밀리초를 초로 읽으면 수만 년 뒤가 됩니다.
- 같은 타임스탬프인데 표시되는 시각이 다른 이유는?
- 유닉스 타임스탬프는 시간대가 없는 절대 시점입니다. 대응하는 벽시계 시각은 위치에 따라 달라지므로, 이 도구는 모든 출력에 시간대를 표시하고 UTC를 항상 함께 보여줍니다.
- 2038년에는 무슨 일이 일어나나요?
- 유닉스 시간을 부호 있는 32비트 정수로 저장하는 시스템은 2038-01-19T03:14:07Z에 오버플로가 발생해 1901년으로 되돌아갑니다. 이 도구는 그 시점을 넘는 타임스탬프에 경고를 표시합니다. 64비트 저장은 영향을 받지 않습니다.
- 유닉스 시간에 윤초가 포함되나요?
- 포함되지 않습니다. 유닉스 시간은 하루를 정확히 86,400초로 정의하므로 윤초는 계산되지 않고 건너뜁니다. 그래서 윤초 자체를 표현할 수 없고, TAI와의 변환도 단순한 오프셋이 아닙니다.
- 1970년 이전 날짜도 처리되나요?
- 네. 에포크 이전은 음수이며, -86400은 1969년 12월 31일입니다.
마지막 업데이트:
진행 단계, 예상 결과, 실제 결과를 적어 주세요. 비밀 정보와 개인 정보는 삭제해 주세요.
