JavaScript에는 날짜와 시간을 저장하고 관리하는 메서드를 제공하는 내장 객체 Date가 있어요.
이 객체는 Java의 java.util.Date 클래스를 기반으로 만들어졌는데, 그 클래스는 2010년대 초에 대체되었어요. 하지만 하위 호환성을 위해 JavaScript의 Date는 계속 남아 있어요.
Date를 다루는 일이 전반적으로 어렵고, 시간대 처리가 형편없거나 아예 없기 때문에 moment.js, day.js, date-fns, luxon 같은 라이브러리가 많이 있어요.
이 중 어느 것도 Exercism에서는 사용할 수 없어요.
직접 만드는 프로젝트에서는 moment.js처럼 더 이상 사용하지 않거나 유지보수되지 않는 패키지를 쓰지 말고, luxon 같은 더 현대적인 대안이나 아직 널리 보급되지 않은 Temporal에 의존해요.
이 연습 문제는 JavaScript가 존재하는 한 계속 중요할 Date에 초점을 맞춰요.
Date 객체는 Date 클래스의 인스턴스예요.
생성자 함수에 아무 인자도 전달하지 않고 만들 수 있어요.
이렇게 하면 현재 날짜와 시간을 나타내는 Date 객체가 만들어져요:
const now = new Date();
// => Thu Apr 14 2022 11:46:08 GMT+0530 (India Standard Time)
// Shows current day, date and time (in your time zone).
숫자를 전달하면 이는 timestamp로 해석돼요.
타임스탬프는 UTC+0 기준 1970년 1월 1일 이후 지나간 밀리초 수를 나타내는 정수예요.
const epoch = new Date(0);
// Thu Jan 01 1970 01:00:00 GMT+0100 (Central European Standard Time)
const another = new Date(1749508766627);
// Tue Jun 10 2025 00:39:26 GMT+0200 (Central European Summer Time)
new Date(0)이 "가장 이른" 날짜 객체를 만들 거라고 기대할 수도 있지만, JavaScript는 날짜를 로컬 시간대로 변환해요. 그래서 GMT / UTC+0 시간대 부근에 있는 사람만 실제로 Unix epoch 값을 얻게 돼요.
날짜를 나타내는 문자열 값을 Date 생성자에 전달할 수 있어요.
구현에 관계없이 일관되게 동작하는 유일한 형식은 국제적으로 인정되고 표준화된 소위 ISO 8601 타임스탬프 문자열의 간소화 버전이에요.
UTC 기준의 한 시점은 이렇게 생겼어요:
YYYY-MM-DDTHH:mm:ss.mssZ
YYYYMMDDTHHmmss.mssZ
여기서 다음 자리에 값이 들어가요:
| 키 | 설명 | 기본값 |
|---|---|---|
| YYYY | 4자리로 표현한 역년(달력상의 해) | |
| MM | 2자리로 표현한 월 | 01 |
| DD | 2자리로 표현한 일 | 01 |
| T | 날짜와 시간을 구분하는 문자 그대로의 T | |
| HH | 24시간제의 시, 2자리 | 00 |
| mm | 분, 2자리 | 00 |
| ss | 초, 2자리 | 00 |
| mss | 밀리초, 3자리 | 000 |
| Z | 문자 그대로의 Z, 또는 오프셋 +/-HH:mm
|
문자 그대로의 Z는 UTC를 나타내요(시간대 없음, 일광 절약 시간 없음).
대부분의 구성 요소에 기본값이 있기 때문에, 뒤쪽 부분을 생략해도 괜찮아요:
YYYY-MM-DD
시간은 기본값인 00:00:00.000이 돼요.
타임스탬프가 Z로 끝나지 않고, +HH:mm이나 -HH:mm(시간대 오프셋을 나타내는)으로도 끝나지 않는다면, 역사적인 이유로 다음이 적용돼요:
시간대 오프셋이 없으면, 날짜만 있는 형식은 UTC 시간으로 해석되고 날짜와 시간이 있는 형식은 로컬 시간으로 해석돼요. UTC 시간으로 해석하는 것은 ISO 8601과 일치하지 않았던 역사적인 명세 오류 때문인데, 웹 호환성 때문에 바꿀 수 없었어요. Broken Parser – A Web Reality Issue를 참고해요.
Date.parse가 받아들이는 다른 형식들은 동작할 수도 있고 안 할 수도 있어요.
JavaScript에서 Date를 다룰 때는 string을 Date로 변환할 때 항상 ISO 8601 타임스탬프를 사용해요.
날짜만 있는 형식도 허용되지만, 모든 ISO 8601 형식이 지원되는 것은 아니에요. MDN의 간소화 버전 설명 페이지를 참고해요.
이미 있는 Date 객체를 생성자 인자로 사용할 수도 있어요.
이렇게 하면 같은 날짜와 시간을 가진 기존 Date 객체의 복사본이 만들어져요.
const t1 = new Date();
const t2 = new Date(t1);
// Values of t1 and t2 will be the same.
날짜를 나타내는 Date는 숫자 세 개를 전달해서 만들 수 있어요. 날짜와 시간을 나타내는 Date는 숫자 여섯 개를 전달해서 만들 수 있어요.
const date1 = new Date(95, 11, 17);
// Creates Date for Dec 17 1995 00:00 if your local timezone is equivalent to UTC.
const date2 = new Date(2013, 12, 5, 13, 24, 0);
// Creates Date for Jan 5 2014 13:24 if your local timezone is equivalent to UTC.
두 번째 값은 month인데, 1월이 0이고 12월이 11이에요.
Date.parse()날짜 파싱 함수 Date.parse에 대한 언급이나 참고 자료를 볼 수도 있어요.
이 함수에는 몇 가지 불변(invariant)만 있고 나머지 구현은 명세되어 있지 않아서(따라서 표준도 아니어서) 사용하지 않는 게 좋아요.
Date 구성 요소에 접근하기날짜 객체에는 날짜의 구성 요소를 반환하는 다양한 메서드가 있어요:
getFullYear(); // Get the year (4 digits)
getMonth(); // Get the month, from 0 to 11.
getDate(); // Get the day of month, from 1 to 31.
getHours(); // Get the hour in a 24 clock, from 0 to 23
getMinutes(); // Get the minutes, from 0 to 59
getSeconds(); // Get the seconds, from 0 to 59
getMilliseconds(); // Get the milliseconds, from 0 and 999
getDay(); // Get the day of week, from 0 (Sunday) to 6 (Saturday).
각 메서드에는 값을 갱신하는 대응하는 set 변형(예: setFullYear)이 있어요.
넘치는 값은 다음 구성 요소로 넘어가요:
const date = new Date('2025-02-28T12:42:00Z');
// => Fri Feb 28 2025 13:42:00 GMT+0100 (Central European Standard Time)
date.setDate(29);
// there was no February 29th in 2025.
date.getDate();
// => 1
date.toString();
// => Sat Mar 01 2025 13:42:00 GMT+0100 (Central European Standard Time)
로컬 시간대를 무시하는 모든 메서드에는 UTC 변형이 있어요.
Date 객체에는 UNIX 타임스탬프를 밀리초로 반환하는 getTime() 메서드가 있어요. 즉, 1970년 1월 1일 UTC 자정 이후 지난 밀리초 수예요.
또한 날짜 객체를 ISO 8601 타임스탬프 문자열로 변환하는 toISOString() 메서드도 있어요.
크다(>), 크거나 같다(>=), 작다(<), 작거나 같다(<=) 연산자는 두 날짜 사이 또는 날짜와 숫자 사이에 바로 사용할 수 있어요.
JavaScript가 날짜를 원시값으로 변환하려고 시도하기 때문에 이렇게 동작해요.
두 날짜 사이 또는 날짜와 숫자 사이를 비교할 때, JavaScript는 [Symbol.toPrimitive]("number")를 호출하고, 이는 내부적으로 date.valueOf()를 호출해요.
후자는 date.getTime()을 호출하는 것과 같아요.
이 동작에 의존하고 싶지 않다면 먼저 getTime()으로 숫자로 변환해요.
날짜는 동등 비교(==, ===)로 비교할 수 없지만, .getTime()의 결과는 비교할 수 있어요.
이번 연습 문제에서는 약속을 관리하기 위한 몇 가지 함수를 다뤄요. 이 시스템은 모든 것을 ISO 8601 형식의 문자열로 저장하지만, 사람들이 달력을 사용하는 방식과는 달라요. 여러 형식 사이를 변환하려면 다양한 함수가 필요해요.
지금으로부터 n일 뒤의 현재 시각에 약속을 만들어요.
이 함수는 n일을 받아서 지금으로부터 n일 뒤의 약속 시각을 반환해요.
createAppointment(4, now);
// Given now is Sun Oct 05 2022 23:28:43 GMT+0600 (Bangladesh Standard Time)
// => Sun Oct 09 2022 23:28:43 GMT+0600 (Bangladesh Standard Time)
두 번째 매개변수 now를 사용하지 않으면, 대신 현재 시각을 사용해야 해요.
여러 도구는 국제 표준인 ISO 8601 형식에서만 동작해요.
getAppointmentTimestamp 함수를 작성해서 날짜를 받아 그 형식의 문자열을 반환하게 해요.
const appointment = new Date(Date.UTC(2010, 6, 16, 12, 42, 0, 0));
getAppointmentTimestamp(appointment);
// => '2010-07-16T12:42:00.000Z'
타임스탬프는 읽기 어려워요. 약속의 세부 정보를 가져오는 함수가 도움이 될 거예요.
getAppointmentDetails 함수는 ISO 8601 형식의 타임스탬프를 받아서 연도, 월, 일, 시, 분을 반환해요.
getAppointmentDetails('2022-04-24T08:15:00.000');
// => { year: 2022, month: 3, date: 24, hour: 8, minute: 15 }
이 함수는 첫 번째 인자로 약속 시각을, 두 번째 인자로 몇 가지 옵션을 담은 객체를 받아요. 객체의 옵션에 따라 약속을 업데이트하고, 새로운 약속 날짜를 반환해야 해요. 옵션 객체에는 여러 옵션이 있을 수 있어요.
updateAppointment('2022-02-09T09:20:00.000', { month: 6 });
// => { year: 2022, month: 6, date: 9, hour: 10, minute: 20 }
이 함수는 두 약속(타임스탬프)을 인자로 받아요. 두 시각의 차이를 초 단위로 반환해야 해요.
0.5초는 거의 의미가 없으므로, 반환하기 전에 숫자를 반올림해요.
timeBetween('2022-12-12T09:20:00.000', '2022-12-18T08:30:00.000');
// => 515400
마지막으로, 약속이 잡히면 시스템은 그것이 유효한지 확인해야 해요. 다시 말해, 약속은 과거가 아니라 미래여야 해요.
isValid 함수를 작성해요. 이 함수는 두 개의 인자, 즉 약속 타임스탬프(문자열)와 현재 시각의 타임스탬프(문자열)를 받고, 주어진 현재 시각을 기준으로 약속이 미래라면 true를 반환해요.
isValid('2022-02-11T23:00:00.000', '2022-02-08T23:00:00.000');
// => true
Exercism에 가입하고 JavaScript 트랙을 개념 37개연습 문제 159개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.