holdanddeepdive/javascript-deep-dive

프론트엔드 타임존 관련 세션

Opened this issue · 0 comments

https://forward.nhn.com/2020/session/16

프런트엔드 타임 존 뽀개기: 글로벌 서비스를 위한 타임 존 좌충우돌 개발기

타임존의 탄생

영국 그리니치 천문대(GMT)가 +00:00으로 기준 시간대가 된다. 여기에 경도별로 조정된 시간의 차이를 오프셋이라고 한다. 대략적인 오프셋으로 나누게 되는데 이것을 타임존이라고 한다. 타임존과 오프셋은 1:1 매칭 관계가 아니다. 해가 길어질 때 오프셋이 달라지게 된다. (Daylight Saving Time, DST) => 개발자의 괴로움

브라우저의 타임존 지원 기능과 한계

// 대한민국
const d = new Data();
d.getTimezoneOffset() / 60; // -9

브라우저는 ECMAScript 명세를 구현하고, 명세는 타임존과 서머타임에 대해 정의한다. 그러나 문제는 정의가 모호하다는 것! => ECMAScript 구현체 (ex. 브라우저)는 현지 일광 절약 시간 조정을 결정하기 위해 최선을 다할 것으로 예상됩니다.

브라우저와 Node.js도 타임존 변경 기능을 지원하지 않으며 OS 타임존을 따르고 있다.

타임존의 규칙을 알면 해결할 수 있지 않을까? 🤔
-> No Rules! 나라 상황에 맞게 변경하는 역사가 있으므로 규칙을 만들 수 없다.
규칙이 없으므로 프로그래밍으로 해결할 수 없고 DB로 관리해야 할 수 밖에 없다.
ECMAScript 명세에서는 IANA 타임존 DB를 사용하도록 권고한다.

프론트엔드에서 해결 방법

타임존 오프셋만 정확하게 구할 수 있다면, 다른 타임 존으로 변경이 가능하다.
프론트에서 타임존 변경을 구현하는 방법은 2가지가 있다.

  1. 타임존 데이터베이스를 JS 소스에 내장하는 방법
  2. DateTimeFormat 표준 스펙을 활용하는 방법

타임존 데이터베이스를 JS 소스에 내장하는 방법

대부분의 브라우저에서 사용 가능하다. 특히 오래된 브라우저를 지원할 때 좋다.
타임존 데이터베이스에서 변경 내역을 조회하여 타임존 오프셋을 구한다.
moment.js는 타임존 DB를 내장하고 타임존 병경을 지원하기 때문에 많이 사용한다.

이 방법을 선택하게 되면 지원해야 하는 지역이 많아지면 해당하는 데이터를 모두 가지고 있어야 하므로 메모리 부담도 함께 증가하게 된다. 또한 기간별로도 갖고 있어야 한다.
2020년 9월 15일 moment.js는 더 이상 추가 개발을 하지 않고 유지보수 모드로 전환한다고 발표하기도 했다.

DateTimeFormat으로 타임존 오프셋 구하기

Intl은 ECMAScript 국제화 API이며 DateTimeFormat은 언어에 맞는 날짜 및 시간 서식을 지원하는 객체의 생성자를 의미한다. 타임존 오프셋을 얻기 위해 DateTimeFormat을 사용한다.

권장하지 않는 방법

const date = new Date();
const options = {
  timeZone: 'America/Los_Angeles',
  timeZoneName: 'short'
};
console.log(new Intl.DateTimeFormat('ko-KR', options).format(date);
// 2020. 12. 14. GMT-8

리턴되는 문자열은 Locale과 전달한 타겟 타임존에 맞게 출력이 된다.
여기서 GMT-8을 파싱하면 오프셋을 얻을 수 있지 않을까?
=> ❌ 리턴되는 문자열을 파싱하는 것은 좋지 않다. 브라우저마다 동작이 다를 수 있고, 버전별로도 포맷이 달라질 수 있기 때문이다. (ex. 크롬은 보이지만 사파리는 보이지 않는다)

권장하는 방법

const formatter = new Intl.DateTimeFormat('en-us', {
  hourCycle: 'h23',
  year: 'numeric',
  month: 'numeric',
  day: 'numeric',
  hour: 'numeric',
  minute: 'numeric',
  second: 'numeric',
  timeZone: 'America/Los_Angeles',
});

formatter.format(new Date('2020-12-14T10:00:00'));
// "12/13/2020, 17:00:00"

formatter.formatToParts(new Date('2020-12-14T10:00:00+09:00'));
/*
0: {type: "month", value: "12"}
1: {type: "literal", value: "/"}
2: {type: "day", value: "13"}
3: {type: "literal", value: "/"}
4: {type: "year", value: "2020"}
5: {type: "literal", value: ", "}
6: {type: "hour", value: "17"}
7: {type: "literal", value: ":"}
8: {type: "minute", value: "00"}
9: {type: "literal", value: ":"}
10: {type: "second", value: "00"}
*/

결과로 리턴된 배열을 루프를 돌면서 날짜 파트를 추출한다. 한국의 2020년 12월 14일 오전 10시는 로스앤젤레스에서 2020년 12월 13일 17시로 변경이 된 것이다!

function getOffset(parts, date) {
  const [v, M, d, h, m, s] = parts;
  const utc = new Date(Date.UTC(y, M-1, d, h, m, s));
  const offset = (utc - date) / 60 / 1000;
  
  return offset;
}

const december 

위와 같은 방법으로 프론트에서도 타임존을 정확하게 변경하는 것이 가능하다.
단 성능이 느리므로, DateTimeFormat 인스턴스는 캐싱해서 사용하는 것이 좋다!
(또한 IE에서 지원이 되지 않는다,,, 🤦)