원문: https://philna.sh/blog/2026/01/11/javascript-date-calculation/

문제
2025년 1월, 저는 미국 캘리포니아주 산타클라라에서 보고서를 만들기 위한 자바스크립트를 작성하고 있었습니다. 한 달 동안 발생한 이벤트 수를 구하고 싶었기 때문에, 해당 월의 첫째 날로 날짜 객체를 만들고, 한 달을 더한 뒤, 하루를 빼서 마지막 날을 구하려 했습니다. 간단해 보이죠?
그런데 정말 이상한 결과가 나왔습니다. 문제를 다음 코드로 재현할 수 있었습니다.
const date = new Date("2024-01-01T00:00:00.000Z");
date.toISOString();
// => "2024-01-01T00:00:00.000Z" as expected
date.setMonth(1);
date.toISOString();
// => "2023-03-04T00:00:00.000Z" WTF?
2024년 1월 1일에 한 달을 더했는데 2023년 3월 4일이 되었습니다. 무슨 일이 일어난 걸까요?
시간과 시간대
이 이야기의 배경이 왜 미국 서부 해안인지 의아했을 수도 있지만, 사실 장소가 중요한 요인이었습니다. 이 코드는 UTC 시간대와 그보다 동쪽인 지역에서는 정상적으로 동작했을 겁니다.
자바스크립트의 날짜는 단순한 날짜가 아니라 시간까지 함께 다룹니다. 이 예제에서는 일과 월만 다루고 싶었지만, 시간도 중요했습니다.
저도 이 사실을 알고 있었기에 시간을 UTC로 설정하면 어디서든 잘 동작할 거라 생각했습니다. 그게 패착이었습니다. 무슨 일이 일어났는지 하나씩 살펴보겠습니다.
2024년 1월 1일 자정(UTC)은 태평양 표준시(UTC-8)로는 아직 2023년 12월 31일 오후 4시입니다. date.setMonth(1)은 월을 2월로 설정합니다(월은 일과 달리 0부터 시작합니다). 하지만 시작 날짜가 2023년 12월 31일이었으므로, 자바스크립트는 존재하지 않는 2023년 2월 31일을 처리해야 합니다. 이때 다음 달로 넘겨서 3월 3일이 됩니다. 마지막으로, 출력을 위해 날짜를 다시 UTC로 변환하면 최종 결과는 2023년 3월 4일 자정이 됩니다.
단계별로 살펴보면 각 단계는 합리적으로 느껴지지만, 혼란은 그 결과가 너무나 예상 밖이었다는 데서 비롯됩니다.
그렇다면 어떻게 고칠 수 있을까요?
항상 UTC를 사용하세요
저는 시간에는 관심이 없었고 UTC로 작업하고 싶었기 때문에, Date 객체의 setUTCMonth 메서드를 사용해서 코드를 수정했습니다. 원래 코드에서는 하루를 빼서 해당 월의 마지막 날을 구했으므로, setUTCDate 메서드도 함께 사용했습니다. 모든 set${timePeriod} 메서드에는 대응하는 setUTC${timePeriod} 메서드가 있습니다.
const date = new Date("2024-01-01T00:00:00.000Z");
date.toISOString();
// => "2024-01-01T00:00:00.000Z"
date.setUTCMonth(1);
date.toISOString();
// => "2024-02-01-T00:00:00.000Z"
이렇게 문제를 해결했습니다. 하지만 더 나은 방법은 없을까요?
Temporal을 소개합니다
이 문제가 발생한 이유 중 하나는 날짜를 조작하려 했지만, 실제로는 자신도 모르게 날짜와 시간을 함께 조작하고 있었기 때문입니다. 글 서두에서 Temporal을 언급한 이유는 바로 이런 상황을 위한 객체가 있기 때문입니다.
이 코드를 Temporal로 작성한다면 Temporal.PlainDate를 사용할 수 있습니다. 시간이나 시간대 없이 달력상의 날짜만 표현하는 객체입니다.
이것만으로도 훨씬 단순해지지만, Temporal은 날짜를 조작하는 방법도 더 직관적으로 만들어 줍니다. 월이나 일을 직접 설정하거나 밀리초를 더하는 대신, 기간을 더합니다. Temporal.Duration 객체로 기간을 생성하거나, 기간을 정의하는 객체를 사용할 수 있습니다.
Temporal은 객체를 불변(immutable)으로 만들기 때문에, 날짜를 변경할 때마다 새로운 객체를 반환합니다.
이 경우 한 달을 더하고 싶었으므로, Temporal로는 다음과 같이 작성합니다.
const startDate = Temporal.PlainDate.from("2024-01-01");
// => Temporal.PlainDate 2024-01-01
const nextMonth = startDate.add({ months: 1 });
// => Temporal.PlainDate 2024-02-01
const endDate = nextMonth.subtract({ days: 1 });
// => Temporal.PlainDate 2024-01-31
시간 걱정 없이 날짜를 조작할 수 있습니다. 훌륭하죠!
물론 잘 설계된 Temporal API에는 이 외에도 많은 장점이 있으며, 모든 자바스크립트 런타임에 포함되는 날이 기다려집니다.
시간대를 주의하세요
Temporal은 아직 많은 자바스크립트 엔진에 탑재되지 않았습니다. 이 글을 쓰는 시점에서 Firefox에서만 사용할 수 있으므로, 직접 테스트해 보고 싶다면 Firefox를 열거나 폴리필(polyfill)인 @js-temporal/polyfill 또는 temporal-polyfill을 확인해 보세요.
수정: 이 글을 게시한 지 불과 이틀 만에 Chrome 144에서 Temporal 지원이 시작되었습니다. Can I Use에 따르면 Temporal은 Safari Technology Preview에서 플래그를 활성화해야 사용할 수 있으므로, Safari에서도 곧 지원될 수 있을 것 같습니다.
아직 Date를 사용해야 한다면 시간대를 반드시 염두에 두세요. 지금이라도 Temporal로 전환하거나, 최소한 사용 방법을 익혀 두시길 권합니다.
그리고 시간대를 조심하세요. 피하려 해도 결국 골치 아프게 만드니까요.
'Web Frontend Developer' 카테고리의 다른 글
| [번역] 리액트는 기본값으로 승리했습니다 – 그리고 프런트엔드 혁신을 죽이고 있습니다 (0) | 2026.05.08 |
|---|---|
| [번역] CSS in 2026: 프런트엔드 개발을 바꾸는 새로운 기능들 (0) | 2026.03.27 |
| [번역] 이제 모던 CSS가 SPA를 끝낼 때입니다 (12) | 2026.03.27 |
| [번역] 디자인 엔지니어란 무엇인가? (0) | 2026.03.11 |
| [번역] 토스트 컴포넌트 만들기 (0) | 2026.02.11 |