Moment.js에서 JS Temporal API로의 전환
요약
JavaScript의 기존 Date API와 Moment.js의 한계를 극복하기 위해 등장한 새로운 표준인 Temporal API를 소개합니다. Moment.js는 가변성(mutability)과 큰 번들 크기 문제로 인해 유지 관리 모드로 전환되었으며, Temporal은 이를 해결하는 현대적인 내장 API로 자리 잡을 예정입니다.
핵심 포인트
- Moment.js는 가변 객체로 인한 부작용과 트리 쉐이킹 미지원으로 인한 번들 크기 증가 문제가 있음
- Temporal API는 ECMAScript Stage 4 단계에 도달한 차세대 시간 및 날짜 표준임
- Temporal은 특정 시점과 시간대 정보를 정교하게 다루며, 기존 Date API의 결함을 보완함
- 최신 브라우저(Chrome, Firefox 등)에서 지원이 시작되었으며 폴리필을 통해 사용 가능함
Date API에서 Moment.js를 거쳐 이제 Temporal로 넘어갑니다. 새로운 표준은 기존 Date API의 공백을 메우는 동시에, Moment 및 기타 라이브러리에서 발견된 한계점들을 해결합니다. Joe Attardi는 Moment 기반 코드를 새로운 Temporal API로 마이그레이션하기 위한 실질적인 "레시피(recipes)"를 공유합니다. JavaScript로 작성된 거의 모든 종류의 애플리케이션은 어떤 방식으로든 시간이나 날짜를 다룹니다. 초기에는 이것이 내장된 Date API에 국한되었습니다. 이 API는 기본적인 기능은 포함하고 있지만, 수행할 수 있는 작업에는 상당히 제한적입니다.
Moment.js와 같은 제3자 라이브러리, 그리고 이후에 등장한 Intl API 및 새로운 Temporal API와 같은 내장 API들은 시간과 날짜를 다루는 데 있어 훨씬 더 큰 유연성을 제공합니다.
Moment.js의 부상과 몰락
Moment.js는 시간과 날짜를 다루기 위한 강력한 유틸리티를 제공하는 JavaScript 라이브러리입니다. 시간대 조작(time zone manipulation)과 같이 기본 Date API에서 누락된 기능들을 포함하고 있으며, 많은 일반적인 작업들을 더 단순하게 만들어 줍니다. 또한 Moment는 날짜와 시간을 포맷팅(formatting)하는 함수들도 포함하고 있습니다. 이 라이브러리는 다양한 애플리케이션에서 널리 사용되는 라이브러리가 되었습니다.
하지만 Moment에게도 문제점은 있었습니다. 이는 규모가 큰 라이브러리이며, 애플리케이션의 번들 크기(bundle size)를 상당히 증가시킬 수 있습니다. 이 라이브러리는 트리 쉐이킹(tree shaking, 라이브러리의 사용되지 않는 부분을 제거할 수 있는 현대적 번들러의 기능)을 지원하지 않기 때문에, 단 한두 개의 함수만 사용하더라도 Moment 라이브러리 전체가 포함됩니다.
Moment의 또 다른 문제는 생성된 객체들이 *가변적(mutable)*이라는 사실입니다. Moment 객체에 특정 함수를 호출하면 부작용(side effects)이 발생하여 해당 객체의 값을 변하게(mutate) 합니다. 이는 예상치 못한 동작이나 버그로 이어질 수 있습니다.
2020년, Moment의 유지 관리자들은 이 라이브러리를 유지 관리 모드(maintenance mode)로 전환하기로 결정했습니다. 더 이상의 새로운 기능 개발은 이루어지지 않으며, 유지 관리자들은 새로운 프로젝트에 Moment를 사용하는 것을 권장하지 않습니다.
date-fns와 같은 다른 JavaScript 날짜 라이브러리들도 존재합니다.
하지만 JavaScript에 직접 내장된 새로운 API인 Temporal이 등장했습니다. 이는 기존 Date API의 빈틈을 메울 뿐만 아니라, Moment 및 기타 라이브러리에서 발견되는 몇 가지 한계점들을 해결하는 새로운 표준입니다.
Temporal이란 무엇인가?
Temporal은 현대 JavaScript를 정의하는 ECMAScript 표준에 추가되고 있는 새로운 시간 및 날짜 API입니다. 2026년 3월 기준으로, Temporal은 TC39 프로세스(JavaScript 언어의 제안 및 추가 사항을 감독하는 위원회)의 Stage 4 단계에 도달했으며, 차기 ECMAScript 명세에 포함될 예정입니다. 이미 여러 브라우저에 구현되어 있으며, Chrome 144+ 및 Firefox 139+에서 지원되고, Safari도 곧 뒤를 이을 것으로 예상됩니다. 지원되지 않는 브라우저와 Node.js를 위한 폴리필 (Polyfill)도 사용할 수 있습니다.
Temporal API는 일반적으로 특정 시점(moments in time)을 나타내는 객체를 생성합니다. 이 객체들은 특정 시간대 (Time zone) 내의 전체 날짜 및 시간 스탬프일 수도 있고, 시간대나 날짜 정보가 없는 일반적인 "벽시계 (wall clock)" 시간의 인스턴스일 수도 있습니다. Temporal의 주요 기능 중 일부는 다음과 같습니다:
날짜를 포함하거나 포함하지 않는 시간.
Temporal 객체는 특정 날짜의 특정 시간을 나타내거나, 날짜 정보가 없는 시간을 나타낼 수 있습니다. 시간 정보가 없는 특정 날짜 또한 표현할 수 있습니다.
시간대 (Time zone) 지원.
Temporal 객체는 시간대를 완전히 인식하며, 서로 다른 시간대 간의 변환이 가능합니다. Moment 역시 시간대를 지원하지만, 추가적인 moment-timezone 라이브러리가 필요합니다.
불변성 (Immutability).
Temporal 객체가 한 번 생성되면 변경할 수 없습니다. 시간 산술 연산이나 시간대 변환을 수행해도 기존 객체를 수정하지 않습니다. 대신, 새로운 Temporal 객체를 생성합니다.
1부터 시작하는 인덱싱 (1-based indexing).
Date API(및 Moment)에서 흔히 발생하는 버그의 원인 중 하나는 월 (month)이 0부터 시작하는 인덱스 (zero-indexed)라는 점입니다. 이는 우리가 일상생활에서 이해하는 방식인 1월이 1월이 아니라 0월임을 의미합니다. Temporal은 1부터 시작하는 인덱싱을 사용하여 이 문제를 해결합니다. 즉, 1월은 1월입니다.
브라우저에 내장됨.
Temporal은 브라우저 자체에 내장된 API이므로, 애플리케이션의 번들 크기 (bundle size)에 아무런 영향을 주지 않습니다.
또한 Date API가 사라지는 것은 아니라는 점도 유의해야 합니다. Temporal이 이 API를 대체하기는 하지만, Date API가 제거되거나 지원 중단 (deprecated)되는 것은 아닙니다. 브라우저가 갑자기 Date API를 제거한다면 수많은 애플리케이션이 작동을 멈출 것입니다. 하지만 Moment는 현재 유지 관리 모드 (maintenance mode)에 있는 레거시 (legacy) 프로젝트로 간주된다는 점도 명심하십시오.
이 글의 나머지 부분에서는 Moment 기반의 코드를 새로운 Temporal API로 마이그레이션하기 위한 몇 가지 "레시피 (recipes)"를 살펴보겠습니다. 리팩터링을 시작해 봅시다!
날짜 및 시간 객체 생성하기
날짜와 시간을 조작하기 전에, 이를 나타내는 객체를 먼저 생성해야 합니다. 현재 날짜와 시간을 나타내는 Moment 객체를 생성하려면 moment 함수를 사용합니다.
const now = moment();
console.log(now);
// Moment<2026-02-18T21:26:29-05:00>
이제 이 객체를 필요에 따라 포맷팅하거나 조작할 수 있습니다.
// UTC로 변환
// 경고: 이는 Moment 객체를 변형 (mutate)시키며 UTC 모드로 전환합니다!
console.log(now.utc());
...
Moment에 대해 기억해야 할 핵심 사항은 Moment 객체가 항상 시간 및 날짜에 대한 정보를 포함한다는 것입니다. 시간 정보만 다루고 싶다면 대개 문제가 없지만, 날짜가 시간 계산에 영향을 미칠 수 있는 일광 절약 시간제 (Daylight Saving Time)나 윤년 (leap years)과 같은 상황에서는 예상치 못한 동작을 유발할 수 있습니다.
Temporal은 더 유연합니다. Temporal.Instant 객체를 생성함으로써 현재 날짜와 시간을 나타내는 객체를 만들 수 있습니다. 이는 "에포크 (the epoch)" (1970년 1월 1일 자정 UTC) 이후의 시간으로 정의된 시점을 나타냅니다. Temporal은 나노초 (nanosecond) 수준의 정밀도로 이 시점을 참조할 수 있습니다.
const now = Temporal.Now.instant();
// 에포크 이후의 가공되지 않은 나노초 확인
console.log(now.epochNanoseconds);
...
Temporal.Instant 객체는 from 정적 메서드 (static method)를 사용하여 특정 시간과 날짜에 대해서도 생성할 수 있습니다.
const myInstant = Temporal.Instant.from('2026-02-18T21:10:00-05:00');
// 로컬 시간대(local time zone)에 맞춰 instant를 포맷팅합니다. 이 작업은
// 포맷팅만 제어할 뿐, `moment.utc`가 하는 것처럼 객체를 변경(mutate)하지는 않는다는 점에 유의하세요.
...
다음과 같은 다른 유형의 Temporal 객체들도 생성할 수 있습니다:
Temporal.PlainDate: 시간 정보가 없는 날짜.
Temporal.PlainTime: 날짜 정보가 없는 시간.
Temporal.ZonedDateTime: 특정 시간대(time zone)의 날짜와 시간.
이 각각의 객체는 날짜 및/또는 시간을 지정하는 객체나 파싱할 날짜 문자열을 인자로 받는 from 메서드를 가지고 있습니다.
// 단순히 날짜만 지정
const today = Temporal.PlainDate.from({
year: 2026,
...
파싱 (Parsing)
지금까지 날짜와 시간 정보를 프로그래밍 방식으로 생성하는 방법을 살펴보았습니다. 이제 파싱(parsing)에 대해 알아보겠습니다. 파싱은 Moment가 내장된 Temporal API보다 더 유연한 분야 중 하나입니다.
moment 함수에 날짜 문자열을 전달하여 날짜를 파싱할 수 있습니다. 인자를 하나만 전달하면 Moment는 ISO 날짜 문자열을 기대하지만, 사용 중인 날짜 형식을 지정하는 두 번째 인자를 제공하면 다른 형식도 사용할 수 있습니다.
const isoDate = moment('2026-02-21T09:00:00');
const formattedDate = moment('2/21/26 9:00:00', 'M/D/YY h:mm:ss');
console.log(isoDate);
...
이전 버전에서 Moment는 임의의 형식으로 작성된 날짜 문자열을 파싱하기 위해 최선의 추측(best guess)을 시도했습니다. 이는 예측 불가능한 결과로 이어질 수 있었습니다. 예를 들어, 02-03-2026은 2월 3일일까요, 아니면 3월 2일일까요? 이러한 이유로, 최신 버전의 Moment는 (원하는 형식을 지정하는 두 번째 인자가 함께 제공되지 않는 한) ISO 형식의 날짜 문자열 없이 호출될 경우 눈에 띄는 지원 중단(deprecation) 경고를 표시합니다.
Temporal은 특정 형식으로 지정된 날짜 문자열만 파싱합니다. 문자열은 ISO 8601 형식 또는 그 확장 표준인 RFC 9557을 준수해야 합니다. 규격에 맞지 않는 날짜 문자열이 from 메서드에 전달되면, Temporal은 RangeError를 발생시킵니다.
// RFC 9557 날짜 문자열 사용
const myDate = Temporal.Instant.from('2026-02-21T09:00:00-05:00[America/New_York]');
console.log(myDate.toString({ timeZone: 'America/New_York' }));
...
날짜 문자열의 정확한 요구 사항은 어떤 종류의 Temporal 객체를 생성하느냐에 따라 달라집니다. 위의 예시에서 Temporal.Instant는 시간대 오프셋(time zone offset)과 함께 날짜 및 시간을 지정하는 완전한 ISO 8601 또는 RFC 9557 날짜 문자열을 요구합니다. 하지만 날짜 형식의 일부 서브셋(subset)만을 사용하여 PlainDate 또는 PlainTime 객체를 생성할 수도 있습니다.
const myDate = Temporal.PlainDate.from('2026-02-21');
console.log(myDate.toString());
// 2026-02-21
...
이러한 문자열들은 여전히 예상되는 형식을 준수해야 하며, 그렇지 않으면 에러가 발생한다는 점에 유의하세요.
// 규격에 맞지 않는 시간 문자열 사용. 이들은 모두 RangeError를 발생시킵니다.
Temporal.PlainTime.from('9:00');
Temporal.PlainTime.from('9:00:00 AM');
전문가 팁: 비(non)-ISO 문자열 처리
Temporal은 신뢰성을 우선시하기 때문에 02-01-2026과 같은 문자열의 형식을 추측하려고 시도하지 않습니다. 데이터 소스에서 이러한 문자열을 사용하는 경우, Temporal에서 사용하기 전에 문자열 조작(string manipulation)을 통해 값을 2026-02-01과 같은 ISO 문자열로 재배열해야 합니다.
포매팅 (Formatting)
Moment 또는 Temporal 객체를 생성하고 나면, 어느 시점에는 이를 포맷팅된 문자열로 변환하고 싶을 것입니다.
이 지점에서는 Moment가 조금 더 간결합니다. 원하는 날짜 형식을 설명하는 토큰(token) 문자열과 함께 객체의 format 메서드를 호출하면 됩니다.
const date = moment();
console.log(date.format('MM/DD/YYYY'));
// 02/22/2026
...
반면에 Temporal은 조금 더 상세한(verbose) 작성을 요구합니다. Instant와 같은 Temporal 객체는 객체의 속성으로 지정된 다양한 포매팅 옵션을 허용하는 toLocaleString 메서드를 가지고 있습니다.
const date = Temporal.Now.instant();
// 인자가 없으면 현재 로케일(locale)의 기본 형식을 가져옵니다.
console.log(date.toLocaleString());
...
Temporal의 날짜 포맷팅 (date formatting)은 내부적으로 (이미 현대적인 브라우저에서 즉시 사용할 수 있는) Intl.DateTimeFormat API를 사용합니다. 이는 사용자 정의 포맷 옵션이 포함된 재사용 가능한 DateTimeFormat 객체를 생성한 다음, 해당 객체의 format 메서드에 Temporal 객체를 전달할 수 있음을 의미합니다. 이로 인해 Moment와 달리 사용자 정의 날짜 포맷을 지원하지 않습니다. 만약 'Q1 2026'이나 다른 특수한 포맷팅이 필요하다면, 별도의 사용자 정의 날짜 포맷팅 코드를 작성하거나 제3자 라이브러리 (third-party library)를 사용해야 할 수도 있습니다.
const formatter = new Intl.DateTimeFormat('en-US', {
month: '2-digit',
day: '2-digit',
...
Moment의 포맷팅 토큰 (formatting tokens)은 작성하기 더 간편하지만, 로케일 (locale) 친화적이지 않습니다. 포맷 문자열은 월/일 순서와 같은 것들을 "하드 코딩 (hard code)"합니다. Temporal처럼 설정 객체 (configuration object)를 사용하는 것의 장점은, 주어진 모든 로케일에 자동으로 적응하여 올바른 형식을 사용한다는 점입니다.
const date = Temporal.Now.instant();
const formatOptions = {
month: 'numeric',
...
날짜 계산 (Date calculations)
많은 애플리케이션에서 날짜에 대한 계산을 수행해야 할 때가 있습니다. 시간 단위 (일, 시간, 초 등)를 더하거나 빼고 싶을 수 있습니다. 예를 들어, 현재 날짜가 있다면 사용자에게 지금으로부터 1주일 뒤의 날짜를 보여주고 싶을 수 있습니다.
Moment 객체에는 이러한 연산을 수행하는 add 및 subtract와 같은 메서드가 있습니다. 이 함수들은 값과 단위를 인자로 받습니다 (예: add(7, 'days')). 그러나 Moment와 Temporal 사이의 매우 중요한 차이점 중 하나는, 이러한 날짜 계산을 수행할 때 내부 객체가 수정되어 원래의 값이 손실된다는 점입니다.
const now = moment();
console.log(now);
// Moment<2026-02-24T20:08:36-05:00>
...
원래의 날짜를 잃지 않으려면, Moment 객체에서 clone을 호출하여 복사본을 만들 수 있습니다.
const now = moment();
const nextWeek = now.clone().add(7, 'days');
console.log(now);
...
반면에, Temporal 객체들은 *불변(immutable)*입니다. Instant, PlainDate 등과 같은 객체를 한 번 생성하고 나면, 해당 객체의 값은 절대 변하지 않습니다. Temporal 객체는 또한 add 및 subtract 메서드를 가지고 있습니다.
Temporal은 어떤 시간 단위(time units)를 어떤 객체 타입에 더할 수 있는지에 대해 다소 까다롭게 구분합니다. 예를 들어, Instant에는 일(days)을 더할 수 없습니다:
const now = Temporal.Now.instant();
const nextWeek = now.add({ days: 7 });
// RangeError: Temporal error: Largest unit cannot be a date unit
이는 Instant 객체가 UTC 기준의 특정 시점을 나타내며, 달력에 의존하지 않기(calendar-agnostic) 때문입니다. 일(day)의 길이는 일광 절약 시간제(Daylight Saving Time)와 같은 시간대(time zone) 규칙에 따라 변할 수 있으므로, Instant에서는 이러한 계산을 수행할 수 없습니다. 하지만 PlainDateTime과 같은 다른 유형의 객체에서는 이 작업을 수행할 수 있습니다:
AI 자동 생성 콘텐츠
본 콘텐츠는 Smashing Magazine의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기