3줄 요약
1. Temporal API가 TC39 Stage 4에 도달하며 ES2026 표준에 정식 포함된다. ECMAScript 역사상 가장 큰 단일 추가(약 4,500개 테스트)다.
2. 30년간 개발자를 괴롭혀 온 Date 객체의 가변성, 0-based 월, 타임존 미지원 문제를 근본적으로 해결한다.
3. Chrome 144, Firefox 139, Edge 144에서 이미 사용 가능하며,Temporal.PlainDate,Temporal.ZonedDateTime등 용도별 8개 타입을 제공한다.
결론부터 말하면
JavaScript의 Date 객체는 1995년 Java에서 급하게 포팅된 이후 30년간 변하지 않았다. Temporal API는 불변 객체, IANA 타임존 네이티브 지원, 나노초 정밀도, 용도별 분리된 타입을 제공하며 이 문제를 완전히 해결한다. 2026년 3월 Stage 4에 도달했고, 주요 브라우저에서 이미 지원 중이다.
Temporal API란?
Temporal은 JavaScript의 Date 객체를 대체하기 위해 설계된 새로운 날짜/시간 API다. TC39(ECMAScript 표준 위원회)에서 2017년 Stage 1으로 시작해 9년간 개발되었으며, 2026년 3월 Stage 4에 도달하면서 ES2026 표준에 포함이 확정됐다.
Date 객체 하나로 모든 날짜/시간 개념을 처리하던 기존 방식 대신, Temporal은 상황에 맞는 8개의 독립된 타입을 제공한다. 모든 객체는 불변(immutable)이며, 시간대 처리가 내장되어 있다.
Bloomberg, Google, Mozilla, Microsoft, Igalia 등이 공동으로 개발에 참여했으며, Test262 기준 약 4,500개의 테스트가 추가되어 ECMAScript 역사상 가장 큰 규모의 기능 추가다.
기존 Date 객체의 문제점
Date가 왜 교체 대상이 되었는지 구체적으로 살펴보자.
가변성(Mutability)
const meeting = new Date(2026, 2, 15, 14, 0); // 3월 15일
scheduleMeeting(meeting);
// meeting 객체가 함수 내부에서 변경되었을 수 있다
console.log(meeting); // 예측 불가
Date 객체는 setMonth(), setDate() 등으로 원본이 직접 변경된다. 여러 곳에서 같은 객체를 참조하면 의도치 않은 버그가 발생한다.
0-based 월 인덱싱
new Date(2026, 0, 1); // 1월 1일 (0 = 1월)
new Date(2026, 11, 1); // 12월 1일 (11 = 12월)
1월이 0이고 12월이 11이다. 이 설계는 off-by-one 에러의 대표적 원인이다.
브라우저별 파싱 차이
new Date("2026-01-01"); // 어떤 브라우저는 UTC로, 다른 브라우저는 로컬 시간으로 해석
같은 문자열이 환경에 따라 다른 결과를 반환한다. 날짜 문자열 파싱 규칙이 명확하지 않기 때문이다.
타임존 지원 부재
const date = new Date();
date.getTimezoneOffset(); // 오프셋만 반환, IANA 타임존 이름은 없음
Date는 타임존이 아닌 오프셋만 지원한다. “Asia/Seoul”이나 “America/New_York” 같은 IANA 타임존을 다룰 방법이 없다. DST(서머타임) 전환도 제대로 처리하지 못한다.
날짜 전용 타입 없음
생일처럼 시간 정보가 불필요한 경우에도 Date는 항상 시간과 타임존 정보를 포함한다. “3월 15일”이라는 단순한 날짜를 표현할 깔끔한 방법이 없다.
Temporal의 8가지 타입
Temporal은 용도에 따라 정확한 타입을 선택해서 사용한다.
| 타입 | 설명 | 사용 예시 |
|---|---|---|
PlainDate | 날짜만 (시간 없음) | 생일, 공휴일 |
PlainTime | 시간만 (날짜 없음) | 알람, 영업시간 |
PlainDateTime | 날짜 + 시간 (타임존 없음) | 로컬 이벤트 |
ZonedDateTime | 날짜 + 시간 + 타임존 | 글로벌 서비스, 항공편 |
Instant | UTC 절대 시점 | 서버 로그, 타임스탬프 |
Duration | 시간 간격 | 경과 시간, 타이머 |
PlainYearMonth | 연월 | 월별 청구, 리포트 |
PlainMonthDay | 월일 | 기념일, 매년 반복 이벤트 |
핵심 코드 예시
PlainDate — 시간대 없는 날짜
// 생성
const birthday = Temporal.PlainDate.from("2026-03-15");
const today = Temporal.Now.plainDateISO();
// 불변 연산 — 원본은 변하지 않는다
const nextWeek = today.add({ days: 7 });
console.log(today.toString()); // 원본 그대로
console.log(nextWeek.toString()); // 7일 후
// 두 날짜 사이의 간격
const diff = today.until(birthday);
console.log(diff.days); // 남은 일수
ZonedDateTime — 타임존 포함 날짜/시간
// 서울과 뉴욕의 같은 '순간'
const seoul = Temporal.ZonedDateTime.from(
"2026-03-15T09:00:00+09:00[Asia/Seoul]"
);
const newYork = seoul.withTimeZone("America/New_York");
console.log(seoul.toString()); // "2026-03-15T09:00:00+09:00[Asia/Seoul]"
console.log(newYork.toString()); // "2026-03-14T19:00:00-05:00[America/New_York]"
// DST 자동 처리
const london = Temporal.ZonedDateTime.from(
"2026-03-29T00:30:00+00:00[Europe/London]"
);
const plus1h = london.add({ hours: 1 }); // "2026-03-29T02:30:00+01:00[Europe/London]"
// 01:30은 DST 전환으로 존재하지 않으므로 자동 건너뜀
Instant — 절대 시점
// 서버 이벤트 기록
const eventTime = Temporal.Now.instant();
console.log(eventTime.epochMilliseconds); // Unix 타임스탬프 (ms)
// 특정 시점을 각 시간대로 변환
const instant = Temporal.Instant.from("2026-03-15T00:00:00Z");
const inSeoul = instant.toZonedDateTimeISO("Asia/Seoul");
const inTokyo = instant.toZonedDateTimeISO("Asia/Tokyo");
console.log(inSeoul.hour); // 9 (UTC+9)
console.log(inTokyo.hour); // 9 (UTC+9)
Duration — 시간 간격 계산
const projectStart = Temporal.PlainDate.from("2026-01-10");
const deadline = Temporal.PlainDate.from("2026-03-31");
const remaining = projectStart.until(deadline);
console.log(remaining.toString()); // "P80D" (80일)
// Duration을 다른 단위로 변환
const duration = Temporal.Duration.from({ hours: 130, minutes: 20 });
console.log(duration.total({ unit: "second" })); // 469200
Date vs Temporal 비교
| 항목 | Date | Temporal |
|---|---|---|
| 불변성 | 가변 (원본 변경됨) | 불변 (새 객체 반환) |
| 월 인덱스 | 0부터 시작 (0 = 1월) | 1부터 시작 (1 = 1월) |
| 타임존 | 오프셋만 지원 | IANA 타임존 네이티브 지원 |
| 정밀도 | 밀리초 | 나노초 |
| 타입 | 단일 타입 | 용도별 8개 타입 |
| DST 처리 | 수동 | 자동 |
| 달력 | 그레고리력만 | 히브리력, 이슬람력 등 지원 |
| 파싱 | 브라우저마다 다름 | 엄격하고 일관된 규칙 |
브라우저 지원 현황 (2026년 3월 기준)
| 브라우저 | 버전 | 지원 상태 |
|---|---|---|
| Firefox | 139+ | 지원 (2025년 5월~) |
| Chrome | 144+ | 지원 (2026년 1월~) |
| Edge | 144+ | 지원 (2026년 1월~) |
| Safari | Technology Preview | 부분 지원 (플래그 필요) |
| Node.js | v26 | 예정 |
| TypeScript | 6.0 Beta | 타입 정의 지원 (2026년 2월~) |
Safari를 제외한 주요 브라우저에서 이미 사용 가능하다. Safari도 Technology Preview에서 테스트할 수 있으며, 정식 지원은 2026년 내 예상된다.
프로덕션 환경에서 모든 브라우저를 지원해야 한다면 @js-temporal/polyfill 또는 temporal-polyfill을 사용할 수 있다.
npm install @js-temporal/polyfill
import { Temporal } from "@js-temporal/polyfill";
const now = Temporal.Now.plainDateISO();
console.log(now.toString()); // 폴리필로 모든 환경에서 동일하게 동작
기존 코드 마이그레이션 가이드
Date에서 Temporal로 전환할 때 자주 쓰는 패턴을 정리했다.
// (Before) Date — 현재 날짜
const today = new Date();
const year = today.getFullYear();
const month = today.getMonth() + 1; // 0-based 보정 필요
// (After) Temporal — 현재 날짜
const today = Temporal.Now.plainDateISO();
const year = today.year;
const month = today.month; // 1-based, 보정 불필요
// (Before) Date — 날짜 비교
const d1 = new Date("2026-03-15");
const d2 = new Date("2026-04-01");
const diffMs = d2 - d1;
const diffDays = Math.floor(diffMs / (1000 60 60 * 24)); // 직접 계산
// (After) Temporal — 날짜 비교
const d1 = Temporal.PlainDate.from("2026-03-15");
const d2 = Temporal.PlainDate.from("2026-04-01");
const diff = d1.until(d2);
console.log(diff.days); // 17 — 내장 메서드로 바로 계산
// (Before) Date — 타임존 변환
// 외부 라이브러리(moment-timezone, date-fns-tz) 필수
// (After) Temporal — 타임존 변환
// 외부 라이브러리 없이 네이티브로 처리
const seoul = Temporal.Now.zonedDateTimeISO("Asia/Seoul");
const london = seoul.withTimeZone("Europe/London");
Moment.js, date-fns는 이제 필요 없나?
State of JS 조사에서 날짜 처리는 “정적 타입 부족” 다음으로 두 번째 고통 포인트로 꼽혔다. 이 문제를 해결하기 위해 Moment.js(2011), date-fns, Luxon 같은 라이브러리가 등장했고, 현재 npm에서 주당 8천만 다운로드를 기록하고 있다.
Temporal이 표준화되면서 이 라이브러리들의 핵심 기능(타임존 처리, 불변 연산, 포매팅)이 네이티브로 대체된다. 다만 Moment.js의 상대 시간 표현(3일 전)이나 date-fns의 풍부한 유틸리티 함수처럼 Temporal이 직접 제공하지 않는 편의 기능도 있다.
당장 기존 프로젝트의 라이브러리를 걷어낼 필요는 없지만, 새 프로젝트에서는 Temporal 네이티브로 시작하는 것이 번들 크기와 유지보수 측면에서 유리하다.
9년의 여정 — 주요 마일스톤
| 시기 | 사건 |
|---|---|
| 1995년 | Brendan Eich가 Java의 java.util.Date를 JavaScript로 포팅 |
| 2011년 | Moment.js 등장, Date의 한계를 라이브러리로 우회하기 시작 |
| 2017년 | Maggie Johnson-Pint(Moment.js 관리자)가 TC39에 Temporal 제안, Stage 1 |
| 2021년 | Stage 2.7 — 설계 확정 |
| 2023년 | Stage 3 — 브라우저 구현 시작 |
| 2024년 | temporal_rs(Rust 기반 공유 엔진) 개발, Google + Boa 협업 |
| 2025년 5월 | Firefox 139, 최초로 Temporal 정식 지원 |
| 2026년 1월 | Chrome 144, Edge 144 지원 |
| 2026년 3월 | TC39 Stage 4 도달 — ES2026 표준 확정 |
Bloomberg의 금융 시스템에서 나노초 단위의 시간 정밀도와 IANA 타임존 지원이 필요했던 것이 Temporal 개발의 핵심 동력이었다. Bloomberg 엔지니어 Philipp Dunkel이 스펙 챔피언을, Jason Williams가 TC39 위원으로 표준화를 이끌었다.
참고 자료
- Temporal: The 9-Year Journey to Fix Time in JavaScript — Bloomberg Engineering
- Temporal — MDN Web Docs
- TC39 Proposal: Temporal
- Temporal API — Can I Use
- ES2026 Solves JavaScript Headaches — The New Stack