Engineering · 2026년 9월 22일

Unix 타임스탬프 변환 해설 — 에포크 초, 밀리초, ISO 8601

Unix 타임스탬프는 정수 하나로 보이지만 단위를 헷갈리는 순간 오늘 날짜가 1970년이 됩니다. 에포크 초와 밀리초, ISO 8601, 시간대 그리고 자주 나오는 실수를 위한 변환 치트시트.

시각을 저장하는 시스템은 언젠가 같은 질문을 합니다. 이 숫자는 초인가, 밀리초인가. Unix 타임스탬프는 단순해 보입니다 — 1970년 1월 1일 이후 경과한 초를 세는 정수 — 그래서 그만큼 엉터리로 다뤄집니다. 1000배가 잘못 들어간 값, 1970년으로 떨어진 날짜, 반대편 대륙의 사용자에게만 나타나는 시간대 오차. 모두 같은 몇 가지 오해에서 나옵니다.

이 글은 Unix 타임스탬프가 실제로 무엇을 재는지, 초와 밀리초의 차이, ISO 8601이 어디에 끼어드는지, 그리고 추측 없이 형식을 변환하는 방법을 다룹니다.

Unix 타임스탬프란 무엇인가

Unix 타임스탬프(에포크 시간이라고도 합니다)는 1970년 1월 1일 00:00:00 UTC, 즉 Unix 에포크 이후 경과한 초 수입니다. 시간대 정보가 없는 단순 정수라서, 같은 숫자가 지구상 어디서든 같은 순간을 가리킵니다.

시간대를 담고 있지 않기에 에포크는 기계들 사이의 자연스러운 교환 형식입니다. API, 로그, 데이터베이스, 메시지 큐는 타임스탬프를 저장하고 사람은 날짜를 읽습니다. 이 둘의 변환이 오류가 사는 곳입니다.

타임스탬프에 없는 것에 주목하세요. 시간대, 로케일, 달력입니다. 1758537600은 그저 하나의 순간일 뿐입니다. 방콕에서 21시로 보이든 마드리드에서 14시로 보이든, 그것은 어떻게 보여줄지에만 달려 있습니다.

초 대 밀리초 — 가장 잦은 실수

두 관례가 공존하고 어디에나 섞여 있습니다.

  • 초 — 현재 10자리, 예: 1758537600. 전통적인 Unix, Go의 Unix(), Java의 getEpochSecond(), Redis, 대부분의 데이터베이스가 이 단위를 씁니다.
  • 밀리초 — 13자리, 예: 1758537600000. JavaScript의 Date.now(), Java의 getTime(), 대부분의 브라우저 API가 이 단위를 씁니다.

단위를 헷갈리면 날짜가 1970년 또는 2286년으로 점프합니다. 단위를 판별하는 보편 규칙은 없으므로 확실한 길은 소스의 계약을 아는 것입니다. 추측해야 한다면 10자리는 대개 초, 13자리는 대개 밀리초입니다. 하지만 추측 자체가 오류이지 해법이 아닙니다.

규칙: 단위를 값과 함께 저장하세요. 컬럼명(created_at_ms), 명시적인 API 필드, unit 속성 무엇이든 좋습니다. time이라는 맨 정수보다는 낫습니다.

ISO 8601 — 사람이 읽는 쪽

ISO 8601 문자열은 에포크 수의 짝입니다. 읽기 쉽고 UTC로 정렬 가능하며, 오프셋을 달고 있으면 모호하지도 않습니다.

2026-09-22T14:00:00Z       # 끝의 Z가 표시하는 UTC
2026-09-22T14:00:00+02:00  # 같은 순간, 베를린 현지 시각
2026-09-22T14:00:00.123Z   # 밀리초가 보존됨

끝의 Z와 숫자 오프셋은 장식이 아닙니다. 둘 중 하나가 없으면 문자열에는 시간대가 없고 코드는 추측을 강요받습니다. 그리고 그곳에서 “같은 시각”이 조용히 세 시간짜리 오차가 됩니다.

시간대와 UTC

저장과 비교는 UTC로 하고, 사람이 볼 때만 최외곽에서 현지 시간대로 변환하세요.

const now = Date.now();                 // 에포크 이후 밀리초
const seconds = Math.floor(now / 1000); // 에포크 이후 초
new Date(seconds * 1000).toISOString(); // "2026-09-22T14:00:00.000Z"

두 규칙이 시간대 문제의 대부분을 막습니다.

  • 순수한 로컬 시각끼리 비교하지 마세요. 둘 다 순간으로 직렬화한 뒤 비교합니다.
  • 사용자의 시간대가 중요한 곳에서는 값을 값과 함께 보관합니다 (약속, 일정, 청구 기간). UTC 순간에 Asia/Seoul 같은 IANA 존 ID를 더하는 편이 고정 오프셋보다 낫습니다. 고정 오프셋은 서머타임에서 어긋납니다.

변환 치트시트

변환 JavaScript Python
밀리초 → 날짜 문자열 new Date(1758537600000).toISOString() datetime.fromtimestamp(1758537600, tz=timezone.utc)
날짜 문자열 → 초 Math.floor(Date.parse(s) / 1000) int(dt.replace(tzinfo=timezone.utc).timestamp())
초 → 밀리초 seconds * 1000 seconds * 1000
현재 시각 Date.now() (밀리초) int(time.time()) (초)

Date.now()는 밀리초를, time.time()은 초를 돌려준다는 점에 주목하세요. 단위가 보편 표준이 아니라 언어별 관례라는 사실의 압축된 예시입니다.

피해야 하는 실수

  • “혹시 몰라서” 1000을 곱한다. 판단은 계약에 따르고, 숫자 자릿수에 따르지 않습니다.
  • 오프셋 없는 로컬 시각을 저장한다. 서머타임이 언젠가 산술을 깨뜨립니다.
  • 문자열을 쪼개 파싱한다. 표준 라이브러리 파서를 쓰세요. ISO 8601은 주 번호, 약식, 소수 부분을 갖고 있어 직접 짠 코드가 여섯 곳에서 실패합니다.
  • SQL 안에 날짜 형식을 둔다. 값은 숫자 또는 UTC로 데이터베이스에 두고, 포맷은 로케일과 시간대를 제어할 수 있는 애플리케이션에서 합니다.
  • 완전한 타임스탬프를 사람용 식별자로 쓴다. 소리 내어 읽다 보면 숫자가 떨어져 나갑니다. 짧은 참조가 필요하면 축약하거나 해시하세요.

직접 해보기

타임스탬프 변환기에 에포크 값이나 날짜 문자열을 붙여 넣고, 초·밀리초·ISO 8601·내 시각 사이를 왕복해 보세요. 변환은 브라우저에서 일어나므로, 읽기 좋게 만들기 위해 민감 여부와 무관하게 어떤 타임스탬프도 기기를 떠날 필요가 없습니다.