timestamp unix time date converter

Unix 타임스탬프 변환기: 에포크 시간을 읽기 쉬운 날짜로 변환하세요

Unix 타임스탬프를 사람이 읽을 수 있는 날짜로 또는 그 반대로 변환하세요. 에포크 시간 및 날짜 형식을 다루는 개발자를 위한 필수 온라인 도구입니다.

소개

Unix 타임스탬프는 컴퓨터 과학에서 가장 기본적인 개념 중 하나입니다. Unix 에포크(1970년 1월 1일 00:00:00 UTC) 이후 경과한 초를 단순한 정수로 표현합니다. 이 단순하고 범용적인 형식은 데이터베이스 레코드, API 응답, 로그 파일, 예약된 작업 등 모든 것을 지탱합니다.

"2024년 4월 9일"과 같은 사람이 읽을 수 있는 날짜 문자열과 달리, Unix 타임스탬프에는 시간대도, 로케일도, 모호함도 없습니다. 숫자 1712620800은 지구상 어디서나 정확히 동일한 순간을 가리킵니다. 이 특성이 Unix 타임스탬프를 소프트웨어 엔지니어링에서 날짜와 시간의 공용어로 만듭니다.

Unix 에포크의 역사

Unix 운영 체제는 1960년대 후반부터 1970년대 초반에 걸쳐 벨 연구소(Bell Labs)의 Ken Thompson, Dennis Ritchie 등에 의해 개발되었습니다. 시간 추적은 처음부터 커널에 내장되어 있었지만, 에포크 날짜의 정확한 선택은 초기 개발 과정에서 변화했습니다.

초기 Unix 버전 일부에서는 1971년 1월 1일이 에포크로 사용되었습니다. 다른 버전들은 1969년 1월 1일을 시도하기도 했습니다. 결국 1970년 1월 1일 00:00:00 UTC가 표준화되었습니다. 이는 시스템 탄생에 충분히 가까운 "깔끔한" 날짜로, 실제 이벤트의 타임스탬프가 작은 양의 정수가 된다는 장점이 있었습니다.

1970-01-01의 선택은 본질적으로 임의적입니다. 중요한 것은 날짜 자체가 아니라 관례의 일관성입니다. 모든 시스템이 동일한 에포크에 동의하기 때문에, 타임스탬프는 프로그램, 언어, 플랫폼 간에 변환 오버헤드 없이 교환될 수 있습니다.

이 관례는 나중에 POSIX(이식 가능한 운영 체제 인터페이스)에서 공식화되었습니다. POSIX는 Unix 타임스탬프를 "1970-01-01T00:00:00Z 이후의 윤초를 제외한 초 수"로 정의합니다.

초와 밀리초

모든 "타임스탬프"가 동일한 단위는 아닙니다. 가장 중요한 차이점은 단위에 있습니다:

  • Unix 타임스탬프(POSIX): 에포크 이후 초 수 — 예: 1712620800
  • JavaScript Date: 에포크 이후 밀리초 수 — 예: 1712620800000
  • Java System.currentTimeMillis(): 에포크 이후 밀리초 수
  • Go time.Now().Unix(): 초; time.Now().UnixNano()는 나노초 정밀도
  • Python time.time(): 부동소수점 초

초와 밀리초 사이의 1000배 차이는 흔한 버그의 원인입니다. 밀리초 타임스탬프를 초를 기대하는 코드에 전달하면 먼 미래(약 서기 56000년)의 날짜가 생성됩니다. 반대의 경우에는 1970년 1월 날짜가 생성됩니다.

변환은 간단합니다:

const unix_ms = unix_s * 1000;
const unix_s  = Math.floor(unix_ms / 1000);

실용적인 판단 기준: 타임스탬프가 10자리 숫자이면 초일 가능성이 높고, 13자리이면 밀리초일 가능성이 높습니다.

ISO 8601 형식과 변형

ISO 8601은 날짜와 시간 표현을 위한 국제 표준입니다. 일련의 명확한 문자열 형식을 정의합니다:

2024-04-09                        # 날짜만
2024-04-09T12:00:00               # 로컬 날짜시간 (시간대 정보 없음)
2024-04-09T12:00:00Z              # UTC (Z = Zulu 시간 = UTC+0)
2024-04-09T20:00:00+08:00         # UTC 오프셋 포함 (예: Asia/Shanghai)
2024-04-09T12:00:00.123Z          # 밀리초 포함
2024-04-09T12:00:00.123456789Z    # 나노초 포함

T는 날짜 부분과 시간 부분을 구분합니다. Z 접미사(NATO 음성 알파벳 "Zulu", 즉 UTC+0)는 시간이 UTC임을 나타냅니다. +08:00과 같은 오프셋은 현지 시간이 UTC보다 8시간 앞선다는 것을 의미합니다.

ISO 8601은 REST API, 로그 파일, 그리고 경계를 넘어 시간 데이터를 교환하는 모든 시스템에 권장되는 형식입니다. 사람이 읽을 수 있고, 문자열로 사전순 정렬이 가능하며, 모호함이 없습니다.

시간대와 UTC

**UTC(협정 세계시)**는 세계의 주요 시간 표준입니다. 그 자체로 시간대는 아니지만, 모든 시간대가 정의되는 기준선입니다. UTC+0은 겨울의 그리니치 표준시(GMT)와 동일합니다.

시간대는 UTC의 오프셋으로 표현됩니다: UTC+5:30(인도), UTC-8(미국 태평양 표준시), UTC+9(한국, 일본). 그러나 오프셋만으로는 시간대를 완전히 설명하기에 충분하지 않습니다. 일광 절약 시간(DST)에 의해 오프셋이 변하기 때문입니다.

IANA 시간대 데이터베이스(tz 데이터베이스 또는 zoneinfo라고도 함)는 세계 모든 시간대의 권위 있는 목록입니다. America/New_York, Europe/Berlin, Asia/Tokyo, Asia/Seoul과 같은 식별자를 사용합니다. 이 식별자들은 현재 UTC 오프셋뿐만 아니라 DST 규칙과 정치적 변경(예: 국가가 시간대를 변경한 경우)의 전체 역사적 기록을 포함합니다.

모든 주요 프로그래밍 언어와 운영 체제에는 IANA 데이터베이스가 포함되어 있습니다:

  • JavaScript(Node.js): IANA 식별자를 사용하는 Intl.DateTimeFormat
  • Python: zoneinfo 모듈(Python 3.9 이상) 또는 pytz
  • Java: java.time.ZoneId (예: ZoneId.of("Asia/Seoul"))
  • Go: time.LoadLocation("Asia/Seoul")

애플리케이션 로직에서 시간대를 표현할 때 +09:00과 같은 원시 UTC 오프셋을 사용하지 마세요. 오프셋은 계절에 따라 변하므로 IANA 식별자를 사용하세요.

일광 절약 시간(DST)의 복잡성

일광 절약 시간은 저녁 일조 시간을 늘리기 위해 여름철에 시계를 1시간 앞당기는 관행입니다. 북미와 유럽 대부분 지역과 남미, 중동, 오세아니아 일부 지역에서 시행됩니다. 한국, 일본, 중국, 인도 등 많은 나라는 DST를 시행하지 않습니다.

DST는 두 가지 고전적인 이상 현상을 유발합니다:

봄 앞당기기(Spring forward): 시계가 오전 2:00에서 바로 오전 3:00으로 이동합니다. 오전 2:00~3:00의 60분은 현지 시간으로 존재하지 않습니다. 오전 2:30에 작업을 예약하면, 스케줄러의 구현에 따라 오전 3:30에 실행되거나 완전히 건너뛰어집니다.

가을 되돌리기(Fall back): 시계가 오전 2:00에서 오전 1:00으로 돌아갑니다. 오전 1:00~2:00의 60분이 두 번 발생합니다. "현지 시간 오전 1:45"의 로그 항목을 기록하면 모호해집니다—그 시간의 첫 번째 발생인지 두 번째 발생인지 알 수 없습니다.

Unix 타임스탬프는 항상 UTC를 기준으로 하기 때문에 DST의 영향을 전혀 받지 않습니다. 숫자 1712620800은 어디에 있든, 어느 계절이든 항상 동일한 순간을 가리킵니다.

황금 법칙: 항상 UTC로 타임스탬프를 저장하고 전송하세요. 현지 시간으로의 변환은 사용자에게 표시하기 직전의 표현 계층에서만 수행하세요.

2038년 문제

2038년 문제(Y2K38 또는 Unix 밀레니엄 버그라고도 함)는 2000년의 Y2K 버그와 성격이 유사한 소프트웨어 취약점입니다.

근본 원인: 많은 레거시 시스템이 Unix 타임스탬프를 32비트 부호 있는 정수로 저장합니다. 32비트 부호 있는 정수의 최대값은 2,147,483,647입니다. 이는 다음에 해당합니다:

2038년 1월 19일 03:14:07 UTC

그 순간의 1초 후, 32비트 부호 있는 정수는 오버플로우하여 가장 작은 음수 값 -2,147,483,648로 돌아갑니다. 이는 1901년 12월 13일 20:45:52 UTC에 해당합니다. 오버플로우하는 시스템은 갑자기 날짜가 1901년이라고 인식합니다.

영향을 받을 수 있는 시스템:

  • 32비트 아키텍처를 위해 컴파일된 레거시 임베디드 시스템 및 IoT 기기
  • MySQL TIMESTAMP 타입을 사용하는 오래된 데이터베이스 스키마 (버전 8.0 이전은 32비트 스토리지 사용)
  • 32비트 Linux 커널 (64비트 플랫폼에서는 커널 수준에서 해결됨)
  • 수정 시간을 32비트 정수로 기록하는 일부 오래된 파일 시스템

해결책은 간단합니다: 모든 타임스탬프 저장을 64비트 부호 있는 정수로 마이그레이션하세요. 64비트 타임스탬프는 약 292,277,026,596년까지 표현할 수 있어 실질적인 우려가 없습니다. 현대의 64비트 시스템 대부분은 이미 이를 올바르게 처리합니다.

다양한 언어에서 타임스탬프 다루기

JavaScript

// 현재 시간
const now = Date.now();                   // 에포크 이후 밀리초
const unix = Math.floor(now / 1000);      // 초로 변환

// Unix 타임스탬프 파싱
const date = new Date(unix * 1000);
console.log(date.toISOString());          // "2024-04-09T12:00:00.000Z"
console.log(date.toLocaleString("ko-KR", { timeZone: "Asia/Seoul" }));

Python

import time
import datetime

# 현재 Unix 타임스탬프 (초)
unix = int(time.time())

# UTC 날짜시간으로 변환
dt = datetime.datetime.fromtimestamp(unix, tz=datetime.timezone.utc)
print(dt.isoformat())   # "2024-04-09T12:00:00+00:00"

# IANA 시간대에 zoneinfo 사용
from zoneinfo import ZoneInfo
dt_local = dt.astimezone(ZoneInfo("Asia/Seoul"))
print(dt_local.isoformat())

Go

package main

import (
    "fmt"
    "time"
)

func main() {
    unix := time.Now().Unix()           // int64 초
    t := time.Unix(unix, 0).UTC()
    fmt.Println(t.Format(time.RFC3339)) // "2024-04-09T12:00:00Z"

    loc, _ := time.LoadLocation("Asia/Seoul")
    fmt.Println(t.In(loc).Format(time.RFC3339)) // "2024-04-09T21:00:00+09:00"
}

Java

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;

long unix = Instant.now().getEpochSecond();        // 초
Instant instant = Instant.ofEpochSecond(unix);
System.out.println(instant.toString());            // "2024-04-09T12:00:00Z"

ZonedDateTime zdt = instant.atZone(ZoneId.of("Asia/Seoul"));
System.out.println(zdt.toString());

Rust

use std::time::{SystemTime, UNIX_EPOCH};

fn main() {
    let unix = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("시간이 역행했습니다")
        .as_secs();   // u64 초

    println!("{}", unix);  // 예: 1712620800
}

주요 타임스탬프 형식 표

형식 예시 사용처
Unix (초) 1712620800 Unix/Linux, POSIX API
Unix (밀리초) 1712620800000 JavaScript, Java
Unix (마이크로초) 1712620800000000 PostgreSQL, 일부 API
Unix (나노초) 1712620800000000000 Go, Rust
ISO 8601 UTC 2024-04-09T12:00:00Z REST API, 데이터베이스
ISO 8601 (오프셋 포함) 2024-04-09T20:00:00+08:00 캘린더 앱
RFC 2822 Tue, 09 Apr 2024 12:00:00 +0000 이메일 헤더
RFC 3339 2024-04-09T12:00:00Z 인터넷 프로토콜
HTTP 날짜 Tue, 09 Apr 2024 12:00:00 GMT HTTP 헤더

타임스탬프 산술

Unix 타임스탬프는 단순한 숫자이므로, 산술 연산이 매우 간단합니다.

두 타임스탬프 사이의 기간 계산:

start = 1712620800
end   = 1712707200
duration_seconds = end - start   # 86400초 = 정확히 1일

미래 또는 과거 날짜 계산:

const now = Math.floor(Date.now() / 1000);
const oneWeekLater   = now + 7 * 24 * 60 * 60;   // +604800초
const thirtyDaysAgo  = now - 30 * 24 * 60 * 60;  // -2592000초

초로 표현한 일반적인 기간:

기간
1분 60
1시간 3,600
1일 86,400
1주 604,800
30일 2,592,000
1년 (365일) 31,536,000

참고: "1개월 추가"와 같은 달력 인식 산술에는 원시 초 대신 날짜 라이브러리를 사용하세요. 월의 길이가 다르고, DST로 인해 일부 날은 23시간 또는 25시간이 될 수 있습니다.

사용 사례

애플리케이션 로깅: Unix 타임스탬프가 있는 로그 항목은 서로 다른 시간대에서 실행되는 분산 시스템 간에 모호함 없이 정렬, 필터링 및 비교될 수 있습니다.

REST API: 타임스탬프를 Unix 정수로 반환하면 서버 측에서의 시간대 해석을 피할 수 있습니다. 클라이언트는 정수를 읽어 사용자의 로컬 시간대로 형식화합니다.

데이터베이스: 타임스탬프를 정수(또는 ISO 8601 문자열)로 저장하는 것은 플랫폼별 날짜 타입보다 이식성이 높습니다. PostgreSQL의 TIMESTAMPTZ는 내부적으로 UTC를 사용하고, MySQL의 DATETIME(TIMESTAMP 대신 권장)은 Y2038 제한을 피합니다.

예약 작업과 cron 작업: UTC로 "매일 오전 3:00 실행"을 계산하면 DST로 인한 예상치 못한 동작을 피할 수 있습니다. 많은 스케줄링 프레임워크(Kubernetes CronJobs, GitHub Actions 스케줄)는 관례상 UTC를 사용합니다.

캐시 만료 및 TTL: HTTP Cache-Control: max-age=3600Expires 헤더는 절대 Unix 타임스탬프 또는 상대 초를 사용합니다. CDN과 브라우저는 캐시를 무효화하기 위해 정확한 타임스탬프 산술에 의존합니다.

이벤트 소싱 및 감사 추적: 불변 이벤트 로그에는 이벤트를 명확하게 순서화하는 타임스탬프가 필요합니다. Unix 타임스탬프, 특히 나노초 해상도의 경우, 고처리량 시스템에서도 이 보장을 제공합니다.

모범 사례

  1. 항상 UTC로 타임스탬프를 저장하세요. 데이터베이스에 현지 시간을 저장하지 마세요. 현지 시간으로의 변환은 표현 계층에서만 수행하세요.

  2. 64비트 정수를 사용하세요. 새 코드에서 타임스탬프에 int32를 사용하지 마세요. 시스템이 현재 가까운 미래의 날짜만 처리하더라도 64비트가 안전한 기본값입니다.

  3. 사람이 읽을 수 있는 직렬화에는 ISO 8601을 사용하세요. 로그나 API에서 문자열 표현이 필요할 때, ISO 8601은 모호하지 않고 사전순으로 정렬됩니다.

  4. 오프셋 대신 IANA 시간대 식별자를 사용하세요. "Asia/Seoul"은 올바릅니다. "+09:00"은 (DST를 시행하는 지역에서) 연 2회 변경되어 취약합니다.

  5. 타임스탬프 단위를 검증하세요. 외부 타임스탬프를 사용하기 전에 초, 밀리초 또는 다른 단위인지 확인하세요. 빠른 확인: 10자리 숫자는 초일 가능성이 높고, 13자리는 밀리초일 가능성이 높습니다.

  6. 프로덕션 코드에서 정규 표현식으로 날짜를 파싱하지 마세요. 언어의 표준 라이브러리나 충분히 테스트된 서드파티 라이브러리를 사용하세요.

  7. "자정"에 주의하세요. DST를 시행하는 시간대에서는 특정 날짜에 자정이 존재하지 않을 수 있습니다(봄 앞당기기 시). 날짜만의 계산에는 안전한 "대표" 시간으로 정오(UTC 12:00)를 사용하세요.

  8. DST 전환 주변에서 테스트하세요. 애플리케이션에 스케줄링이나 시간 계산이 포함된다면, 관련 시간대의 봄 앞당기기 및 가을 되돌리기 경계를 구체적으로 테스트하는 테스트를 작성하세요.

자주 묻는 질문(FAQ)

Q: Unix 타임스탬프 0은 무엇을 나타냅니까?
A: 1970년 1월 1일 00:00:00 UTC — Unix 에포크입니다. 음수 타임스탬프는 1970년 이전의 날짜를 나타냅니다.

Q: 타임스탬프로 정렬할 수 있습니까?
A: 네. Unix 타임스탬프는 단조 증가하는 정수이므로, 타임스탬프로 정렬하는 것은 시간순으로 정렬하는 것과 동일합니다.

Q: Unix 시간은 윤초의 영향을 받습니까?
A: POSIX는 Unix 시간을 "각 날이 정확히 86,400초"로 정의하므로, 윤초는 계산되지 않습니다. POSIX 타임스탬프는 기술적으로 "Unix 시간" 또는 "POSIX 시간"이며, "진정한" 국제 원자 시간(TAI)이 아닙니다. 실제로 이는 애플리케이션 코드에서 거의 문제가 되지 않습니다.

Q: Unix 타임스탬프로 표현할 수 있는 최대 날짜는 무엇입니까?
A: 64비트 부호 있는 정수를 사용하면 최대값은 약 292,277,026,596년에 해당합니다. 32비트 부호 있는 정수의 경우 2038년 1월 19일 03:14:07 UTC가 최대값입니다.

Q: 브라우저에서 현재 Unix 타임스탬프를 어떻게 얻을 수 있습니까?
A: 브라우저 콘솔에서 Math.floor(Date.now() / 1000)을 실행하면 현재 초 단위의 Unix 타임스탬프가 반환됩니다.

Q: 타임스탬프를 변환하면 1970-01-01이 표시되는 이유는 무엇입니까?
A: 초를 기대하는 함수에 밀리초를 전달하고 있을 가능성이 높습니다(또는 그 반대). 먼 미래의 날짜가 표시되면 1000으로 나누고, 1970년 1월 1일이 표시되면 1000을 곱하세요.