유닉스 시간 완벽 정리: 1970년부터 2038년까지
2026년 11월 업데이트
1767225600 같은 유닉스 타임스탬프는 정해진 기준 시점, 즉 유닉스 에포크인 1970년 1월 1일 00:00:00 UTC부터 흐른 초를 센 값입니다. 이 숫자는 정확히 2026년 1월 1일 0시(UTC)입니다. 타임스탬프는 시간대가 붙지 않은 숫자일 뿐이라서, 컴퓨터가 시각을 저장하고 주고받는 가장 흔한 방법이 되었습니다. 이 글에서는 타임스탬프 읽는 법, 여러 언어에서 올바르게 변환하는 법, 그리고 실제 버그를 일으키는 밀리초, 시간대, 윤초, 2038년 문제를 설명합니다.
유닉스 시간은 이렇게 동작합니다
유닉스 시간은 1초마다 1씩 늘어나고, 같은 순간이라면 지구 어디에서나 값이 같습니다. 기준이 되는 값 몇 가지를 보겠습니다.
| 타임스탬프 | 날짜와 시각(UTC) | 참고 |
|---|---|---|
0 | 1970-01-01 00:00:00 | 에포크 |
86400 | 1970-01-02 00:00:00 | 하루는 86,400초 |
-86400 | 1969-12-31 00:00:00 | 음수는 1970년 이전 날짜 |
1000000000 | 2001-09-09 01:46:40 | 처음으로 10자리가 된 타임스탬프 |
1767225600 | 2026-01-01 00:00:00 | 아래 예제에서 사용 |
2000000000 | 2033-05-18 03:33:20 | |
2147483647 | 2038-01-19 03:14:07 | 부호 있는 32비트 정수의 최댓값 |
유닉스 시간에서는 하루가 언제나 정확히 86,400초라서 계산이 간단합니다. 1,767,225,600 ÷ 86,400 = 20,454이므로 2026년 1월 1일은 에포크로부터 20,454일째이고, 타임스탬프에 7 × 86,400을 더하면 정확히 1주일 뒤가 됩니다. 현지 시각은 또 다른 문제인데, 이는 시간대 부분에서 설명합니다.
초, 밀리초, 그보다 작은 단위
전통적인 유닉스 방식은 초 단위로 세지만, 더 작은 단위를 쓰는 시스템도 많습니다. 보통 자릿수를 보면 알 수 있습니다. 2001년부터 2286년 사이의 날짜라면 초 단위 타임스탬프는 10자리입니다.
| 자릿수 | 단위 | 주로 쓰는 곳 | 2026년 1월 1일 0시(UTC) |
|---|---|---|---|
| 10 | 초 | 유닉스 셸, PHP, 대부분의 데이터베이스, JWT의 exp 클레임 | 1767225600 |
| 13 | 밀리초 | JavaScript Date.now(), Java System.currentTimeMillis() | 1767225600000 |
| 16 | 마이크로초 | 일부 API, 로그, 분석 시스템 | 1767225600000000 |
| 19 | 나노초 | Go UnixNano(), Python time.time_ns() | 1767225600000000000 |
단위를 헷갈리는 것은 아주 흔한 버그입니다. 밀리초를 초로 읽으면 수만 년 뒤의 날짜가 나오고, 초를 밀리초로 읽으면 1970년 1월로 돌아갑니다. 변환한 날짜가 1970년으로 나온다면 먼저 이것을 의심하세요. 유닉스 타임스탬프 변환기는 자릿수를 보고 단위를 자동으로 알아냅니다.
시간대와 UTC
타임스탬프에는 시간대가 없습니다. 어떤 한 순간을 가리킬 뿐이고, 시간대는 화면에 보여 줄 때에만 필요합니다. 타임스탬프 1767225600은 다음과 같습니다.
- 런던: 2026년 1월 1일 00:00 (겨울에는 UTC+0)
- 상하이: 2026년 1월 1일 08:00 (UTC+8)
- 서울·도쿄: 2026년 1월 1일 09:00 (UTC+9)
- 뉴욕: 2025년 12월 31일 19:00 (겨울에는 UTC−5)
여기서 좋은 습관이 나옵니다.
- 시각은 UTC로 저장하고 주고받으세요. 유닉스 타임스탬프나
Z또는 오프셋으로 끝나는 ISO 8601 문자열을 쓰면 됩니다. - 현지 시각으로는 사람에게 날짜를 보여 주거나 사람에게서 입력받을 때, 즉 맨 끝에서만 바꾸세요.
- 고정 오프셋이나 약어 대신
Asia/Seoul,America/New_York같은 IANA 시간대 이름을 쓰세요. 이름에는 그 지역의 서머타임 규칙과 그 변천이 담겨 있지만, −05:00 같은 오프셋은 뉴욕에서 1년 중 일부 기간에만 맞습니다. 한국은 서머타임이 없어 늘 UTC+9이지만, 미국이나 유럽 쪽 시각을 다룬다면 이 차이가 중요합니다. - “매주 월요일 베를린 시각 9시 회의”처럼 현지 시각으로 정한 미래 일정은 미리 계산한 타임스탬프가 아니라 현지 시각과 시간대 이름을 함께 저장하세요. 각국 정부는 서머타임 규칙을 바꾸기도 해서, 오늘 계산한 타임스탬프가 나중에 1시간 어긋날 수 있습니다.
여러 도시의 시각을 비교하려면 서머타임을 자동으로 반영하는 시차 계산기를, 두 날짜 사이의 일수를 세거나 날짜에 며칠을 더하려면 날짜 계산기를 쓰세요.
코드에서 타임스탬프 변환하기
모든 예제는 2026-01-01 00:00:00 UTC인 1767225600을 사용합니다.
JavaScript
| 작업 | 코드 |
|---|---|
| 현재 시각(초) | Math.floor(Date.now() / 1000) |
| 타임스탬프 → ISO 문자열 | new Date(1767225600 * 1000).toISOString() 결과 "2026-01-01T00:00:00.000Z" |
| 특정 시간대로 표시 | new Date(1767225600 * 1000).toLocaleString("ko-KR", { timeZone: "Asia/Seoul" }) |
| 날짜 문자열 → 타임스탬프 | Date.parse("2026-01-01T09:00:00+09:00") / 1000 결과 1767225600 |
JavaScript의 날짜는 밀리초 단위이므로 넣을 때는 1,000을 곱하고 꺼낼 때는 1,000으로 나누세요. 또 new Date("2026-01-01")(날짜만)은 UTC 자정으로 읽히지만, new Date("2026-01-01T00:00")(오프셋 없는 날짜와 시각)은 현지 시각으로 읽힙니다. 한국에서 실행하면 두 값은 9시간 차이가 납니다. 특정한 순간을 뜻한다면 항상 Z나 오프셋을 붙이세요.
Python
| 작업 | 코드 |
|---|---|
| 현재 시각(초) | time.time() (실수) |
| 타임스탬프 → UTC datetime | datetime.fromtimestamp(1767225600, tz=timezone.utc) |
| 타임스탬프 → 특정 시간대 | datetime.fromtimestamp(1767225600, tz=ZoneInfo("Asia/Seoul")) |
| datetime → 타임스탬프 | datetime(2026, 1, 1, tzinfo=timezone.utc).timestamp() 결과 1767225600.0 |
임포트는 from datetime import datetime, timezone과 from zoneinfo import ZoneInfo(Python 3.9 이상)입니다. tzinfo가 없는 “naive” datetime은 피하세요. 여기에 .timestamp()를 호출하면 컴퓨터의 현지 시간대로 간주하고, naive 값을 돌려주는 datetime.utcfromtimestamp()는 Python 3.12부터 사용 중단(deprecated)되었습니다. Windows에서는 IANA 시간대 데이터베이스가 기본으로 들어 있지 않아서 zoneinfo를 쓰려면 tzdata 패키지(pip install tzdata)도 필요합니다.
SQL
| 데이터베이스 | 타임스탬프 → 날짜 | 날짜 → 타임스탬프 |
|---|---|---|
| PostgreSQL | to_timestamp(1767225600) | EXTRACT(EPOCH FROM TIMESTAMPTZ '2026-01-01 00:00:00+00') |
| MySQL | FROM_UNIXTIME(1767225600) | UNIX_TIMESTAMP('2026-01-01 00:00:00') |
| SQLite | datetime(1767225600, 'unixepoch') | unixepoch('2026-01-01 00:00:00') |
PostgreSQL의 to_timestamp는 timestamptz를 돌려주며, 세션의 TimeZone 설정에 따라 표시됩니다. MySQL의 FROM_UNIXTIME과 UNIX_TIMESTAMP는 둘 다 세션 시간대를 따르기 때문에 같은 쿼리라도 서버마다 결과가 다를 수 있습니다. UTC로 받고 싶다면 먼저 SET time_zone = '+00:00'을 실행하세요. SQLite는 UTC로 동작하며, unixepoch()는 SQLite 3.38 이상이 필요합니다. 그보다 오래된 버전에서는 텍스트를 돌려주는 strftime('%s', '2026-01-01 00:00:00')을 쓸 수 있습니다.
셸
| 작업 | Linux (GNU date) | macOS (BSD date) |
|---|---|---|
| 현재 시각(초) | date +%s | date +%s |
| 타임스탬프 → 날짜(UTC) | date -u -d @1767225600 | date -u -r 1767225600 |
| 날짜 → 타임스탬프 | date -u -d '2026-01-01 00:00:00' +%s | date -u -j -f '%Y-%m-%d %H:%M:%S' '2026-01-01 00:00:00' +%s |
윤초
UTC는 조금씩 불규칙한 지구 자전에 맞추기 위해 가끔 윤초를 넣습니다. 1972년 이후 27번 추가되었고, 가장 최근은 2016년 12월 31일 23:59:60 UTC입니다. 유닉스 시간은 윤초가 없는 것처럼 동작합니다. 하루가 언제나 정확히 86,400초이므로, 윤초 동안 유닉스 시계는 1초를 반복하거나 멈추고, 일부 대형 사업자는 그 1초를 여러 시간에 걸쳐 조금씩 나눠 흡수(“스미어”)합니다. 대부분의 소프트웨어에서는 드러나지 않습니다. 짧은 시간 간격을 재는 것처럼 윤초가 문제가 되는 곳에서는 벽시계 시각 대신 JavaScript의 performance.now()나 Python의 time.monotonic() 같은 단조 시계를 쓰세요. 2022년 국제도량형총회는 2035년까지 윤초를 사실상 폐지하기로 의결했습니다.
2038년 문제
오래된 시스템 중에는 유닉스 시간을 부호 있는 32비트 정수로 저장하는 것이 많습니다. 이 정수의 최댓값은 2,147,483,647, 즉 2038년 1월 19일 화요일 03:14:07 UTC(한국 시각 낮 12:14:07)입니다. 1초 뒤에는 값이 −2,147,483,648로 넘어가고, 그런 시스템은 이를 1901년 12월 13일 20:45:52 UTC로 읽습니다.
최신 64비트 운영체제와 언어는 64비트 타임스탬프를 쓰므로 약 2,920억 년 동안 넘칠 일이 없습니다. 위험은 아직 32비트를 쓰는 오래된 기기나 임베디드 장치, 파일 형식, 데이터베이스 열에 있습니다. 예를 들어 MySQL의 TIMESTAMP 열 타입은 2038-01-19 03:14:07 UTC에서 끝나지만, DATETIME에는 이런 제한이 없습니다. 15년짜리 대출 상환 일정이나 유효기간이 긴 인증서처럼 먼 미래의 날짜를 계산하는 소프트웨어는 2038년이 오기 훨씬 전에 이 한계에 부딪힐 수 있습니다.
ISO 8601: 사람이 읽기 쉬운 대안
프로그램뿐 아니라 사람도 읽어야 하는 시각이라면 ISO 8601이나, 이를 인터넷용으로 더 엄격하게 정한 RFC 3339를 쓰세요. 큰 단위부터 작은 단위 순서로 적습니다.
2026-01-01T00:00:00Z: UTC.Z는 오프셋이 0이라는 뜻입니다.2026-01-01T09:00:00+09:00: 같은 순간을 서울·도쿄 시각으로 쓴 것2026-01-01: 시각 없는 날짜
연도부터 초까지 순서대로 적기 때문에, 형식과 오프셋이 같은 ISO 8601 문자열은 그냥 텍스트로 정렬해도 시간순이 되어 파일 이름이나 로그에 쓰기 좋습니다. 반면 01/02/2026 같은 형식은 미국에서는 1월 2일, 유럽 대부분에서는 2월 1일이라 헷갈리니 피하세요.
관련 도구
- 유닉스 타임스탬프 변환기 – 초, 밀리초, 마이크로초, 나노초 타임스탬프를 날짜로, 날짜를 타임스탬프로 바꿉니다.
- 시차 계산기 – 서머타임을 반영해 여러 도시의 날짜와 시각을 비교합니다.
- 날짜 계산기 – 두 날짜 사이의 일수를 세거나 날짜에 일, 주, 개월을 더합니다.