이 글은 Intl.DateTimeFormat에 대한 내용이 아닙니다. 본 가이드에서 다루는 JavaScript Temporal API는 기존 Date 객체의 근본적인 한계를 해결하기 위해 TC39에서 Stage 3까지 진행된 차세대 날짜/시간 표준입니다. 타임존, 불변성, 나노초 정밀도, 다중 캘린더를 네이티브로 지원하며, 2025년부터 주요 브라우저에 순차적으로 탑재되고 있습니다.

기존 Date의 문제점

JavaScript의 Date 객체는 1995년 Java의 java.util.Date를 10일 만에 복제한 결과물입니다. 주요 문제점은 다음과 같습니다.

  • 뮤터블(Mutable) - setMonth() 등 메서드가 원본 객체를 직접 변경
  • 타임존 미지원 - UTC와 로컬 타임존만 다루며, IANA 타임존 변환 불가
  • 밀리초 정밀도 - 마이크로초/나노초 단위 연산 불가능
  • 달력 시스템 - 그레고리력만 지원, 히브리/이슬람/일본력 등 미지원
  • 월 인덱스 0 시작 - month가 0~11로 직관적이지 않음

Temporal API 핵심 클래스

Temporal은 용도별로 분리된 7개 핵심 클래스를 제공합니다.

클래스용도예시
Temporal.InstantUTC 기준 절대 시점 (나노초)API 타임스탬프
Temporal.ZonedDateTime타임존 + 캘린더 포함 시점회의 일정 관리
Temporal.PlainDateTime타임존 없는 날짜+시간알람 설정
Temporal.PlainDate날짜만 (연/월/일)생년월일
Temporal.PlainTime시간만 (시/분/초)영업 시간
Temporal.Duration시간 간격타이머, 경과 시간
Temporal.Now현재 시각 조회현재 날짜/시간

현재 시각 조회 - Temporal.Now

// 현재 UTC 시점
const instant = Temporal.Now.instant();
console.log(instant.toString());
// "2026-04-15T02:11:00Z"

// 현재 로컬 날짜
const today = Temporal.Now.plainDateISO();
console.log(today.toString());
// "2026-04-15"

// 타임존 포함 현재 시각
const now = Temporal.Now.zonedDateTimeISO();
console.log(now.toString());
// "2026-04-15T11:11:00+09:00[Asia/Seoul]"

날짜 생성과 조작

// PlainDate 생성
const date = Temporal.PlainDate.from('2026-04-15');
const date2 = Temporal.PlainDate.from({ year: 2026, month: 4, day: 15 });

// 불변 연산 - 원본은 변하지 않음
const nextMonth = date.add({ months: 1 });
console.log(nextMonth.toString()); // "2026-05-15"
console.log(date.toString());      // "2026-04-15" (원본 유지)

// 특정 필드만 변경
const modified = date.with({ month: 12, day: 25 });
console.log(modified.toString()); // "2026-12-25"

// 날짜 차이 계산
const start = Temporal.PlainDate.from('2026-01-01');
const end = Temporal.PlainDate.from('2026-12-31');
const diff = end.since(start);
console.log(diff.toString());              // "P365D"
console.log(diff.total({ unit: 'days' })); // 365

타임존 변환 - ZonedDateTime

Temporal의 가장 강력한 기능입니다. IANA 타임존 데이터베이스를 네이티브로 지원합니다.

// 서울 시간 생성
const seoul = Temporal.ZonedDateTime.from(
  '2026-04-15T14:30:00+09:00[Asia/Seoul]'
);

// 뉴욕 시간으로 변환 (자동 DST 처리)
const newYork = seoul.withTimeZone('America/New_York');
console.log(newYork.toString());
// "2026-04-15T01:30:00-04:00[America/New_York]"

// 런던 시간으로 변환
const london = seoul.withTimeZone('Europe/London');
console.log(london.toString());
// "2026-04-15T06:30:00+01:00[Europe/London]"

// DST 전환 감지
console.log(newYork.hoursInDay); // 24 (일반 날)
// DST 전환일에는 23 또는 25 반환

Duration 활용 - 시간 간격 연산

// Duration 생성
const meeting = Temporal.Duration.from({ hours: 1, minutes: 30 });
const breakTime = Temporal.Duration.from({ minutes: 15 });

// 연산
const total = meeting.add(breakTime);
console.log(total.toString()); // "PT1H45M"
console.log(total.total({ unit: 'minutes' })); // 105

// 두 시점 사이 간격
const eventStart = Temporal.PlainDateTime.from('2026-04-15T09:00');
const eventEnd = Temporal.PlainDateTime.from('2026-04-15T17:30');
const workHours = eventEnd.since(eventStart);
console.log(workHours.toString()); // "PT8H30M"

// 반올림
const rounded = workHours.round({ smallestUnit: 'hour' });
console.log(rounded.toString()); // "PT9H"

정렬과 비교

// 날짜 비교
const dates = [
  Temporal.PlainDate.from('2026-12-25'),
  Temporal.PlainDate.from('2026-01-01'),
  Temporal.PlainDate.from('2026-07-04'),
];

// 정렬 (compare 함수 활용)
dates.sort(Temporal.PlainDate.compare);
console.log(dates.map(d => d.toString()));
// ["2026-01-01", "2026-07-04", "2026-12-25"]

// 동등 비교
const a = Temporal.PlainDate.from('2026-04-15');
const b = Temporal.PlainDate.from('2026-04-15');
console.log(a.equals(b)); // true

다중 캘린더 시스템

// 그레고리력 → 일본력 변환
const gregorian = Temporal.PlainDate.from('2026-04-15');
const japanese = gregorian.withCalendar('japanese');
console.log(japanese.toLocaleString('ja-JP-u-ca-japanese'));
// "令和8年4月15日"

// 히브리력
const hebrew = gregorian.withCalendar('hebrew');
console.log(hebrew.year, hebrew.month, hebrew.day);

실전 예제: D-Day 카운터

function getDDay(targetDate) {
  const today = Temporal.Now.plainDateISO();
  const target = Temporal.PlainDate.from(targetDate);
  const diff = today.until(target);
  const days = diff.total({ unit: 'days' });

  if (days > 0) return 'D-' + days;
  if (days === 0) return 'D-Day';
  return 'D+' + Math.abs(days);
}

console.log(getDDay('2026-12-25')); // "D-254"

실전 예제: 글로벌 회의 스케줄러

function scheduleGlobalMeeting(baseTime, timeZones) {
  const base = Temporal.ZonedDateTime.from(baseTime);

  return timeZones.map(tz => ({
    timezone: tz,
    localTime: base.withTimeZone(tz)
      .toLocaleString('en-US', {
        weekday: 'short',
        hour: '2-digit',
        minute: '2-digit',
        timeZoneName: 'short'
      })
  }));
}

const schedule = scheduleGlobalMeeting(
  '2026-04-15T15:00:00+09:00[Asia/Seoul]',
  ['Asia/Seoul', 'America/New_York', 'Europe/London', 'Asia/Tokyo']
);
// [
//   { timezone: 'Asia/Seoul',      localTime: 'Wed, 03:00 PM KST' },
//   { timezone: 'America/New_York', localTime: 'Wed, 02:00 AM EDT' },
//   { timezone: 'Europe/London',    localTime: 'Wed, 07:00 AM BST' },
//   { timezone: 'Asia/Tokyo',       localTime: 'Wed, 03:00 PM JST' }
// ]

Date vs Temporal 비교 정리

항목DateTemporal
불변성뮤터블 (setMonth 등)완전 불변
타임존UTC/로컬만IANA 전체 지원
정밀도밀리초나노초
캘린더그레고리력만다중 캘린더
월 인덱스0-111-12
파싱브라우저마다 다름RFC 9557 표준
DST 처리수동자동

브라우저 지원 현황 (2026년 4월 기준)

Chrome 129+, Edge 129+에서 플래그 없이 사용 가능하며, Firefox와 Safari도 구현 진행 중입니다. 프로덕션에서 사용하려면 폴리필(@js-temporal/polyfill 또는 temporal-polyfill)을 함께 적용하는 것을 권장합니다.

// 폴리필 설치
npm install @js-temporal/polyfill

// 사용
import { Temporal } from '@js-temporal/polyfill';
const now = Temporal.Now.plainDateISO();

Temporal API는 JavaScript 날짜/시간 처리의 패러다임을 바꾸는 표준입니다. moment.js나 date-fns 같은 라이브러리 없이도 타임존 변환, DST 처리, 나노초 정밀도 연산이 가능해집니다. 지금부터 폴리필과 함께 프로젝트에 적용해 보시기 바랍니다.