- Published on
Javascript의 새로운 Temporal API 살펴보기
- Authors
- Name
- 윤영서
JavaScript 개발자라면 누구나 Date 객체를 다루면서 어려움을 겪었던 적이 있을 것입니다. 가변성 문제, 신뢰할 수 없는 파싱, 제한적인 시간대 지원 등 여러 문제들이 있었기 때문입니다.
Date 객체의 이러한 문제는 그 탄생 배경에서부터 시작됩니다. 1995년, Brendan Eich는 단 10일 만에 JavaScript를 만들어 Netscape에 탑재해야 했습니다. 거의 모든 프로그래밍 언어에서 날짜 처리는 기본적인 부분이었고, JavaScript도 이를 구현해야 했습니다. 시간이 부족했던 Brendan은 "Java처럼 만들라"는 지시에 따라 당시 초기 단계였던 java.util.Date
를 그대로 가져왔고, 아이러니하게도 이 API는 Java에서조차 1997년에 이미 문제가 많다고 판단되어 개선된 API로 대체되었습니다.
하지만 JavaScript의 Date는 ECMAScript의 어떤 변경사항도 기존 동작과 호환되어야 한다는 웹 호환성(Web Compatibility)
과 현재 코드가 특정 방식으로 동작한다면, 명세에 없더라도 그대로 유지해야 한다는 웹 현실(Web Reality)
이라는 두 가지 중요한 제약 때문에 쉽게 변경할 수 없었습니다. JavaScript가 세계에서 가장 많이 사용되는 프로그래밍 언어가 되면서, 1998년의 오래된 코드들도 여전히 브라우저에서 실행되고 있었고, 이미 많은 코드들이 Date 객체의 현재 동작에 의존하고 있었기 때문입니다.
결국 TC39(JavaScript 표준화 위원회)
는 Date 객체를 직접 수정하는 대신, 새로운 날짜/시간 API인 Temporal
을 제안하게 됩니다. 이제 Temporal API
를 통해 어떻게 이러한 문제들을 해결할 수 있는지 살펴보겠습니다.
기존 Date 객체의 문제점
기존 Date 객체와 관련된 ECMA Script 문서를 살펴보면 시간 값에 대해 다음과 같이 서술하고 있습니다.
time value is a Number, either a finite integral Number representing an instant in time to millisecond precision or NaN representing no specific instant.
즉, JavaScript에서 날짜와 시간은 단순히 숫자에 불과합니다. 1970년 1월 1일 00:00:00 UTC(UNIX Epoch)로부터 경과된 밀리초를 나타내는 숫자이거나, 유효하지 않은 날짜를 나타내는 NaN일 뿐입니다.
이러한 설계는 다음과 같은 문제들을 야기합니다.
1. 의미론적 문제
예를 들어 다음과 같은 날짜가 있다고 생각해보겠습니다.
const appointment = new Date('2025-02-15T10:30:00');
console.log(date.getTime()); // 1739583000000
달력에 약속을 표기할때, 2025년 2월 15일 오전 10시 30분
에 약속이 있음을 표기하고 싶었습니다. 그러나 JavaScript에서는 이를 1739583000000
이라는 숫자로 변환해 사용합니다. 이 숫자만 확인했을때는, 이 시간이 어떤 시간대를 기준으로 하는 것인지, 원래 입력된 시간이 현지 시간 기준인지 UTC 기준인지 등에 대한 맥락이 손실됩니다.
2. 암묵적인 변환의 위험성
위와 같은 맥락에서, 하나의 타임스탬프는 여러 시간으로 해석될 수 있습니다.
동일한 순간을 여러 시간대에서 보면
// 하나의 타임스탬프
const timestamp = 1739583000000; // 특정 시점
// 여러 시간대에서 이 시점을 보면
console.log(new Date(timestamp).toLocaleString('ko-KR', { timeZone: 'Asia/Seoul' }));
// "2025. 2. 15. 오후 2:00:00"
console.log(new Date(timestamp).toLocaleString('en-US', { timeZone: 'America/New_York' }));
// "2025. 2. 15. 오전 12:00:00"
console.log(new Date(timestamp).toLocaleString('en-GB', { timeZone: 'Europe/London' }));
// "2025. 2. 15. 오전 5:00:00"
으로 같은 타임스탬프에 여러 시간대가 대응될 수 있습니다.
다음과 같은 상황이 있다고 가정해보겠습니다.
거래 시각이 UTC 기준 2025-02-15T05:00:00Z라고 해보겠습니다. 한국에 있는 사용자가 2월 15일 오후 2시에 쿠폰을 사용하려 합니다. 이 시각 기준으로 뉴욕에서는 2월 15일 자정인 오늘
거래한 셈이 됩니다. 또 LA에서는 2월 14일 오후 9시, 어제
거래한 셈이 됩니다.
이런 상황에서 '오늘의 거래 내역'을 보여주려면 어떤 시간대 기준으로 해야할까요? '일일 한도'는 어느 시간대의 '하루'를 기준으로 계산해야 할까요?
이처럼 단순히 UTC나 ISO 문자열로 시간을 저장하는 것만으로는, 시간이 가진 비즈니스적/현실적 의미를 완전히 담아내기 어렵습니다.
새로운 Temporal API
JavaScript의 새로운 Temporal API
는 이러한 문제들을 해결하기 위해 설계되었습니다. 주요 특징들을 살펴보겠습니다.
1. 불변성
Temporal API
의 모든 객체는 불변(immutable)
입니다. 기존 Date 객체는 가변(mutable)
이어서 예기치 않은 부작용이 발생할 수 있었습니다.
// 기존 Date 객체
const date = new Date('2025-02-15T10:30:00');
date.setHours(12); // 기존 객체 변경
console.log(date); // 2025-02-16T03:57:48.402Z
// Temporal API
const now = Temporal.PlainDate.from('2025-02-15');
const nextDay = now.add({ days: 1 });
console.log(now.toString()); // 2025-02-15 - 원본 유지
console.log(nextDay.toString()); // 2025-02-16
2. 명확한 타입 구분과 직관적인 날짜 연산
Temporal API
는 용도에 따라 다양한 타입을 제공합니다.
// 날짜만 다루기
const date = Temporal.PlainDate.from('2025-02-15'); //2025-02-15
// 시간만 다루기
const time = Temporal.PlainTime.from('10:30:00'); // 10:30:00
// 날짜와 시간 함께 다루기
const dateTime = Temporal.PlainDateTime.from('2025-02-15T10:30:00'); // 2025-02-15T10:30:00
// 시간대가 있는 날짜/시간
const zonedDateTime = Temporal.ZonedDateTime.from('2025-02-15T10:30:00+09:00[Asia/Seoul]'); //2025-02-15T10:30:00+09:00[Asia/Seoul]
// 기간 표현
const duration = Temporal.Duration.from('P1Y2M3DT4H5M6S'); // P1Y2M3DT4H5M6S
특이한 점은, 기간도 표현이 가능하다는 것입니다. 이전까지 Date 객체는 시간 간격을 표현하기 위해서는 두 시점의 차이를 계산해야 했습니다.
const start = new Date('2025-01-01');
const end = new Date('2025-01-05');
const diff = end - start; // 345600000로 나옴
하지만 이런 방식은 단순히 밀리초 단위의 숫자만 반환하기 때문에, 실제로 '몇 년 몇 개월 몇 일'과 같은 의미 있는 기간을 표현하기 어려웠습니다. Temporal API
는 이러한 기간을 직접적으로 다룰 수 있게 해줍니다.
// Temporal에서의 기간 계산
const start = Temporal.PlainDate.from('2025-01-01');
const end = Temporal.PlainDate.from('2025-02-15');
// 월과 일로 표현
const duration1 = start.until(end, { largestUnit: 'month' });
console.log(duration1.toString()); // P1M14D
// 주와 일로 표현
const duration2 = start.until(end, { largestUnit: 'week' });
console.log(duration2.toString()); //P6W3D
Temporal API
는 기존 Date 객체에 존재하지 않았던 ISO 8601 형식의 Duration 지원을 통해 좀 더 명확한 기간 표현을 가능하게 합니다.
3. 강력한 시간대 지원
Temporal API
는 IANA 시간대 데이터베이스를 기반으로 한 포괄적인 시간대 지원을 제공합니다.
// 서울 시간
const seoulTime = Temporal.ZonedDateTime.from('2025-02-15T10:30:00+09:00[Asia/Seoul]');
// 뉴욕 시간으로 변환
const nyTime = seoulTime.withTimeZone('America/New_York');
console.log(nyTime.toString()); //2025-02-14T20:30:00-05:00[America/New_York]
또한 썸머타임(DST)을 포함한 시간대 변환도 지원합니다. DST가 적용되는 시간대에서는 일 년에 두 번의 시간 변화가 발생하는데, 이런 복잡한 상황을 Temporal이 자동으로 관리해줍니다.
// 뉴욕의 DST 시작 전후 시간 비교 (2025년 3월 9일 새벽 2시에 DST 시작)
const beforeDST = Temporal.ZonedDateTime.from('2025-03-09T01:30:00-05:00[America/New_York]');
const afterDST = beforeDST.add({ hours: 1 });
console.log(beforeDST.toString()); // 2025-03-09T01:30:00-05:00[America/New_York]
console.log(afterDST.toString()); // 2025-03-09T03:30:00-04:00[America/New_York]
// 2시가 아닌 3시로 변경됨 (DST로 인해 2시가 스킵됨)
// DST 종료 시점의 처리 (2025년 11월 2일 새벽 2시에 DST 종료)
const beforeDSTEnd = Temporal.ZonedDateTime.from('2025-11-02T01:30:00-04:00[America/New_York]');
const afterDSTEnd = beforeDSTEnd.add({ hours: 1 });
console.log(beforeDSTEnd.toString()); // 2025-11-02T01:30:00-04:00[America/New_York]
console.log(afterDSTEnd.toString()); // 2025-11-02T01:30:00-05:00[America/New_York]
// 같은 시각이지만 UTC 오프셋이 변경됨
Date 객체에서 Temporal API로 전환하기
기존 Date 객체에서 Temporal API로 전환하는 방법은 다음과 같습니다.
1. 타임스탬프로만 사용중이었다면
// 기존 코드
const date = new Date('2025-02-15T10:00:00Z');
const timestamp = date.getTime();
// Temporal로 변환
const instant = Temporal.Instant.fromEpochMilliseconds(timestamp);
2. 타임존을 저장하고 있었다면
const date = new Date('2025-02-15T10:00:00');
console.log(date.getHours()); // 시스템 시간대 기준
// Temporal로 변환
const localDateTime = date
.toTemporalInstant()
.toZonedDateTimeISO(Temporal.Now.timeZoneId());
3. 날짜만 중요하고 시간은 중요하지 않았다면
// 기존 코드
const date = new Date('2025-02-15');
// Temporal로 변환
const plainDate = Temporal.PlainDate.from({
year: date.getFullYear(),
month: date.getMonth() + 1,
day: date.getDate()
});
4. 특정 시간대의 시간이 중요했다면
// 기존 코드
const seoulDate = new Date('2025-02-15T10:00:00+09:00');
// Temporal로 변환
const zonedDateTime = Temporal.ZonedDateTime.from(
'2025-02-15T10:00:00+09:00[Asia/Seoul]'
);
브라우저 지원 현황
Temporal API
는 아직 Stage 3이기 때문에 브라우저에서 지원하고 있는 상황은 아닙니다.
만약 간단하게 사용해보고 싶다면 사용하고 싶다면 Cookbook 페이지의 브라우저 콘솔에서 사용해볼 수 있습니다.(이 글의 테스트도 Cookbook에서 진행되었습니다.)
또한 개발 환경인 경우에는 폴리필을 사용해볼 수 있습니다. 그러나 이것도 production에서는 권장하지 않는 방법이므로 주의해서 사용해야합니다.
다루기 어려운 Date 객체로 인해 많은 JavaScript 개발자들이 moment.js나 dayjs와 같은 외부 라이브러리를 사용해왔습니다. 하지만 이러한 라이브러리들은 번들 크기를 증가시키고, 전역 객체를 수정하는 등의 부작용을 가지고 있었습니다. Temporal API
는 이러한 문제들을 근본적으로 해결하면서도, 현대적이고 강력한 기능들을 제공합니다.
앞으로 Temporal API가 정식으로 도입되면, 더 이상 외부 라이브러리에 의존하지 않고도 안전하고 직관적인 날짜/시간 처리가 가능해질 것입니다. 특히 글로벌 서비스에서 발생하는 시간대 관련 문제들을 더욱 명확하게 해결할 수 있게 될 것으로 기대됩니다.
✏️ 출처
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal
https://docs.timetime.in/blog/js-dates-finally-fixed/
https://blogs.igalia.com/compilers/2020/06/23/dates-and-times-in-javascript/
https://developer.mozilla.org/en-US/blog/javascript-temporal-is-coming/?ck_subscriber_id=1780635791#core_concepts
https://maggiepint.com/2017/04/11/fixing-javascript-date-web-compatibility-and-reality/
https://www.digi.com/resources/documentation/digidocs/90001488-13/reference/r_iso_8601_duration_format.htm
https://www.iana.org/time-zones