JavaScript 개발자를 오랫동안 괴롭혀온 Date 객체의 시대가 드디어 저물고 있습니다. ES2026에서 정식 도입되는 Temporal API는 불변 객체, 타임존 퍼스트 클래스 지원, 비그레고리력 캘린더 등 현대적인 날짜/시간 처리의 모든 것을 제공합니다. moment.js나 date-fns 같은 라이브러리 없이도 복잡한 날짜 연산을 정확하게 수행할 수 있습니다.

Date 객체의 한계

기존 Date 객체는 1995년 Java의 java.util.Date를 그대로 복사한 것으로, 태생적 결함이 많습니다.

// Date 객체의 문제점들
const date = new Date(2026, 0, 1); // 월이 0부터 시작 (0 = 1월)
date.setMonth(12); // 13월? -> 다음 해 1월로 자동 변환

// 뮤터블 - 원본이 변경됨
const original = new Date('2026-04-04');
const modified = original;
modified.setDate(10);
console.log(original.getDate()); // 10 - 원본도 변경!

// 타임존 처리 불가
// UTC나 로컬 타임존만 지원, 임의 타임존 변환 불가능

Temporal API 핵심 타입

Temporal은 용도에 따라 명확하게 구분된 여러 타입을 제공합니다.

Temporal.PlainDate - 날짜만

// 날짜만 다루기 (시간 정보 없음)
const today = Temporal.PlainDate.from('2026-04-04');
const birthday = Temporal.PlainDate.from({ year: 1990, month: 3, day: 15 });

// 불변 - 항상 새 객체 반환
const nextWeek = today.add({ days: 7 });
console.log(today.toString());     // 2026-04-04 (원본 유지)
console.log(nextWeek.toString());  // 2026-04-11

// 날짜 비교
console.log(Temporal.PlainDate.compare(today, nextWeek)); // -1
console.log(today.equals(nextWeek)); // false

// 유용한 속성들
console.log(today.dayOfWeek);    // 6 (토요일)
console.log(today.daysInMonth);  // 30 (4월)
console.log(today.daysInYear);   // 365
console.log(today.weekOfYear);   // 14

Temporal.PlainTime - 시간만

const lunchTime = Temporal.PlainTime.from('12:30:00');
const meetingEnd = lunchTime.add({ hours: 1, minutes: 30 });
console.log(meetingEnd.toString()); // 14:00:00

// 나노초 정밀도 지원
const precise = Temporal.PlainTime.from('10:30:00.123456789');
console.log(precise.nanosecond); // 789

Temporal.PlainDateTime - 날짜 + 시간 (타임존 없음)

const appointment = Temporal.PlainDateTime.from('2026-04-04T14:30:00');
const rescheduled = appointment.add({ hours: 2, days: 1 });
console.log(rescheduled.toString()); // 2026-04-05T16:30:00

Temporal.ZonedDateTime - 타임존 포함 완전한 시간

// 타임존 지정 - IANA 타임존 이름 사용
const seoulMeeting = Temporal.ZonedDateTime.from({
  year: 2026, month: 4, day: 4,
  hour: 14, minute: 0,
  timeZone: 'Asia/Seoul'
});

// 다른 타임존으로 변환
const nyTime = seoulMeeting.withTimeZone('America/New_York');
console.log(nyTime.toString());
// 2026-04-04T01:00:00-04:00[America/New_York]

const londonTime = seoulMeeting.withTimeZone('Europe/London');
console.log(londonTime.toString());
// 2026-04-04T06:00:00+01:00[Europe/London]

// DST(서머타임) 자동 처리
const beforeDST = Temporal.ZonedDateTime.from(
  '2026-03-08T01:30:00[America/New_York]'
);
const afterAdd = beforeDST.add({ hours: 2 });
// 서머타임 전환을 자동으로 처리

기간과 차이 계산

// Temporal.Duration - 기간 표현
const projectDuration = Temporal.Duration.from({
  months: 3, weeks: 2, days: 5
});
console.log(projectDuration.total({ unit: 'days' }));

// 두 날짜 사이의 차이
const start = Temporal.PlainDate.from('2026-01-15');
const end = Temporal.PlainDate.from('2026-04-04');

const diff = start.until(end);
console.log(diff.toString()); // P2M17D (2개월 17일)
console.log(diff.months);     // 2
console.log(diff.days);       // 17

// 가장 큰 단위 지정
const diffInDays = start.until(end, { largestUnit: 'day' });
console.log(diffInDays.days); // 79

// since는 반대 방향
const ago = end.since(start);
console.log(ago.toString()); // P2M17D

실전: D-Day 카운터 구현

function getDDay(targetDateStr) {
  const today = Temporal.Now.plainDateISO();
  const target = Temporal.PlainDate.from(targetDateStr);
  const diff = today.until(target, { largestUnit: 'day' });

  if (diff.sign === -1) {
    return `D+${Math.abs(diff.days)}`;
  } else if (diff.days === 0) {
    return 'D-Day';
  } else {
    return `D-${diff.days}`;
  }
}

console.log(getDDay('2026-12-25')); // D-265
console.log(getDDay('2026-01-01')); // D+93

실전: 글로벌 미팅 스케줄러

function scheduleMeeting(dateTime, hostTimeZone, attendeeTimeZones) {
  const meeting = Temporal.ZonedDateTime.from({
    ...Temporal.PlainDateTime.from(dateTime),
    timeZone: hostTimeZone
  });

  const schedule = attendeeTimeZones.map(tz => {
    const local = meeting.withTimeZone(tz);
    const hour = local.hour;
    const isWorkHours = hour >= 9 && hour < 18;
    return {
      timezone: tz,
      localTime: local.toPlainDateTime().toString().slice(0, 16),
      isWorkHours
    };
  });

  return schedule;
}

const result = scheduleMeeting(
  '2026-04-04T10:00',
  'Asia/Seoul',
  ['America/New_York', 'Europe/London', 'Asia/Tokyo']
);
console.log(result);
// [
//   { timezone: 'America/New_York', localTime: '2026-04-03T21:00', isWorkHours: false },
//   { timezone: 'Europe/London', localTime: '2026-04-04T02:00', isWorkHours: false },
//   { timezone: 'Asia/Tokyo', localTime: '2026-04-04T10:00', isWorkHours: true }
// ]

폴리필 및 마이그레이션

브라우저 네이티브 지원이 완료되기 전에 @js-temporal/polyfill 패키지로 즉시 사용할 수 있습니다.

npm install @js-temporal/polyfill

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

Temporal API는 JavaScript의 가장 오래된 약점 중 하나였던 날짜/시간 처리를 완전히 새롭게 재설계한 것입니다. 불변성, 타입 안전성, 타임존 퍼스트 클래스 지원은 모든 JavaScript 개발자가 오래 기다려온 기능입니다. 새 프로젝트에서는 Temporal API를 기본으로 채택하는 것을 강력히 권장합니다.