Unix 타임스탬프 변환

Unix 타임스탬프를 원하는 시간대의 날짜·시각으로, 또 그 반대로 변환합니다. 초인지 밀리초인지 스스로 판별하고 ISO 8601 형식과 얼마나 전인지도 보여 줍니다.

현재 Unix 시각
판별 중…
입력

Unix 타임스탬프란 무엇인가

Unix 타임스탬프 — 에포크 시각, 또는 POSIX 시각이라고도 합니다 — 는 1970년 1월 1일 00:00:00 UTC(Unix 에포크라 불리는 순간)로부터 몇 초가 지났는지 세는 단일 숫자입니다. 시간대도 달력도 서식도 없는 하나의 정수이므로, 시간의 한 순간을 저장·정렬·비교하거나 네트워크로 보내야 할 때 컴퓨터가 손을 뻗는 형식입니다. 데이터베이스의 "created_at" 열, JWT의 "iat"·"exp" 클레임, 파일의 mtime, 그리고 앞으로 읽을 거의 모든 로그 파일의 타임스탬프 뒤에 있는 것이 이것입니다.

대가는 맨 숫자가 사람에게 아무 뜻도 없다는 점입니다. 1716197600은 흠잡을 데 없이 올바른 순간이지만, 그것이 지난 화요일인지 3년 전인지 한눈에 알 수 없습니다. 그 양방향 번역이야말로 이 도구가 하는 일입니다.

초냐 밀리초냐 — 사람을 무는 모호함

본래의 Unix 관례는 정수 초를 세며, 셸의 date +%s, PHP의 time(), 잘라 낸 Python의 time.time()에서 얻는 것이 이것입니다. JavaScript, Java 등 많은 것은 밀리초를 셉니다: Date.now()는 1000배 큰 수를 돌려줍니다. 둘 다 "타임스탬프"라 불리고, 뒤바꾸는 것은 가장 흔한 날짜 버그 중 하나입니다.

그 실패는 요란하지 않고 조용히 일어납니다. 밀리초 값을 초로 읽으면 날짜가 수만 년 앞으로 날아가고, 초 값을 밀리초로 읽으면 모든 것이 1970년 1월로 무너집니다. 어느 쪽도 오류를 내지 않습니다 — 진짜처럼 보이는 잘못된 날짜가 나올 뿐입니다.

이 도구는 수의 크기로 추측하고, 그 추측을 결과 바로 옆에 알립니다. 오늘의 에포크 초는 10자리, 오늘의 에포크 밀리초는 13자리라 추측은 거의 늘 맞습니다 — 하지만 곧 버그 리포트에 붙여넣을 값에는 "거의"로 충분하지 않으므로, 해석은 늘 보이고 늘 클릭 한 번으로 바꿀 수 있습니다.

시간대, UTC, 그리고 "로컬"이 미끄러운 이유

Unix 타임스탬프에는 시간대가 없습니다. 그것은 하나의 순간을 가리키고, 1716197600은 2024-05-20T09:33:20Z입니다. 그 같은 순간이 런던에서는 10:33, 예루살렘에서는 12:33, 서울에서는 18:33에 동시에 있습니다. 그날 런던과 예루살렘은 서머타임을 쓰고, 한국에는 서머타임이 아예 없기 때문입니다. 시간대는 값의 일부가 아니라 값을 들여다보는 렌즈입니다.

그래서 이 도구는 여러 렌즈를 한꺼번에 보여 줍니다. UTC는 모든 서버와 로그가 합의하는 중립 기준입니다. 로컬 시각은 사용자의 기기가 보고하는 것으로, 시간대 이름(예: Europe/Berlin)을 붙여 어느 렌즈가 낳은 값인지 늘 알 수 있게 표시합니다 — 판별은 브라우저 안에서 이루어지므로 베를린의 방문자는 베를린 시각을, 서울의 방문자는 서울 시각을 봅니다. 세 번째 줄은 전체 IANA 시간대 데이터베이스에서 고른 임의의 시간대로, 다른 곳에 있는 서버의 로그를 읽을 때 원하는 줄입니다.

  • UTC — 모든 시스템이 합의하는 기준이자 저장하고 로그하기에 옳은 것입니다.
  • 로컬 — 사용자의 기기가 보는 것과 같은 순간이며, 판별한 시간대 레이블이 붙습니다.
  • 고른 시간대 — 다른 곳의 머신에서 온 로그와 트레이스를 읽기 위한 것입니다.
  • ISO 8601 — 교환용 텍스트 형식으로, 예를 들어 2024-05-20T09:33:20.000Z 입니다.
  • 상대 — "3시간 전"처럼 얼마나 최근인지 빠르게 가늠하기 위한 것입니다.

오프셋(+03:00, -04:00)은 각 시각 옆에 표시됩니다. 고정이 아니기 때문입니다: 대부분의 시간대는 서머타임으로 한 시간 옮겨지므로 같은 시간대라도 시기에 따라 다른 오프셋을 냅니다. 전환 근처의 날짜야말로 손 계산이 틀리는 자리입니다.

실무에서 에포크 시각 다루기

어느 언어든 셸이든 에포크 값을 만들고 읽는 저마다의 방법이 있습니다. 기억해 둘 만한 것은 다음과 같습니다:

date +%s                         # 셸: 현재 시각을 초로
date -d @1716197600              # 셸(GNU): 초에서 날짜로
Date.now()                       # JavaScript: 현재 시각을 밀리초로
new Date(1716197600 * 1000)      # JavaScript: 초에서 Date로
time.time()                      # Python: 초, 부동소수점으로
datetime.fromtimestamp(1716197600, tz=timezone.utc)
SELECT EXTRACT(EPOCH FROM now()) -- PostgreSQL: 초

대부분의 날짜 고통을 피하는 경험칙: 순간은 UTC로 저장·전송하고 — 에포크 정수든 ISO 8601 문자열이든 — 로컬 시간대로의 변환은 실제로 누군가에게 표시하는 마지막 순간까지 미루세요. 일찍 서식화하는 것이 시간대 버그를 표시 계층에 머물게 하지 않고 데이터에 구워 넣는 원인입니다.

한 가지 더 알아 둘 것: 부호 있는 32비트 초 카운터는 2038년 1월 19일에 소진됩니다. "2038년 문제"입니다. 현대 시스템은 64비트 값을 써서 괜찮지만, 오래된 임베디드 기기와 레거시 데이터베이스 열이야말로 그것을 아직 만날 수 있는 자리입니다.

에포크란 무엇이며, 왜 모든 타임스탬프가 1970년부터 시작하지는 않는가

이 뜻에서 에포크란 그저 선택된 영점 — 카운터가 세기 시작한다고 정의된 순간 — 입니다. Unix 에포크는 1970년 1월 1일 00:00:00 UTC이며, 무언가에서 유도된 것이 아니라 편리한 둥근 날짜였기 때문에 선택되었습니다: 형식 자체가 하필 그 날짜를 요구하지는 않습니다. 초기 Unix는 훨씬 가까운 영점에서 60분의 1초를 셌고, 제3판 매뉴얼은 그것이 오래갈 수 없는 이유를 분명히 밝혔습니다 — 32비트의 60분의 1초는 2.26년마다 위기를 보장한다, 즉 약 828일마다입니다. 같은 32비트로 정수 초를 세면 136년, 부호가 있으면 68년까지 갑니다. 그것이 위에 나온 2038년 날짜입니다.

이 내력이 바로 긴 숫자를 믿기 전에 확인해야 할 이유입니다. 다른 시스템은 다른 영점에서 다른 해상도로 세며, 그중 하나를 Unix 초로 읽으면 몇 시간이 아니라 몇 세기가 어긋납니다. 가장 자주 만나게 될 것은 다음과 같습니다:

  • Windows FILETIME — 1601년 1월 1일 UTC부터의 100나노초 틱입니다. 1000만으로 나누고 11644473600을 빼면 Unix 초가 됩니다. 이 가이드 전체에서 쓰는 순간 1716197600은 그 방식에서 133606712000000000입니다.
  • .NET DateTime.Ticks — 같은 100나노초 틱이지만 서기 1년 초부터 셉니다. Unix 에포크 자체가 621355968000000000 틱에 놓이며, 나누기 전에 빼야 할 수가 이것입니다.
  • Apple의 기준 날짜 — 2001년 1월 1일 UTC부터의 초로, Cocoa와 Core Data가 씁니다. 978307200을 더하면 Unix 초가 됩니다: 1716197600은 거기서 737890400입니다.
  • Excel의 일련번호 — 초가 아니라 날이며, 1899년 12월 30일부터 셉니다. Excel이 1900년을 윤년으로 다루기 때문인데, 실제로는 아니었습니다.
  • GPS 시각 — 1980년 1월 6일부터의 초로, 윤초를 한 번도 건너뛰지 않고 세므로 GPS 시각과 UTC는 더 이상 일치하지 않습니다.

마지막 항목은 Unix 값 자체에 대해서도 참인 것을 가리킵니다: 그것은 스톱워치 눈금이 아닙니다. POSIX는 그것을 에포크 이후 지난 초 수에 근사하는 값으로 정의하고, 하루하루가 정확히 86400초로 셈해질 것을 요구합니다 — 그래서 UTC에 삽입된 윤초는 전혀 세어지지 않습니다. 따라서 두 Unix 타임스탬프의 차이는 실제로 지난 초가 아니라 그 사이의 달력 초의 수이며, 값은 UTC 달력 눈금의 부호화로 이해하는 것이 가장 적절합니다. 어떤 타임스탬프도 윤초를 가리키지 못하는 것 역시 그 때문입니다. 이 부호화에는 윤초의 자리가 없습니다.

날짜에서 에포크 값으로: 셸, JavaScript, Python

위의 블록은 에포크 값을 읽을 수 있는 것으로 바꿉니다. 반대 방향 — 이미 손에 있는 날짜를, 텍스트로든 숫자의 묶음으로든, 에포크 값으로 바꾸는 것 — 이야말로 시간대 버그가 사는 자리입니다. 텍스트가 시간대를 밝히지 않으면 여기 있는 어느 것이든 조용히 시간대를 가정하기 때문입니다. 같은 일을 세 가지 방법으로, 이 가이드의 순간 2024-05-20T09:33:20Z에 대해 보입니다. 셸 줄은 GNU의 date이며, BSD와 macOS의 date는 다른 옵션을 받습니다.

date -u -d '2024-05-20 09:33:20' +%s     # 1716197600 — -u가 텍스트를 UTC로 읽는다
date -d '2024-05-20 09:33:20 UTC' +%s    # 같은 것, 시간대를 텍스트 안에서 밝힌다
date -d '2024-05-20 09:33:20' +%s        # 둘 다 아님: 당신 머신의 시간대, 다른 순간
Date.parse('2024-05-20T09:33:20Z') / 1000    // 1716197600 — Z가 있어서 UTC가 된다
new Date('2024-05-20T09:33:20Z').getTime()   // 같은 순간을 밀리초로
Date.parse('2024-05-20 09:33:20')            // Z 없음: 당신 머신의 시간대, 다른 순간
from datetime import datetime, timezone
int(datetime(2024, 5, 20, 9, 33, 20, tzinfo=timezone.utc).timestamp())   # 1716197600 — 시간대를 가진 datetime
int(datetime.fromisoformat('2024-05-20T09:33:20+00:00').timestamp())     # 같은 것, 문자열에서 파싱
int(datetime(2024, 5, 20, 9, 33, 20).timestamp())                        # 나이브: 당신 머신의 시간대

셋 모두 형태는 같습니다: 시간대를 밝히거나, 아니면 머신에 설정된 것을 물려받거나입니다. 내 노트북에서는 맞고 동료의 것에서는 몇 시간 어긋나는 숫자는 거의 늘 이것이며, 그래서 위의 도구는 둘 중 하나를 골라 주지 않고 UTC와 당신의 로컬 눈금을 나란히 보여 줍니다. 숫자가 이미 손에 있고 눈으로 확인하고 싶다면, 이 페이지 위쪽 입력란에 붙여 넣으세요.

자주 묻는 질문

제 숫자가 초인지 밀리초인지 어떻게 아나요?
그 크기로 압니다: 대략 1e11 미만의 값은 초로, 그보다 크면 밀리초로 읽습니다. 오늘은 이것이 10자리 초와 13자리 밀리초를 깔끔하게 나눕니다. 해석은 결과 옆에 표시되고 클릭 한 번으로 바꿀 수 있어 추측이 감춰지지 않습니다.
"로컬"은 어느 시간대인가요?
사용자의 기기가 보고하는 시간대로, 브라우저 안에서 판별되고 헷갈리지 않도록 이름으로 표시됩니다. 사이트의 시간대가 아니라 당신의 시간대입니다 — 베를린의 방문자는 베를린 시각을, 서울의 방문자는 서울 시각을 봅니다.
날짜를 타임스탬프로 되돌릴 수 있나요?
네. 같은 입력란이 양방향을 받습니다: 숫자를 입력하면 날짜가, 2024-05-20이나 2024-05-20T09:33:20Z 같은 날짜를 입력하면 에포크 값이 나옵니다. 전환할 모드가 없습니다.
1970년 이전 날짜도 다루나요?
네. 에포크 이전의 타임스탬프는 그저 음수이며 — -86400은 1969년 12월 31일입니다 — 음수 값도 받아들여 평소대로 변환됩니다.
제 타임스탬프가 예상과 다른 날짜를 보이는 이유는 무엇인가요?
거의 늘 두 이유 중 하나입니다: 초/밀리초 해석이 짐작과 다르거나(레이블을 확인하고 바꾸세요), UTC 값을 로컬 시각 기대와 비교하고 있는 것입니다. 이 도구는 두 줄을 나란히 보여 주어 어느 쪽인지 알 수 있게 합니다.
2038년 문제란 무엇인가요?
에포크 초를 부호 있는 32비트 정수로 저장하는 시스템은 2038년 1월 19일에 넘쳐 음수로 되감겨 1901년 날짜를 냅니다. 64비트 값을 쓰는 것 — 현대의 거의 모든 것 — 은 영향받지 않지만, 레거시 임베디드 시스템과 오래된 데이터베이스 열은 지금도 위험할 수 있습니다.
제 타임스탬프가 18자리인 이유는 무엇인가요?
아마 Unix 타임스탬프가 아니기 때문입니다. 오늘의 Unix 초는 10자리, Unix 밀리초는 13자리입니다. 18자리는 100나노초 틱의 모습이고, Windows FILETIME은 1601년부터, .NET은 서기 1년 초부터 그것을 셉니다. 16자리는 또 하나의 흔한 경우로, 같은 순간을 마이크로초로 나타낸 것입니다. 위의 에포크 절에 각각 빼야 할 수가 있습니다.
Python에서 날짜를 Unix 타임스탬프로 바꾸려면 어떻게 하나요?
시간대를 가진 datetime — tzinfo를 지닌 것 — 을 만들고 그 timestamp 메서드를 호출한 뒤 결과를 정수로 잘라 냅니다. 위의 코드 블록에 그 줄이 있고, 같은 절이 셸과 JavaScript에서도 같은 일을 보입니다. 어려움은 모두 tzinfo에 있습니다: 그것을 빼면 Python은 당신의 숫자를 머신의 로컬 시각으로 읽고 아무 말 없이 다른 값을 돌려줍니다.
입력한 타임스탬프가 어딘가로 전송되나요?
아니요. 파싱, 변환, 서식화가 모두 플랫폼 자체의 날짜·시간대 지원을 써서 브라우저 안에서 돕니다. 입력한 내용이 기기 밖으로 나가지 않습니다.

관련 도구