회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
AWS 이용 중이라면 최대 700만 원 지원받으세요
프론트엔드에서 날짜와 시간을 다루는 작업은 매우 흔한 일입니다. 그래서 ‘자바스크립트(JavaScript)’에서는 Date
객체가 빌트인으로 존재하죠. 하지만 JavaScript의 Date
만으로 날짜를 다루는 것은 생각보다 쉽지 않습니다. 게다가 꼭~ 로컬 컴퓨터에서 개발할 때는 문제가 없다가 배포하고 난 후에야 문제가 생기기 때문에 우리를 참 난감하게 합니다. 그러다 보니 JavaScript로 개발할 때는 moment.js, day.js, luxon 같은 날짜 및 시간 관리 라이브러리를 거의 필수적으로 쓰는 편입니다.
회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
회원가입을 하면
성장에 도움이 되는 콘텐츠를
스크랩할 수 있어요!
확인
프론트엔드에서 날짜와 시간을 다루는 작업은 매우 흔한 일입니다. 그래서 ‘자바스크립트(JavaScript)’에서는 Date
객체가 빌트인으로 존재하죠. 하지만 JavaScript의 Date
만으로 날짜를 다루는 것은 생각보다 쉽지 않습니다. 게다가 꼭~ 로컬 컴퓨터에서 개발할 때는 문제가 없다가 배포하고 난 후에야 문제가 생기기 때문에 우리를 참 난감하게 합니다. 그러다 보니 JavaScript로 개발할 때는 moment.js, day.js, luxon 같은 날짜 및 시간 관리 라이브러리를 거의 필수적으로 쓰는 편입니다.
그런데 발생하는 문제 중에서도 우리는 유독 서버와 클라이언트의 시간차가 9시간이 나는 경우를 자주 만나곤 합니다. 이처럼 클라이언트에서 작성한 코드가 서버에서 다르게 보이는 이유는 각 컴퓨터가 위치한 시간대(timezone) 가 다르기 때문입니다. 하지만 이것만으로는 정답을 100% 맞혔다고 이야기할 수는 없습니다. 왜냐하면 JavaScript의 Date
처리 방법에 대한 설명이 없으니까요.
그래서 오늘은 JavaScript의 Date
가 시간을 다루는 방식에 대해 좀 더 깊게 알아보고자 합니다. 이 포스트에서는 표준 시간대가 왜 필요하고, JavaScript의 날짜와 시간 처리 방식은 무엇이며, 이것이 왜 다루기 어려운지를 알아봅시다. 이번 포스트를 통해 JavaScript에서의 날짜 및 시간 처리가 어렵게 느껴지는 분들께 도움이 되기를 바랍니다.
3월 9일에 생일인 친구가 있어서, 그 친구에게 생일 축하 메시지를 보내고 싶다고 가정해봅시다. 우리는 개발자니까 자정이 되는 즉시 축하 메시지를 보내는 코드를 짜려고 합니다. 그래서 날짜를 아래처럼 만들어 봤습니다.
new Date('2020-03-09');
// Mon Mar 09 2020 09:00:00 GMT+0900 (한국 표준시)
그런데 불필요하게 9시간이 추가가 된 것을 볼 수 있습니다. 생일 축하 메시지는 자정에 보내야 뜻깊을 텐데 말이죠. 한편 제 옆에 있는 또 다른 친구가 작성한 코드는 잘 동작하는 것 같습니다.
new Date('March 9, 2020');
// Mon Mar 09 2020 00:00:00 GMT+0900 (한국 표준시)
잠깐만요, 분명히 둘 다 3월 9일을 입력한 것 같은데 결과가 다르게 나옵니다. 의아하긴 하지만, 시간까지 명시해서 좀 더 확실하게 자정을 가리켜야겠습니다. 구글링을 해보니 시간을 나타내는 문자열에도 표준이 있다고 합니다. 그 형식에 맞추어 다시 날짜를 만들어봤습니다.
new Date('2020-03-09T00:00:00Z')
// Mon Mar 09 2020 09:00:00 GMT+0900 (한국 표준시)
그런데 아직 9시간이 더해진 채 나오네요. 뭐가 문제인가 싶어 거추장스러워 보이는(?) 알파벳을 빼봤습니다.
new Date('2020-03-09 00:00:00')
// Mon Mar 09 2020 00:00:00 GMT+0900 (한국 표준시)
이제야 제대로 나오네요. 오히려 표준 형식에 붙은 문자를 떼고 나서야 제대로 동작하는 것 같습니다. 이게 도대체 무슨 일이죠?
이처럼 JavaScript의 Date
는 같은 날짜와 시간을 입력한 것처럼 보임에도 불구하고 그 결과가 다르게 나오는 경우가 있습니다. 그리고 컴퓨터 자체의 시간대 설정에 따라 그 결과가 달라지기도 합니다. 날짜와 시간의 정확도는 말할 필요도 없이 중요한 작업일 텐데, 시간대가 들어가다 보면 이게 꽤 헷갈립니다.
말이 나온 김에 시간대에 대해 좀 더 알아보도록 합시다. 개발과 직접적으로 관련 있는 내용은 아니지만, 시간대의 원리를 이해해야 JavaScript의 Date
를 더 잘 이해할 수 있습니다. 고등학교 때 배운 지구과학 지식을 여기에다가 써먹을 줄은 몰랐네요.
과거에 시계가 없었을 때는 사람들은 어떻게 시간이 흐르는 것을 판단했을까요? 바로 태양이 움직임에 따라 생기는 그림자를 보고 판단했습니다.
해는 하루 동안 동쪽에서 서쪽으로 이동하므로 그림자는 해가 지나간 반대 방향으로 움직입니다. 그리고 해가 가장 높이 뜰 때, 즉 머리 위에 해가 있을 때는 그림자가 지지 않습니다. 그래서 하루를 24시간으로 나눌 때, 해가 가장 높이 뜨는 시간을 낮 12시로 삼았죠.
하지만 문제가 발생했습니다. 지구는 둥글기 때문에 다른 경도(longitude)에 사는 사람에게는 지금 시각이 정오가 아닐 수 있다는 것이었죠. 다른 경도에 사는 사람과 약속을 잡으려면 어떤 지역의 시간을 기준으로 정오인지를 정해야 했습니다.
즉, 전 세계 어디에서나 동일한 시각을 가리키기 위해서는 인류 공통의 기준점과 표준이 필요했습니다. 그래서 등장한 것이 시간대죠.
찾아보면 시간대의 종류가 꽤 많지만, 여기서는 크게 GMT와 UTC를 알아보고자 합니다. 그리고 우리에게 익숙한 표준시인 KST도 알아보죠.
그리니치 표준시(GMT)는 말 그대로 영국에 위치한 그리니치 천문대를 기준으로 경도를 나누는 시간대입니다. 지구를 대략 15도씩 나누어 24개의 시간대를 만든 셈이죠.
그리니치 천문대가 있는 영국의 시간을 기준으로 이보다 동쪽에 있는 지역일수록 해가 더 일찍 뜨며, 서쪽에 있는 나라일수록 해가 더 늦게 뜹니다. GMT 그 자체는 말 그대로 영국의 시간을 의미하므로, 동쪽에 있는 나라는 GMT보다 이른 시간대를, 서쪽에 있는 나라는 GMT보다 느린 시간대를 가지게 됩니다. 해당 시간대를 부를 때는 시차를 붙여 GMT+01, GMT-02처럼 표현합니다.
여기서 헷갈리면 안 되는 게, 플러스가 적힌 시간대가 더 시간이 이른 것입니다. 왜냐하면 영국을 기준으로 그만큼의 시간이 더 흐른 지역이기 때문이죠.
아까 앞서 작성한 JavaScript의 코드 결과물을 살펴보면 GMT+0900 (한국 표준시)
라고 적힌 것을 볼 수 있습니다. 이는 GMT 기준으로 한국이 9시간을 앞선 시간대에 있다는 것을 의미합니다. 하지만 GMT는 현재 국제 표준으로 삼고 있는 시간대가 아닙니다. 왜냐하면 과학계에서 좀 중요한 일이 있었거든요.
바로 초(second)라는 시간 단위의 정의가 1967년 바뀌었기 때문입니다. 사실 초의 정의가 바뀌기 전까지만 해도 우리는 지구가 한 바퀴 자전하는 것을 24시간으로 삼고 있었습니다. 따라서 초의 정의는 태양이 뜨고 지는 시간, 즉 지구가 한 바퀴 자전하는 시간을 86,400으로 나눈 단위였습니다.
하지만 과학자들이 지구의 자전 속도를 면밀하게 관찰해본 결과, 그 속도가 미세하게 느려지고 있다는 것을 알게 되었죠. 그래서 과학자들은 우주 어디에서도 절대 변하지 않는 시간 단위를 새로 찾게 됩니다.
그렇게 찾은 방법이 세슘 원자의 진동수를 기준으로 초의 정의를 새롭게 만든 것입니다. 그리고 바뀐 초의 정의를 기반으로 시간 체계를 만드는데 이를 IAT(International Atomic Time)라고 부릅니다.
자, 이야기가 다른 길로 샌 것 같아 보이지만 IAT로부터 UTC(Universal Time Coordinated)라는 시간대가 나오기 때문에 둘은 상관관계가 있습니다. 즉, 초의 정의가 바뀌면서 UTC가 나오게 된 셈이죠.
지구의 자전 속도가 느려지면 초의 단위가 바뀌는 GMT와는 다르게, UTC는 윤초를 추가함으로써 초의 정의를 바꾸지 않으면서 시간을 보정할 수 있다는 장점이 있습니다. 그래서 현재는 UTC를 국제 표준 시간대로 삼고 있습니다.
UTC에서 시간대를 나누는 기준은 GMT와 동일하므로 UTC 역시 영국이 포함된 시간대를 기준으로 삼습니다. 즉, UTC 시간이라고 쓰면 그것은 영국의 시간이라고 봐도 무방합니다. 따라서 UTC는 전 세계 어디에서 측정해도 같은 시간을 나타낸다는 것을 꼭 기억해야 합니다. 만약 다른 시간대를 나타내고 싶으면 GMT처럼 영국 기준으로 시간대가 얼마나 떨어져 있는지를 UTC+01, UTC-02 등으로 표기합니다.
GMT와 UTC는 초를 정의하는 단위가 과학적으로 다르지만, 사실 JavaScript에서 그 둘은 사실상 똑같다고 봐도 무방합니다. 왜냐하면 JavaScript에서 두 용어를 그냥 혼용해서 쓰고 있기 때문입니다.
아까도 말했지만 GMT는 지금 사용하고 있는 표준 시간대가 아닙니다. 이미 1972년부터 UTC가 국제 표준이 되었습니다. 따라서 여러분의 컴퓨터 역시 UTC 기반으로 동작합니다. 그런데 유독! JavaScript의 Date
객체의 일부 출력이 시간대를 GMT라고 표시하고 있습니다.
왜냐고요? 그냥 스펙에 그렇게 적혀 있습니다. JavaScript는 1995년에 나온 언어라서 UTC 기반으로 통일을 했을 법도 한데, JavaScript는 원래부터 이상한 언어니까 그러려니 하는 거죠, 뭐.
아무튼 뇌를 비우고 이것만 기억하면 됩니다.
1. JavaScript에서 GMT는 UTC와 똑같다!! 2. 그리고 UTC는 시간대와 상관없이 항상 같은 값이다!! |
예전에는 표준이었는데 아직 용어가 혼용되고 있는 비슷한 예시를 생각해보면 ‘SSL과 TLS 정도의 관계가 아닐까?’라는 생각을 해봅니다.
UTC 이야기가 나왔으니 우리에게 익숙한 KST 이야기도 해볼까 합니다. KST(Korea Standard Time)는 한국 표준시입니다. UTC로부터 9시간 이른 시간대죠. 그런데 KST라는 이름은 사실 UTC 기준시에 닉네임을 붙인 것 정도에 불과합니다. KST의 다른 이름을 아래와 같은 방식으로도 표현할 수 있습니다.
|
그리고 UTC+09 시간대에 속해 있는 나라들이 한국 말고도 또 있다는 점이 주목할만 합니다. 일본과 러시아, 호주, 동남아 일부 지역이 보이네요. 즉 경도는 같은데 위도가 다른 지역에 있는 국가들과는 UTC+09라는 시간대를 공유합니다.
얘네들도 자기네 지역의 시간대에다가 닉네임을 붙였습니다. 일본은 JST(Japan Standard Time), 러시아는 YAKT(Yakutsk Time) 라고 부르네요. 즉, UTC+09 시간대에는 KST, JST, YAKT 등이 속해 있습니다.
그래서 사실 이런 이름들은 우리에게 중요한 정보가 아닙니다. 우리는 단지 UTC 시간만 알면 됩니다.
지금까지는 사람이 시간을 정의하는 방법에 대해 알아보았다면, 지금부터는 컴퓨터가 시간을 정의하는 방법에 대해 알아보고자 합니다.
컴퓨터가 현재 시각을 기억하기 위해서는 기준이 되는 시점, 그리고 흐르는 시간을 측정할 빈도가 필요합니다. 그리고 이를 나타내는 단위가 바로 유닉스 타임(Unix time)입니다.
유닉스 타임은 1970년 1월 1일 0시 0분 0초를 기준으로 지금까지 흐른 시간을 초 단위로 표현한 것입니다. POSIX 시간, 유닉스 에포크(Unix Epoch), 유닉스 타임스탬프(Unix timestamp)처럼 다양한 이름으로 불리기도 합니다.
유닉스 타임 역시 시간대와 상관없이 전 세계 어디에서 호출해도 동일한 시점에 호출한다면 동일한 값을 반환합니다. 왜냐하면 1970년 1월 1일 0시 0분 0초를 기준으로 흐른 시간을 초 단위로 표현하기 때문입니다.
JavaScript의 Date
역시 유닉스 타임을 기반으로 시간을 표현합니다. 다만 초 단위가 아니라 밀리초 단위로 틱을 센다는 점이 다릅니다.
이 유닉스 타임 값은 .getTime()
, .valueOf()
를 통해 얻을 수 있습니다. 예외적으로 정적 메서드인 Date.now()
를 통해 코드 실행 시점의 유닉스 타임을 얻을 수도 있습니다.
// 1. Date 객체를 생성하고 getTime() 메서드를 호출
new Date('2020-03-09').getTime()
// 1583712000000
// 2. Date 객체를 생성하고 valueOf() 메서드를 호출
new Date('2020-03-09').valueOf()
// 1583712000000
// 3. Date.now() 정적 메서드를 호출, 인스턴스가 없다는 점에 주의
Date.now()
// 1646751600000
JavaScript로 날짜 작업을 할 때, Date
는 곧 유닉스 타임이라는 점을 항상 기억해야 합니다. 과장을 좀 보태자면 Date
는 그냥 숫자입니다.
왜냐하면 우리가 JavaScript로 Date
를 다루면서 마주치는 문제들은 주로 유닉스 타임의 입력과 출력 과정에서 발생하기 때문입니다.
1. 2. 위 방법으로 생성된 |
즉, JavaScript Date
가 담고 있는 값은 단순한 유닉스 타임인데 생성자의 값으로 무엇을 넣고, 어떤 메서드를 호출하느냐에 따라 반환되는 값과 형식이 달라집니다.
자, 여기서 하나 짚고 넘어가야 할 것이 있습니다. 바로 ISO 8601이라고 불리는 국제 날짜와 시간 표기법입니다. 컴퓨터 세계에서도 날짜와 시간을 나타내기 위해서 이 표준을 준수하고 있죠.
ISO 8601 표준 방식으로는 다음과 같은 두 가지 방법으로 날짜와 시간을 표현할 수 있습니다.
1. YYYYMMDDTHHmmsssssZ // 기본 형식 2. YYYY-MM-DDTHH:mm:ss.sssZ // 확장 형식 |
첫 번째 방법을 기본 형식, 두 번째 방법을 확장 형식이라고 부릅니다. 기본 형식은 구분자가 없어 가독성이 별로 안 좋기 때문에 JavaScript에서는 두 번째 방법만 쓸 수 있습니다.
알파벳은 각자 연도(Y
), 월(M
), 일(D
), 시(H
), 분(m
), 초와 밀리초(s
)를 나타냅니다. T
는 구분자로, 날짜와 시간 사이에 넣는 문자이며 큰 의미는 없어서 생략하거나 공백으로 대체가 가능합니다. Z
는 현재 내가 표현하는 시각이 UTC 기준임을 나타내는 기호로, 군사 용어에서 이름을 따와 줄루 타임(Zulu time)이라고 부릅니다. Z
를 생략하면 현지 시각을 나타내는 것에 주목하세요.
좋습니다. 이제 본격적으로 예제 코드를 통해 JavaScript에서 날짜와 시간을 표현하는 방법을 알아보겠습니다. 먼저, Date
객체를 생성하는 다양한 방법에 대해 알아봅시다. 표현하고자 하는 날짜는 모두 2022년 3월 9일 자정입니다.
// 1. 날짜와 시간 정보를 각 인자로 넣어 생성하기
// - 현지 시각 기준으로 시간을 넣어야 함
// - 시, 분, 초, 밀리초는 생략 가능
// - 월은 0부터 시작하므로 주의
new Date(2022, 2, 9);
new Date(2022, 2, 9, 0, 0, 0, 0);
// 2. 날짜와 시간 정보 문자열
// - 현지 시각 기준으로 시간을 넣어야 함
// - ISO 8601 표준이 아닌, RFC 2822 라는 별도의 방식
// - 표준은 아닌데 관례적으로 지원하는 경우, 따라서 쓰지 않는 게 좋음
new Date('Mar 9 2022');
new Date('March 9 2022');
new Date('Mar-9-2022');
new Date('Mar 9 2022 00:00:00');
// 3. 밀리초 단위의 유닉스 타임
// - 해당 시간을 나타낼 수 있는 고정 숫자 값
new Date(1646751600000);
// 4. ISO 8601 표준 문자열
// - Z가 있으면 UTC 기준, 없으면 현지 시각 기준
// - 중요! Z가 없어도 `YYYY-MM-DD`만 있는 형태(ex: '2022-03-09')면 UTC로 간주함
// - https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Date/Date
// - T는 생략 가능
new Date('2022-03-09 00:00:00'); // 현지 시각 기준
new Date('2022-03-08T15:00:00Z'); // UTC 기준
// 5. 시간대를 명시적으로 표현한 문자열
// - 시간대를 표현하는 방법도 다양함
new Date('Mar 9 2022 00:00:00 GMT+09:00');
new Date('Mar 9 2022 00:00:00 UTC+09:00');
new Date('Mar 8 2022 15:00:00 +00:00');
new Date('Mar 9 2022 00:00:00 +9');
new Date('Mar 9 2022 00:00:00 +0900');
new Date('2022-03-09 GMT+9');
new Date('2022-03-09 UTC+9');
방법이 매우 다양하고 복잡하기 때문에 외우기보다는 필요에 따라 검색해 쓰는 것이 좋아 보입니다. 다만 4번 규칙은 ISO 8601 표준이기도 하고, 예외적인 규칙이 있다 보니 외워두면 좋을 것 같네요.
Date
객체의 일부 메서드는 UTC 기반의 값을 반환하고, 다른 메서드는 현지 시각 기반의 값을 반환한다는 점에서 주의가 필요합니다.
// 한국 시간 기준으로 2022년 3월 9일 0시 0분 0초
// UTC 기준으로는 2022년 3월 8일 15시 0분 0초
const date = new Date('2022-03-09 00:00:00');
// 1. 유닉스 타임을 반환하는 메서드
date.getTime(); // 1646751600000
date.valueOf(); // 1646751600000
// 2. UTC 기반의 값을 반환하는 메서드
date.getUTCFullYear(); // 2022
date.getUTCMonth(); // 2
date.getUTCDate(); // 8
date.getUTCDay(); // 2 (화요일)
date.getUTCHours(); // 15
date.getUTCMinutes(); // 0
date.getUTCSeconds(); // 0
date.getUTCMilliseconds(); // 0
date.toISOString(); // '2022-03-08T15:00:00.000Z'
date.toJSON(); // '2022-03-08T15:00:00.000Z'
date.toGMTString(); // 'Tue, 08 Mar 2022 15:00:00 GMT'
date.toUTCString(); // 'Tue, 08 Mar 2022 15:00:00 GMT'
// 3. 현지 시각 기반의 값을 반환하는 메서드
date.getFullYear(); // 2022
date.getMonth(); // 2
date.getDate(); // 9
date.getDay(); // 3 (수요일)
date.getHours(); // 0
date.getMinutes(); // 0
date.getSeconds(); // 0
date.getMilliseconds(); // 0
date.toString(); // 'Wed Mar 09 2022 00:00:00 GMT+0900 (대한민국 표준시)'
date.toDateString(); // 'Wed Mar 09 2022'
date.toLocaleString(); // '2022. 3. 9. 오전 12:00:00'
date.toLocaleDateString(); // '2022. 3. 9.'
date.toLocaleTimeString(); // '오전 12:00:00'
date.toTimeString(); // '00:00:00 GMT+0900 (대한민국 표준시)'
지금까지의 모든 설명을 이해했다면, 제일 위에서 봤던 생일 축하 예제 코드가 왜 이상하게 동작했는지를 완벽하게 이해할 수 있습니다.
우선 출력은 별도의 메서드를 쓰지 않았기 때문에 모두 현지 시각 기반으로 출력됩니다.
new Date('2020-03-09');
// Mon Mar 09 2020 09:00:00 GMT+0900 (한국 표준시)
예제의 첫 번째 코드는 ISO 8601 표준 문자열을 인자로 넘겼습니다. 하지만 이 형태가 만약 YYYY-MM-DD
라면 UTC로 해석하기 때문에, 한국 기준으로 오전 9시가 뜨는 것이 정상입니다.
new Date('March 9, 2020');
// Mon Mar 09 2020 00:00:00 GMT+0900 (한국 표준시)
예제의 두 번째 코드는 ISO 8601 표준 문자열이 아닌 문자열을 넘겼습니다. 이 경우에는 현지 시각 기준으로 해석하기 때문에, 한국 기준으로 오전 12시가 뜨는 것이 정상입니다.
new Date('2020-03-09T00:00:00Z')
// Mon Mar 09 2020 09:00:00 GMT+0900 (한국 표준시)
예제의 세 번째 코드는 ISO 8601 표준 문자열을 넘겼지만, UTC 기준으로 해석하기 위해 Z
를 붙였습니다. 이 경우에는 UTC 기준으로 해석하기 때문에, 한국 기준으로 오전 9시가 뜨는 것이 정상입니다.
new Date('2020-03-09 00:00:00')
// Mon Mar 09 2020 00:00:00 GMT+0900 (한국 표준시)
예제의 네 번째 코드는 ISO 8601 표준 문자열과 유사하지만 Z
가 생략되었기 때문에 현지 시각 기준으로 해석됩니다. 따라서 한국 기준으로 오전 12시가 뜨는 것이 정상입니다.
지금까지 우리는 Date
객체의 생성자에 어떤 인자를 넣는지, 그리고 어떤 메서드를 호출하는지에 따라 UTC 시각이 나올 수도 있고 현지 시각이 나올 수도 있다는 것을 확인했습니다. 대부분의 오류는 이러한 Date
의 혼잡스러운 설계로부터 발생한 것을 알 수 있습니다.
그렇다면 JavaScript에서 이러한 날짜와 시간 관련 오류를 방지하기 위해서는 어떻게 해야 할까요? 크게 세 가지 원칙을 말해볼 수 있을 것 같습니다.
|
사실 솔직히 얘기하자면, 위 내용 다 필요 없고 그냥 라이브러리 쓰면(…) 편합니다. 라이브러리의 경우에는 날짜의 입력과 출력 기준이 모두 통일되어 있기 때문에 이러한 오류를 신경 쓰지 않아도 되기 때문에 편리하죠. 특히 서머타임을 다뤄야 하는 경우에는 라이브러리를 사용하지 않으면 매우 번거로운 작업을 해야 합니다.
뭐, 기껏 설명해 놓고 ‘그냥 라이브러리 쓰는 게 좋다!’라고 결론이 이어지는 게 우습기는 합니다. 하지만, 그만큼 날짜와 시간을 다루는 것이 쉽지 않고 신경 써야 할 부분이 많다는 것을 알고 계셨으면 좋겠습니다.