본문으로 바로가기

Unix 타임스탬프 가이드: UTC·KST·2038년 문제

2026.03.302026.07.10 수정35분 읽기

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

목차

API 응답에서 "created_at": 1740000000처럼 큰 숫자를 본 적 있나요? 이것이 Unix 타임스탬프(Epoch time, 유닉스 시간)입니다. 개발 현장 어디서나 등장하는 이 개념을 이해하면 시간 관련 버그의 절반이 사라집니다.

이 글은 Unix 타임스탬프의 정의, 초·밀리초·마이크로초 차이, 언어별 변환 방법, UTC와 KST(한국 표준시) 처리,
2038년 문제, 그리고 실무에서 만나는 시간대 함정까지 한 번에 정리합니다.

Unix 타임스탬프란?#

1970년 1월 1일 00:00:00 UTC(협정 세계시)를 기점(Epoch)으로 경과한 초(second) 수입니다.

Unix 타임스탬프 0       = 1970-01-01 00:00:00 UTC
Unix 타임스탬프 1000000000 = 2001-09-09 01:46:40 UTC
Unix 타임스탬프 1700000000 = 2023-11-14 22:13:20 UTC
Unix 타임스탬프 1767225600 = 2026-01-01 00:00:00 UTC

왜 1970년인가? Unix 운영체제가 개발된 시기(1969–1970)를 기준으로 정했습니다. 기술적으로 임의의 기준점이지만 전 세계 시스템이 이 표준을 따릅니다(POSIX 명세).

윤초(Leap Second)는 무시#

Unix 타임스탬프는 윤초를 카운트하지 않습니다. 매일이 정확히 86,400초로 가정합니다. 천문학적으로 윤초가 가끔 추가되지만,
컴퓨터 시스템에서는 보통 무시하거나 "smear"(분산) 방식으로 처리합니다.

초 vs 밀리초 vs 마이크로초#

구분단위자릿수(2026년 기준)예시
Unix 타임스탬프10자리1,767,225,600
밀리초 타임스탬프ms13자리1,767,225,600,000
마이크로초μs16자리1,767,225,600,000,000
나노초ns19자리1,767,225,600,000,000,000

자릿수만 봐도 어떤 단위인지 구분할 수 있습니다. 일반적으로:

  • 초 단위: Unix·Linux 표준, JWT의 exp/iat, 일반 API
  • 밀리초 단위: JavaScript Date.now(), Java System.currentTimeMillis()
  • 마이크로초·나노초: 고해상도 측정(process.hrtime, time.perf_counter)

자릿수로 단위 추론#

2026년 5월 기준
초:    1,767,xxx,xxx (10자리, 2,000,000,000보다 작음)
ms:    1,767,xxx,xxx,xxx (13자리)
μs:    1,767,xxx,xxx,xxx,xxx (16자리)

10자리 미만은 매우 오래된 날짜이거나 잘못된 값일 가능성이 높습니다.

언어별 현재 타임스탬프 얻기#

JavaScript#

Date.now()                       // 1767225600000 (밀리초)
Math.floor(Date.now() / 1000)    // 1767225600 (초)
new Date().getTime()             // Date.now()와 동일
+new Date()                      // 위와 동일

// Node.js 고해상도
process.hrtime.bigint()           // 나노초 BigInt
performance.now()                 // 부동소수점 ms (페이지 로드 기준)

Python#

import time

time.time()              # 1767225600.123456 (소수점 초)
int(time.time())         # 1767225600 (정수 초)
time.time_ns()           # 1767225600123456789 (나노초)

from datetime import datetime, timezone
datetime.now(timezone.utc).timestamp()  # 부동소수점 초

SQL#

-- MySQL
SELECT UNIX_TIMESTAMP();                        -- 현재 초 타임스탬프
SELECT UNIX_TIMESTAMP('2026-01-01 00:00:00');   -- 특정 날짜
SELECT UNIX_TIMESTAMP() * 1000;                 -- 밀리초

-- PostgreSQL
SELECT EXTRACT(EPOCH FROM NOW());               -- 부동소수점 초
SELECT EXTRACT(EPOCH FROM NOW())::BIGINT;       -- 정수 초

-- SQLite
SELECT strftime('%s', 'now');                   -- 초

Shell#

date +%s              # 1767225600 (초)
date +%s%N            # 나노초 (Linux)
date -u +%s           # UTC 기준 (보통 동일)

Go#

import "time"

time.Now().Unix()       // 초
time.Now().UnixMilli()  // 밀리초
time.Now().UnixNano()   // 나노초

타임스탬프 ↔ 날짜 변환#

타임스탬프 → 날짜#

// JavaScript
const ts = 1767225600;
new Date(ts * 1000).toISOString();
// "2026-01-01T00:00:00.000Z"

new Date(ts * 1000).toLocaleString("ko-KR", { timeZone: "Asia/Seoul" });
// "2026. 1. 1. 오전 9:00:00" (KST = UTC+9)
from datetime import datetime, timezone

datetime.fromtimestamp(1767225600, tz=timezone.utc)
# datetime(2026, 1, 1, 0, 0, tzinfo=timezone.utc)

# KST 변환
import zoneinfo
kst = zoneinfo.ZoneInfo("Asia/Seoul")
datetime.fromtimestamp(1767225600, tz=kst)
# datetime(2026, 1, 1, 9, 0, tzinfo=ZoneInfo('Asia/Seoul'))

날짜 → 타임스탬프#

// JavaScript
new Date("2026-01-01T00:00:00Z").getTime() / 1000;  // 1767225600
new Date("2026-01-01T00:00:00+09:00").getTime() / 1000;  // 한국 시간 입력
from datetime import datetime, timezone

datetime(2026, 1, 1, tzinfo=timezone.utc).timestamp()  # 1767225600.0

시간대(Timezone) 처리#

Unix 타임스탬프는 시간대 정보가 없는 절대 시간(UTC 기준)입니다. 동일한 타임스탬프라도 표시 시간대에 따라 로컬 시간이 달라집니다.

타임스탬프 1767225600
  UTC: 2026-01-01 00:00:00
  KST: 2026-01-01 09:00:00 (UTC+9)
  EST: 2025-12-31 19:00:00 (UTC-5)
  PST: 2025-12-31 16:00:00 (UTC-8)
  JST: 2026-01-01 09:00:00 (UTC+9, 한국과 동일)

한국 표준시(KST) 처리#

한국은 UTC+9로 일년 내내 일정합니다(서머타임 없음). 따라서 시간대 처리가 비교적 단순합니다.

// 현재 KST 시간 표시
new Date().toLocaleString("ko-KR", { timeZone: "Asia/Seoul" });

// UTC 타임스탬프를 KST로 변환
const kstDate = new Date(unixTs * 1000).toLocaleString("ko-KR", {
  timeZone: "Asia/Seoul",
  year: "numeric",
  month: "2-digit",
  day: "2-digit",
  hour: "2-digit",
  minute: "2-digit",
});

흔한 시간대 버그#

1. 서버·클라이언트 시간대 불일치

서버가 UTC, 클라이언트가 KST일 때 날짜가 하루 어긋나는 버그.

서버 타임스탬프: 1767225600 (UTC 2026-01-01 00:00:00)
KST 표시: 2026-01-01 09:00:00
EST 표시: 2025-12-31 19:00:00 ← 하루 다름!

원칙: 서버는 항상 UTC로 저장, 표시할 때만 클라이언트 시간대로 변환.

2. JavaScript new Date("2026-01-01")의 함정

new Date("2026-01-01");
// 브라우저: UTC 2026-01-01 00:00:00 → KST 2026-01-01 09:00:00
// 시간대 정보 없는 ISO 문자열은 UTC로 해석

new Date("2026-01-01T00:00:00");
// 일부 브라우저: 로컬 시간으로 해석 (시간대 정보 없음)
// 권장: 항상 시간대 명시 "2026-01-01T00:00:00Z" 또는 "+09:00"

3. 데이터베이스 컬럼 타입

타입시간대권장 상황
TIMESTAMPUTC 저장, 세션 시간대로 변환일반
DATETIME시간대 정보 없음사용 주의
TIMESTAMPTZ(PostgreSQL)시간대 포함 UTC 저장권장
BIGINT단순 숫자(타임스탬프)정밀도 우선

실무 활용 사례#

1. API 설계#

{
  "id": 12345,
  "created_at": 1767225600,
  "updated_at": 1767312000,
  "expires_at": 1767484800
}

타임스탬프로 저장하면:

  • 시간대에 독립적
  • 정수 비교로 날짜 순서 판단(정렬 빠름)
  • 모든 언어에서 날짜 파싱 가능
  • API JSON 크기 절감

문자열 ISO 8601("2026-01-01T00:00:00Z")도 표준이며, 사람이 읽기 편합니다. 둘 다 사용 가능.

2. JWT 토큰 만료 시간#

JWT의 exp(만료) 클레임은 표준상 Unix 타임스탬프(초)입니다.

{
  "sub": "user123",
  "iat": 1767225600,    // 발급 시간 (issued at)
  "exp": 1767229200,    // 만료 시간 = iat + 3600 (1시간)
  "nbf": 1767225600     // not before
}

서버는 Date.now() / 1000 > exp로 만료 여부를 확인합니다.

3. 캐시 만료 시간#

const cache = {
  data: response,
  expires: Math.floor(Date.now() / 1000) + 300  // 5분 후
};

if (Math.floor(Date.now() / 1000) > cache.expires) {
  // 캐시 만료
}

4. 데이터베이스 인덱싱#

날짜 범위 조회 시 타임스탬프 컬럼은 정수 비교로 인덱스를 효율적으로 활용합니다.

-- 2026년 1월 포스트 조회
SELECT * FROM posts
WHERE created_at BETWEEN 1767225600 AND 1769903999;

-- 인덱스
CREATE INDEX idx_posts_created ON posts(created_at);

5. 이벤트 정렬#

분산 시스템에서 시계 차이로 순서가 뒤집힐 수 있습니다. UUIDv7(2024년 RFC 9562 표준)은 타임스탬프 기반 정렬 가능 UUID입니다.

UUIDv7: 0193f4c5-4abc-7ace-9def-123456789abc
        └─ms 타임스탬프 ─┘

6. 로그 분석#

# 최근 1시간 로그(타임스탬프 ms)
THRESHOLD=$(($(date +%s%3N) - 3600000))
jq "select(.timestamp > $THRESHOLD)" log.json

2038년 문제#

32비트 정수로 Unix 타임스탬프를 저장하면 2038년 1월 19일 03:14:07 UTC에 오버플로가 발생합니다.

2^31 - 1 = 2,147,483,647 (32비트 부호 있는 정수 최댓값)
= 2038-01-19 03:14:07 UTC

이 시각이 지나면 음수가 되어 1901년 12월로 점프합니다(Y2K38 문제).

현재 상황#

대부분의 시스템은 64비트로 전환되어 해결됐습니다. 64비트 타임스탬프는 약 2920억 년까지 표현 가능합니다.

남은 위험:

  • 임베디드 시스템(IoT 기기, 산업 제어)
  • 32비트 모바일 앱(이미 거의 사라짐)
  • 레거시 데이터베이스 컬럼(INT 대신 BIGINT 권장)

데이터베이스 컬럼 권장#

-- 32비트(2038년 문제)
created_at INT

-- 64비트 (권장)
created_at BIGINT
created_at TIMESTAMP   -- DBMS가 자동 처리

자주 묻는 질문#

Q. 타임스탬프 0은 무슨 시간인가요?

A. 1970년 1월 1일 00:00:00 UTC. 이를 "Epoch" 또는 "the Unix Epoch"라고 합니다.

Q. 음수 타임스탬프도 가능한가요?

A. 가능합니다. 1970년 이전 시간을 음수로 표현합니다. 예: -1 = 1969-12-31 23:59:59 UTC. 단, 일부 시스템(MySQL TIMESTAMP)은 음수를 지원하지 않습니다.

Q. 한국 표준시(KST)와 UTC 차이는?

A. KST = UTC + 9시간. 한국은 1961년 이후 서머타임을 사용하지 않아 일년 내내 UTC+9로 일정합니다. 일본 표준시(JST)와 동일.

Q. Date.now() / 1000Math.floor(Date.now() / 1000) 차이는?

A. 전자는 부동소수점(예: 1767225600.123), 후자는 정수(1767225600). API 표준은 정수. 부동소수점 비교는 부정확할 수 있어 정수로 변환합니다.

Q. 자릿수 11자리는 무슨 단위인가요?

A. 11자리 타임스탬프는 약 5,138년에 해당해 자연스러운 단위가 아닙니다. 잘못된 데이터이거나 곱셈 오류일 가능성이 높습니다. 10자리(초) 또는 13자리(ms)인지 확인합니다.

Q. PHP의 time()은 어떤 단위인가요?

A. 초(integer). microtime(true)는 부동소수점 초, microtime()은 ms 단위 문자열.

Q. 타임스탬프가 미래인지 과거인지 확인하려면?

A. Date.now() / 1000 > timestamp 비교. 단, 클라이언트 시계가 잘못 설정된 경우 부정확합니다. 중요한 시간 검증은 서버 시계로 수행합니다.

Q. ISO 8601 vs Unix 타임스탬프 어느 게 좋나요?

A. 둘 다 표준. 사람 가독성·디버깅이 우선이면 ISO 8601("2026-01-01T00:00:00Z"), 정밀도·정렬·연산이 우선이면 Unix 타임스탬프. 보통 API 응답에 둘 다 포함하기도 합니다.

Q. JavaScript new Date()로 잘못된 날짜를 만들면?

A. Invalid Date 객체가 반환되며 getTime()NaN. 입력 검증 후 사용합니다.

const d = new Date("invalid");
isNaN(d.getTime()); // true

Unix 타임스탬프 도구 활용#

타임스탬프 변환 도구에 타임스탬프를 입력하면 UTC·KST 시간으로 즉시 변환합니다. 반대 방향(날짜 → 타임스탬프) 변환, ms 단위 토글, 현재 시각 자동 갱신 등을 지원합니다.

개발 도구가 더 필요하다면 정규식 가이드, JSON 포맷터 가이드, Base64 인코딩 가이드,
JWT 디코더 가이드, Cron 표현식 가이드도 함께 참고할 수 있습니다.

이런 글도 읽어보세요

전체 글 보기

관련 도구