글로벌 서비스의 프론트엔드 개발자는 날짜를 섬세하게 다뤄야 한다. 당연한 이야기지만 우리 서비스를 사용하는 유저가 어느 도시, 어떤 시간대에 살고 있을지 모르기 때문이다. 서버에서라면 늘상 같은 시간대(주로 GMT)를 기준으로 날짜 관련 로직을 구성해도 큰 이슈가 생기지 않겠지만, 프론트엔드라면 유저의 브라우저나 로컬 환경의 시간대가 디폴트인 점을 잊어서는 안 된다.
현재 시간을 기준으로 로직을 만들어야 할 때
서버에서 내려주는 날짜를 그대로 사용할 수 있다면 좋겠지만, 유저가 해당 게시물을 보고있는 바로 그 시점을 기준으로 클라이언트에서 날짜를 포맷해야 하는 경우가 있다. 예를 들어,
- 현재 시간을 기준으로 남은 날짜를 보여주는 경우 (D-day)
앨범 발매일까지 **D-5**
특정 날짜가 고정된 문자열(YYYY-MM-DD
)로 주어진다면, D-day를 구하는 로직은 어떻게 작성해야 할까? 자바스크립트에서 흔히 사용되는 날짜 라이브러리인 dayjs를 사용하면 간편하게 다음과 같은 함수를 만들 수 있다.
const getDday = (targetDate: string) => {
const today = dayjs().format('YYYY-MM-DD');
const date = dayjs(targetDate).format('YYYY-MM-DD');
// 오늘이 특정 날짜 이전이라면 남은 일수를 계산해 반환한다
if (dayjs(today).isBefore(date, 'day')) {
const diff = dayjs(date).diff(today, 'day');
return `D-${diff}`;
}
// 오늘이 특정 날짜 이후라면 지난 일수를 계산해 반환한다
if (dayjs(today).isAfter(date, 'day')) {
const diff = dayjs(today).diff(date, 'day');
return `D+${diff}`;
}
return 'D-day';
};
console.log(getDday('2024-03-09')); // 오늘이 2024-03-08이라면 'D-1' 반환
겉보기에는 문제 없어보이는 로직이지만, 만약 targetDate
가 한국 표준시(KST)로 주어지는데 유저는 영국(GMT)에 있다면 어떨까? 아래와 같은 상황인 거다.
- 앨범 발매일: 2024년 3월 9일 오전 6시 (KST)
- 현재시간(한국 기준): 2024년 3월 8일 오전 6시 (KST)
- 현재시간(영국 기준): 2024년 3월 7일 오후 10시 (GMT)
이 상태에서 한국에 있는 유저가 getDday
함수를 호출한다면 D-1
을, 영국에 있는 유저가 getDday
함수를 호출한다면 D-2
를 반환받게 된다. 하지만 앨범 발매일은 KST 기준이므로, 영국에 있는 유저에게도 남은 일자는 D-1
로 표기되어야 한다. 앨범 발매일을 GMT 기준으로 수정한다면 2024년 3월 8일 오후 10시이기 때문이다.
- 현재 시간을 기준으로 시간이 얼마나 경과했는지 보여주는 경우 (relative time)
댓글 작성일: 25분 전
마찬가지로 지금 시간이 특정 시간에서 얼마나 경과되었는지 보여주어야 할 때도 같은 실수가 발생할 수 있다. 단순히 현재시간에서 연, 월, 일, 시, 분, 초 단위로 경과 시간을 표기하고 싶다면 아래와 같이 day.js의 relative time 플러그인을 사용하면 된다.
dayjs.extend(relativeTime);
const getRelativeTime = (targetDate: string) => {
return dayjs(targetDate).fromNow();
};
댓글 작성일시가 2024년 3월 8일 정오(KST)고, 유저의 현재 시간이 당일 오후 1시(KST)라면 getRelativeTime
함수는 an hour ago
라는 문자열을 반환할 것이다. 하지만 유저가 영국에서 접근한다면 유저의 현재 시간은 당일 오전 오전 4시(GMT)일 것이고, getRelativeTime
함수는 10 hours ago
라는 값을 반환하게 된다. 유저가 한국에 있든 영국에 있든 1시간 전에 작성된 댓글은 동일하게 1시간 전에 작성된 건데, 현재 시간이 각자 다르다 보니 잘못된 값을 반환하는 것이다. 이런 실수를 방지하려면 두 가지 방법이 있다.
-
주어진 기준 일자를 유저가 속한 시간대로 변경한다.
dayjs.extend(utc) dayjs.extend(timezone) dayjs.extend(relativeTime); const getRelativeTime = (targetDate: string) => { const userTz = dayjs.tz.guess(); // 유저의 time zone을 반환 (ex: America/Chicago) const koreanTargetDate = dayjs(targetDate).tz('Asia/Seoul', true); // targetDate를 Dayjs 객체로 변환 const convertedTargetDate = koreanTargetDate.tz(userTz); // 날짜를 Chicago 시간대로 변경 return dayjs(convertedTargetDate).fromNow(); };
-
유저의 현재 시간을 주어진 시간대 기준으로 변경한다.
dayjs.extend(utc) dayjs.extend(timezone) dayjs.extend(relativeTime); const getRelativeTime = (targetDate: string) => { const now = dayjs().tz('Asia/Seoul'); // 유저가 어디에 있든 서울의 현재 시간을 반환 const convertedTargetDate = dayjs(targetDate).tz('Asia/Seoul', true); // targetDate를 Dayjs 객체로 변환 return now.to(convertedTargetDate); };
두 가지 방법 중 우리 서비스는 2번째 방법(현재 시간을 무조건 Asia/Seoul
기준으로 반환)을 택했다. 일단 DB에 저장되는 날짜string
이 모두 Asia/Seoul 기준이었기에, 현재 시간도 Asia/Seoul 기준으로 반환하는 것이 개념적으로 더 직관적이고 더 적은 변환 과정을 거칠 수 있었기 때문이다. 하지만 서비스의 컨셉이나 방향성에 따라 전자의 방법으로 개발하는 것이 더 좋은 가독성을 가질 수 있을 것도 같다.
마찬가지로 getDday 함수도 아래와 같이 수정해볼 수 있다.
// 방법1
const getDday = (targetDate: string) => {
const today = dayjs().tz('Asia/Seoul').format('YYYY-MM-DD'); // 오늘 날짜를 서울 시간대로 반환
const date = dayjs(targetDate).tz('Asia/Seoul', true).format('YYYY-MM-DD');
// ...
};
// 방법2
const getDday = (targetDate: string) => {
const targetDateBySeoul = dayjs().tz('Asia/Seoul', true); // targetDate를 서울 기준 dayjs 객체로 변환
const userTz = dayjs.tz.guess(); // 유저의 time zone을 반환 (ex: America/Chicago)
const today = dayjs().format('YYYY-MM-DD'); // America/Chicago 기준 현재 시간
const date = targetDateBySeoul.tz(userTz).format('YYYY-MM-DD'); // America/Chicago 기준 targetDate
// ...
};