[FrontEnd] 자바스크립트로 날짜 다루기
회사에서 결제 내역을 보여주는 화면을 개발하다 결제 일시가 내가 원하는 방식대로 렌더링되지 않는다는 문제에 부딪혔다.
이 문제를 해결하기 위해 브라우저 환경에서 날짜와 시간을 다루는 방식에 대해 알아보게 되었다. 구글링을 통해 날짜 형식 포맷팅하는 방법을 알 수 있었다. 그러나 단순한 구글링만으로 문제를 해결하기에는 UTC 시간대, 타임존 등 소프트웨어에서 날짜 및 시간을 다루는 방식이 궁금했기 때문에, 타임존에 대한 이해부터 시작해, 자바스크립트에서 어떻게 타임존을 기반으로 날짜 데이터를 다루는지 정리해보고자 한다. NHN 기술블로그에 잘 정리된 글(자바스크립트에서 타임존 다루기)이 있어, 상당 부분 참고했다.
타임존에 대한 이해
타임존이란
타임존(Timezone)이란, 동일한 로컬 시간을 따르는 지역을 의미한다. 각 국가 별로 법적으로 지정하기 때문에, 국가 별로 모두 다르다. 따라서 공통된 기준으로 타임존을 표현하고자 할 경우, 시간의 국제 표준이 필요하게 된다. 국제 표준 시간 중 가장 많이 쓰이는 시간으로는 GMT, UTC가 있다.
- 그리니치 평균시(Greenwich Mean Time, a.k.a. GMT): 경도 0도에 위치한 영국 그리니치 천문대 기준 시간
- 협정 세계시(Coordinated Universal Time, a.k.a. UTC): 세슘 원자의 진동수에 기반한 국제 원자시 기준 시간. 지구 자전 주기의 흐름이 조금씩 늦어진다는 문제점이 있어, GMT를 대체하여 제정된 시간
보통 GMT와 UTC는 표준시 개념을 의미할 때 혼용하여 사용되곤 하지만, 엄밀하게는 다른 시간이다(물론 그 차이는 아주 미세하다). 다만, 소프트웨어에서 국제 표준시를 의미할 때는 UTC를 사용하는 게 보다 일반적이다.
협정 세계시를 기준으로 조정된 시간의 차이를 오프셋이라고 한다. 예컨대 UTC-04:00
은 협정 세계시를 기준으로 4시간이 느리다는 의미이다. 이 오프셋을 기준시로 하여 국가별로 자신들이 사용하는 타임존에 고유의 이름을 부여한다. 한국 타임존의 경우, KST라는 이름을 사용하며, UTC+09:00
오프셋을 사용한다. 오프셋은 반드시 1시간 단위일 필요는 없다.
오프셋과 타임존 이름이 1대 1로 매칭되는 것은 아니다. +09:00
오프셋은 한국 뿐만 아니라 일본, 인도네시아 등 여러 나라에서 사용하기 때문이다. 또한, 한 국가에서 하나의 타임존만을 사용해야 하는 것도 아니다. 예컨대, 서머타임(하절기에 표준시를 원래 시간보다 한 시간 앞당긴 시간으로 이용, a.k.a. DST)을 적용하는 국가도 있다. 또한, 한 국가의 정치적, 경제적 상황에 따라 어떤 오프셋을 기준시로 사용할지도 달라진다. 역사적으로 타임존이 계속해서 바뀌어 왔을 수 있다는 의미다.
표준 타임존 데이터베이스
이와 같이 지역마다, 시점마다 한 타임존은 여러 개의 오프셋을 가질 수 있기 때문에, 소프트웨어에서 타임존을 규칙에 따라 시스템화하는 경우 문제가 발생할 수 있다. 예컨대, 스마트폰의 기준시를 오프셋을 이용해 지정하는 상황이다. 만약, 사용자가 서머타임이 적용되는 국가에 거주하는 경우, 기존 표준시와 DST를 묶어서 하나의 타임존으로 인식해야 한다. 이와 같은 상황에서 범용적으로 사용할 수 있는 타임존 관리 표준이 필요하게 된다. 여러 가지 데이터베이스가 있지만, 그 중에서 가장 신뢰할 만한 표준은 IANA time zone database(tz database, tzdata)이다.(예전에 파이썬에서 타임존을 다룰 때 사용했던 pytz
라이브러리는 이 tz databse를 파이썬으로 사용할 수 있도록 옮겨 놓은 라이브러리인 것이다!)
결과적으로 소프트웨어를 구현할 때 타임존은 생각보다 복잡한 개념이고, 이를 정확하게 계산하기 위해 표준화된 데이터베이스가 존재하며, 무엇이 되었든 언어마다 타임존을 지원할 때에는 이러한 표준 타임존 데이터베이스를 사용하게 된다. 다만, 자바스크립트는 다른 언어에 비해 타임존 지원이 부족한 편이라고 한다. 이 부분에 대해서는 내가 다른 언어의 타임존 지원에 대해 정확하게 알지 못하기 때문에, 참고한 글에서 본 내용을 요약하고자 한다.
- 명시된 표준 시간 데이터베이스가 없다.
- 다만, IANA Time Zone Database를 권장한다. 이로 인해 브라우저 환경마다 타임존 연산이 다르게 동작할 수도 있다.
브라우저 환경에서 타임존 다루기
프런트엔드 개발을 하며 타임존을 다룰 때 주로 고려해야 할 상황은 다음과 같은 상황이다.
- 파싱: 브라우저 환경에서의 날짜 데이터(예컨대, 사용자의 날짜 입력 값)를 타임존 데이터 형태로 변환하는 상황
- 포맷팅: 서버에 저장된 날짜 데이터를 사용자의 타임존에 맞게 변환하는 상황
참고: 서버에 저장되는 타임존 데이터
서버에 저장되는 타임존 데이터는 동일한 절댓값이어야 한다. 이 데이터는 날짜 및 시간 정보가 동일한 오프셋(UTC)에 맞춰져 있거나, 해당 클라이언트 환경의 타임존 정보까지 포함한 값이어야 한다. 보통 다음의 두 가지 형태이다.
- 유닉스 시간: UTC 기준, 숫자 타입. 1970년 1월 1일 00:00:00 UTC 기준 경과 시간을 초로 환산하여 정수로 나타낸 것.
- ISO-8601: 오프셋 정보 포함, 문자열 타입. 날짜 및 시간과 관련된 데이터 교환을 다루는 표준 표기법 중 하나로, 규칙에 따라 문자열로 나타낸 것.
특히, 동일한 서버 데이터에 접근하는 클라이언트가 서로 다른 타임존에 있는 경우, 문제가 될 수 있는 시나리오가 있다. 예컨대, 한국에 있는 고객이 미국 회사의 제품을 2021년 7월 20일 오후 2시에 주문했다고 하자. 그러면 서버에는 한국 서울 시간을 기준으로 한 2021년 7월 21일 오후 2시 데이터가 저장되고, 이것을 한국에 있는 고객이 주문 시각을 확인할 때에는 2021년 7월 20일 오후 2시로, 미국에 있는 회사에서 주문 시각을 확인할 때에는 2021년 7월 20일 오전 1시로 클라이언트 환경에 나타나야 할 것이다.
(타임존 지원이 미약하다고는 하지만) 자바스크립트에서 타임존과 관련해 날짜 데이터를 다룰 수 있는 방법은 다음의 두 가지가 있다.
- 자바스크립트 Date 객체 사용
- 자바스크립트 moment 라이브러리 사용
후자의 경우, 자바스크립트에서 날짜 연산 시 표준처럼 자리 잡은 라이브러리라고 한다.
라이브러리 없이 Date 객체만을 사용해서도 날짜와 관련된 작업을 충분히 처리할 수 있다. 사용법, 메소드를 하나 하나 정리하는 것보다는, Date 객체에 대한 documentation를 필요할 때 참고하도록 하자. 다만, 정리하고자 하는 부분은 다음과 같다.
- Date 객체는 내부적으로 유닉스 시간과 같은 절댓값을 이용해 시간 데이터를 관리한다.
- 그러나 생성자
Date()
나 메소드(parse()
,getHour()
등)는 브라우저가 실행되는 운영체제에 설정된 타임존(즉, 로컬 타임존)의 영향을 받는다. - Date 객체는 불변 객체가 아니고, month의 경우 0부터 시작하므로 사용 시 1을 더해주어야 한다.
문제의 해결
결국 내가 겪은 문제는 서버에 저장된 데이터를 들고 와서 어떻게 포맷팅할 것인가의 문제였다. 서버에 저장되어 있는 데이터가 ISO-8601 형태의 문자열이기 때문에 YYYY-MM-DD
와 같은 식의 친숙한 형태의 포맷으로 바꾸어 렌더링해야 한다.
참고: ISO-8601 문자열에서의 T와 Z
도대체 ‘2021-07-08T05:46:54.000Z’에서 T와 Z가 무엇인지 궁금했는데, 위키피디아를 참고한 결과 다음과 같다고 한다.
- T: 날짜와 시간을 함께 표현하는 혼합일시 표현에서 날짜 뒤에 시간이 이어짐을 알리기 위한 구분
- Z: UTC 시간대. 특정 시간대를 나타내고 싶은 경우, Z가 아니라 ‘2021-07-08T05:46:54.000Z+09:00’와 같이 오프셋을 명시
내가 렌더링되기 원하는 방식은 YYYY-MM-DD
이다. 이와 같은 형식으로 포맷팅할 때, 가장 쉬운 방식은 T를 기준으로 문자열을 자르는 것이다. 그러나 이 경우, 서버에 저장된 데이터가 UTC 시간대이기 때문에, ‘2021-07-20T23:58:00.000Z’와 같은 데이터의 경우, 실제 KST 기준으로는 2021년 7월 21일 오전 8시 58분인 데이터를 ‘2021-07-21’로 표현하게 된다.
뻘짓: 굳이 굳이 T를 기준으로 문자열을 자르고 싶다면?
서버에 저장된 시간이 UTC 시간대이므로, 이를 유닉스 타임스탬프로 바꾼 뒤, 그 시간에 KST 타임존의 오프셋을 더한 타임스탬프(UTC 기준 9시간이 빠르기 때문에 9시간에 해당하는 밀리초 만큼을 더한 타임스탬프)를 다시 ISO string 형태로 변환하면 되긴 한다. 그러나 이는 원래 시간대를 변경한 것이기 때문에 좋지 않은 방식이다. 자바스크립트의
.toISOString()
메소드가 UTC 시간대 기준 ISO string을 변환하기 때문에, T를 기준으로 자르기 위해서 편법을 쓴 것일 뿐이다.var t = Date.parse('2021-07-20T23:58:00.000Z'); // 1626825480000 var t2 = new Date(t); // Wed Jul 21 2021 08:58:00 GMT+0900 (대한민국 표준시) var t3 = t + 9*60*60000; var t4 = new Date(t3); // Wed Jul 21 2021 17:58:00 GMT+0900 (대한민국 표준시) var t5 = new Date(t3).toISOString(t4); // "2021-07-21T08:58:00.000Z"
따라서 서버에 저장된 날짜 데이터를 Date 객체로 만들고, Date 객체의 메소드를 사용해 포맷팅하는 방식으로 해당 문제를 해결했다. 아래는 바뀐 코드이다. 이후 날짜 변환하는 부분을 utils 함수로 분리할 예정이다. (정리하고 보니 구글링하면 나오던 해결 방법…)
<TableBody key = 'payment-history-table'>
{tableData
.slice(page * rowsPerPage, (page + 1) * rowsPerPage)
.map((item) => {
const {
paymentId,
paymentAt,
paymentTotal,
paymentAccount,
paymentType,
refundFlag,
} = item;
// 날짜 변환
const year = paymentAt.getFullYear();
const month = (paymentAt.getMonth()+1).toString().padStart(2, '0');
const day = (paymentAt.getDate).toString().padStart(2, '0');
const convertedPaymentAt = `${year}-${month}-${day}`
...
})}
댓글남기기