Unix 타임스탬프 변환기

현재 Unix 타임스탬프

0

Unix 시간이 실제로 세는 것 (초, 밀리초, 나노초)

Unix 시간은 정수 하나입니다. 기점(epoch)인 1970-01-01T00:00:00Z 이후의 초 수죠. 날짜 자체는 임의적입니다 — 벨 연구소의 초기 Unix는 적당한 해상도로 32비트 값에 들어가는 영점이 필요했고, 1970년의 시작은 가까운 시점의 편리한 어림수였습니다. 음수 값도 완전히 합법이며 1970년 이전의 순간을 뜻합니다. -86400은 1969년의 마지막 날입니다. 이 카운트는 UTC에 고정되어 있으므로, 서울과 상파울루의 두 머신이 같은 순간에 타임스탬프를 만들면 같은 숫자가 나옵니다. 타임스탬프는 순간을 식별하지, 벽시계 눈금을 식별하지 않습니다. 여기서 고전적인 버그 계열은 단위 혼동입니다. 생태계마다 해상도가 다르기 때문이죠. POSIX time(), 대부분의 데이터베이스, JWT 클레임은 초를 쓰고, JavaScript의 Date.now()와 Java의 System.currentTimeMillis()는 밀리초를 반환하며, 많은 API와 DB는 마이크로초를, Go의 UnixNano와 관측성(observability) 세계 대부분은 나노초를 씁니다. 이걸 섞으면 날짜가 1970년에 떨어지거나(밀리초를 초로 취급해 거의 전부를 나눠 없앰) 서기 56,000년 너머로 갑니다(초를 밀리초로 취급). 현재 시대 값에는 자릿수가 가장 빠른 진단법입니다. 10자리는 초(1751414400은 초 단위로 2025년 7월), 13자리는 밀리초, 16자리는 마이크로초, 19자리는 나노초입니다. 관련 함정으로 부동소수점 정밀도가 있습니다. 타임스탬프를 JSON의 부동소수점으로 넘기면 초 단위는 괜찮지만 나노초 값은 손상됩니다. 64비트 double의 정수 정밀도는 53비트뿐이니까요 — 고해상도 API들이 타임스탬프를 문자열로 넘기는 이유 중 하나입니다.
Same instant, four resolutions:
1751414400            seconds       (10 digits)
1751414400000         milliseconds  (13 digits)  Date.now()
1751414400000000      microseconds  (16 digits)
1751414400000000000   nanoseconds   (19 digits)  Go UnixNano

// The two classic unit bugs (JavaScript):
new Date(1751414400)        // 1970-01-21 — seconds fed as ms
new Date(1751414400 * 1000) // 2025-07-02 — correct

윤초 — 모든 타임스탬프 안에 숨은 거짓말

Unix 시간은 1970년 이후의 초를 센다고 주장하지만, 문자 그대로 참은 아닙니다. 지구 자전은 불규칙해서 UTC는 천문 시간과의 정렬을 위해 이따금 윤초를 삽입합니다(1972년 이후 27번). Unix 시간은 이것이 존재하지 않는 척합니다. 정의상 모든 날은 정확히 86,400초입니다. 윤초가 삽입되는 동안 UTC 시계는 23:59:60을 가리키지만 Unix 카운터는 그 순간을 표현할 수 없습니다 — 구현들은 1초를 반복하거나 멈춥니다. 즉 서로 다른 두 실제 순간이 하나의 타임스탬프를 공유할 수 있고, 윤초를 가로질러 잰 간격은 1초 어긋납니다. 탁상공론이 아닙니다. 2012년 6월의 윤초는 커널 타이머 코드의 livelock을 통해 업계 전반의 Linux 시스템을 다운시켰고, 항공사 예약 시스템 등이 함께 멈췄습니다. 2016년 말의 윤초는 Cloudflare에서 프로덕션 장애를 일으켰습니다. 타임스탬프를 빼서 음수 지속시간이 나왔는데 코드가 이를 처리하지 못했던 것이죠. 교훈은 일반화됩니다. 나중에 찍은 벽시계 타임스탬프가 더 크다고 절대 가정하지 마세요. 지속시간 측정에는 단조 시계(monotonic clock)를 쓰세요 — JS의 performance.now(), POSIX의 CLOCK_MONOTONIC, Python의 time.monotonic(). 윤초, NTP 점프, 수동 시계 변경에 면역입니다. 대형 사업자들은 윤초 스미어링(smearing)으로 문제를 우회합니다 — Google과 AWS는 24시간 창에 걸쳐 각 초를 살짝 늘려서 윤초를 미시적인 감속 속으로 사라지게 합니다. 그리고 종착점은 폐지입니다. 2022년 국제도량형총회는 2035년까지 윤초 삽입을 중단하기로 결의했습니다. 그때까지 타임스탬프는 순간의 훌륭한 식별자이자, 지속시간에 대해서는 미묘하게 못 믿을 자입니다.

2038년 문제는 이미 도착해 있다

부호 있는 32비트 정수의 최댓값은 2,147,483,647입니다. Unix 타임스탬프로는 2038-01-19T03:14:07Z이고, 1초 뒤 값이 음수로 감기면서 취약한 시스템은 갑자기 지금이 1901-12-13이라고 믿게 됩니다. 12년 남았으니 나중 문제 같지만 — 미래 날짜를 계산하는 코드는 수십 년 일찍 이 경계를 넘습니다. 20년 만기 주택담보대출 일정은 2018년에 이미 넘었고, 15년짜리 인증서 만료와 긴 TTL은 오늘 넘고 있습니다. 시스템들은 그 날짜가 오기 몇 년 전부터 이미 진짜 2038 버그를 만들어 왔습니다. 해결책인 64비트 time_t는 새 플랫폼에서 수년째 표준이며, 64비트 초 카운터는 약 2,920억 년 동안 안전합니다. 잔여물은 가장자리에 삽니다. 2038년에도 여전히 돌아가고 있을 32비트 ABI의 임베디드·IoT 기기들, 32비트 시간 필드를 가진 오래된 파일 포맷과 네트워크 프로토콜, 그리고 데이터베이스. MySQL의 TIMESTAMP 컬럼 타입이 유명한 생존자입니다. 32비트 epoch 초를 저장해서 역사적으로 2038-01-19 이후를 표현할 수 없습니다(그런 제한이 없는 DATETIME을 쓰세요). Linux 커널은 5.6 버전(2020)에서 32비트 아키텍처용 64비트 시간 시스템 콜을 추가했고, glibc는 _TIME_BITS=64 빌드 옵션으로 뒤따랐습니다. ext4는 타임스탬프를 2446년까지, XFS는 2486년까지 확장했습니다. 인접한 오버플로 두 개도 알아둘 만합니다. 같은 32비트를 부호 없는 정수로 재해석해 2038을 "고친" 시스템은 2106-02-07까지 시간을 벌었을 뿐입니다 — 유예이지 해결이 아닙니다. 밀리초 세계에는 자체 절벽이 있습니다. 임베디드 가동시간 시계에 흔한 32비트 밀리초 카운터는 49.7일마다 감깁니다. Windows 95로 유명해졌고, 나중에는 보잉 787의 발전기 제어 장치에 재부팅을 요구하는 감항 지침까지 낳은 버그 패턴입니다.
2^31 - 1 = 2147483647  →  2038-01-19T03:14:07Z  (signed 32-bit)
+1 second              →  -2147483648  =  1901-12-13T20:45:52Z

2^32 - 1 = 4294967295  →  2106-02-07T06:28:15Z  (unsigned 32-bit)
2^63 - 1 (64-bit)      →  ~292 billion years    (safe)

-- MySQL: TIMESTAMP is 32-bit, DATETIME is not
CREATE TABLE t (
  ts TIMESTAMP,   -- max 2038-01-19 03:14:07 UTC
  dt DATETIME     -- max 9999-12-31 23:59:59
);

타임존과 서머타임 — 타임스탬프 버그가 태어나는 곳

Unix 타임스탬프는 절대적인 순간이고, 타임존은 표시 규칙입니다. 이 두 역할을 분리해 두는 것이 날짜 버그에 대한 단일 최강의 방어입니다. 저장과 전송은 UTC 타임스탬프로 하고, 로컬 시간 변환은 표시 단계에서만 하세요. "타임스탬프"가 로컬 시간 해석을 조용히 거친 순간 — Python의 naive datetime, 서버 로컬 시간으로 채워진 DATETIME 컬럼, datetime-local 폼 입력 — 같은 값이 머신마다 다른 순간을 뜻하게 됩니다. 서머타임(DST)은 하나의 타임존 안에서조차 로컬 시간이 위험한 이유입니다. 시계가 봄에 앞당겨지면 로컬 시간 한 시간이 그냥 존재하지 않게 되고(미국 동부에서 3월 전환일의 새벽 2:30은 무효), 가을에 되돌려지면 한 시간이 두 번 발생해서 어느 쪽 통과인지 모르면 "새벽 1:30"이 모호해집니다. 스케줄링 코드는 두 경우 모두에 부딪힙니다. 매일 02:30 작업은 1년에 두 번 안 울리거나 두 번 울리고, 순진한 "24시간 더하기" 산술은 전환일에 어긋납니다. 그 날들은 23시간이거나 25시간이니까요. 두 가지 규칙이 더 있습니다. 첫째, 오프셋은 타임존이 아닙니다. +09:00은 한 순간을 어떻게 그릴지 알려줄 뿐이고, Asia/Seoul이나 America/Sao_Paulo 같은 IANA 존 이름만이 DST와 정치적 역사 전체를 담습니다 — 그 역사는 바뀝니다. tzdata 패키지가 1년에 몇 번씩 갱신되는 이유죠(브라질은 2019년 DST 폐지, 이집트는 2023년 재도입). 둘째, 언어의 파싱 기벽을 아세요. JavaScript에서 new Date('2026-07-02')는 UTC 자정으로 파싱되지만 new Date('2026-07-02T00:00')는 로컬 시간으로 파싱됩니다 — 그리니치 동쪽이든 서쪽이든 모든 사용자에게 고전적인 하루 어긋남(off-by-one-day) 버그를 만드는 한 글자 차이입니다.
// JavaScript parsing quirk (machine in UTC+9):
new Date('2026-07-02')        // date-only → parsed as UTC
  .toString()                 // Thu Jul 02 2026 09:00 GMT+0900
new Date('2026-07-02T00:00')  // date-time → parsed as LOCAL
  .toString()                 // Thu Jul 02 2026 00:00 GMT+0900

// Same string minus one 'T00:00' = 9 hours apart.

// DST gap: 2:30 AM does not exist on spring-forward day
// America/New_York, 2026-03-08: 01:59:59 → 03:00:00

ISO 8601과 RFC 3339 — 사람과 정렬기를 위한 타임스탬프

타임스탬프가 읽혀야 할 때의 답은 ISO 8601 계열입니다: 2026-07-02T09:30:00+09:00. 실무에서 API들이 구현하는 것은 RFC 3339로, 인터넷 사용을 위해 의도적으로 작게 만든 ISO 8601의 프로파일입니다. ISO 표준 전체에는 API에서 절대 볼 일 없는 것들이 허용됩니다 — 2026-W27-4 같은 주(week) 날짜, 2026-183 같은 서수 날짜, 절단형, 대시 없는 기본형. RFC 3339는 그 전부를 잘라냅니다. 완전한 연-월-일, T 구분자, 필수 UTC 오프셋, 그리고 +00:00의 축약형인 Z. API를 설계한다면 "Z를 붙인 RFC 3339"가 상호운용성이 가장 높은 선택입니다. JavaScript의 toISOString()과 Python의 datetime.isoformat() 모두 적합한 출력을 냅니다. 이 포맷의 킬러 기능은 사전순 정렬이 시간순 정렬과 같다는 것입니다 — 단, 모든 값이 같은 오프셋과 같은 필드 폭을 쓸 때만요. 오프셋이 섞인 문자열을 알파벳순으로 정렬하면 틀립니다(+09:00의 아침이 자기보다 늦은 Z 접미사 오후 뒤로 정렬됩니다). 모든 것을 Z로 정규화해야 할 또 하나의 이유죠. 이 정렬 가능성이 로그 파일, S3 객체 키, 백업 이름이 관례적으로 UTC ISO 타임스탬프로 시작하는 이유입니다. 무는 디테일들: RFC 3339는 -00:00을 허용합니다(역사적으로 "오프셋 미상"을 뜻했으며, UTC임을 단언하는 +00:00과 다릅니다). 소수 초는 선택적이고 가변 폭이라 파서는 .5, .500, .500000을 모두 받아야 합니다. 그리고 날짜만 있는 2026-07-02는 타임스탬프가 아예 아닙니다 — 앞 섹션에서 봤듯 파서마다 다른 자정을 붙입니다. 시스템 간에 순간을 교환할 때는 정수 epoch 값이나 명시적 오프셋이 있는 완전한 RFC 3339 문자열을 보내세요. 그보다 덜 구체적인 것은 두 시스템이 서로 어긋나자는 초대장입니다.
최종 수정:

도구 소개

Unix 타임스탬프 변환기는 1970-01-01 UTC 이후 정수 초(Unix epoch)와 사람이 읽기 좋은 날짜/시각 형식 사이를 변환합니다. JWT의 iat/exp 클레임, 데이터베이스 created_at 컬럼, 로그 라인, 파일 mtime 등 코드 곳곳에 타임스탬프가 있어 매번 머리로 계산하는 수고를 덜어줍니다.

사용 방법

  1. 상단의 실시간 "현재" 타임스탬프를 확인합니다.
  2. Unix 타임스탬프를 입력하면 아래 사람 친화적 날짜가 즉시 갱신됩니다.
  3. 반대로 날짜 선택기에서 날짜를 고르면 Unix 값이 즉시 갱신됩니다.
  4. Now 버튼으로 현재 타임스탬프를 가져옵니다.
  5. 필요한 표현을 복사해 코드, 로그 쿼리, 테스트 픽스처에 사용합니다.

주요 사용 사례

  • Stripe charge의 created 필드를 사람 친화적 날짜로 변환
  • JWT의 iat/exp 클레임을 디코딩해 만료 문제 디버깅
  • 로그 파일의 타임스탬프를 로컬 타임존으로 변환
  • "N초 후 만료" 캐시 항목용 Unix 타임스탬프 생성
  • 알려진 날짜로 데이터베이스 테스트 픽스처 채우기
  • 서버 시계가 예상 epoch와 일치하는지 점검

자주 묻는 질문

Q. 타임스탬프는 초인가요, 밀리초인가요?

A. 둘 다 흔합니다. Unix 도구와 대부분 DB는 초, JavaScript Date.now()는 밀리초입니다. 13자리 숫자는 거의 항상 밀리초, 10자리는 초입니다.

Q. 타임존은 어떻게 처리하나요?

A. Unix 타임스탬프는 정의상 항상 UTC입니다. 로컬 시간은 표시 단계에서만 적용합니다 — 저장은 UTC, 표시는 로컬.

Q. 2038년 문제(Year 2038 problem)는 어떻게 되나요?

A. 부호 있는 32비트 타임스탬프는 2038-01-19에 오버플로합니다. 최신 시스템은 64비트 타임스탬프를 사용해 수십억 년 안전합니다.

Q. JWT의 exp 값이 이상하게 보입니다.

A. epoch 이후 초입니다. iat는 발급 시각, exp는 만료 시각입니다. 둘 다 UTC 기준입니다.