본문 바로가기
Archive

나를 괴롭히던 Date 정리

by livemehere 2022. 8. 30.

Date때문에 얼마나 삽질을 많이했는지 모른다.

단순히 출력할때야 dayjs 나 momentjs 로 간단히 해결하지만

node 서버에서 db와 연동할때, UTC 타임존 때문에 처음엔 너무 해깔렸고,

특히나 스케줄링 서버를 작업할때, 스케줄링이 ec2의 로컬타임존을 따라가면서, 예상했던 한국시간에 계속 맞지 않았던 문제가 있었다.

 

해결방법은 ec2의 타임존을 변경하거나, 날짜 객체를 만들때마다 UTC로 변경해주거나, 아니면 스케줄링모듈이나 데이터베이스의 타임존을 한국시간에 맞추거나인데, 결론적으로 UTC에 맞춰야하는게 맞다는 판단이 들었고, 지금은 그렇게 해결하고있다.

왜냐하면 AWS RDS는 타임존을 UTC로 고정하기 때문에, 내가 다른 데이터베이스를 사용할때 timezone을 한국으로 맞춰놓는데 익숙해진다면 안될거 같다는 생각이 들었다. 아마존이 정답은아니지만, 타임존을 변경하지 못하게하는데는 이유가있지 않을까..?

 

사실 아마존입장에서는 전세계에 서비스를 하는데, 타임스탬프로 UTC타임을 따르고, 가져다가 쓰는 local환경에서 그 환경에 맞게 변환해서 쓰는게 더 유연하다고 생각이 든다.

다만 이때, 시간을 가지고 조회를 하는 작업이 필요하다면 Date로 만든 객체를 UTC 객체로 만들어 사용해야한다.

 

Z가 의미하는 것

date객체를 만들면 시간 끝에 z가 붙는데 이건 어떤걸 의미하는지 잘 몰랐다.

console.log(new Date()) // 2022-08-30T06:30:05.442Z
console.log(new Date(0)) // 1970-01-01T00:00:00.000Z

 

기본적으로 Date는 생성되고서 UTC기준으로 시간을 생성합니다. ( 한국에서 new Date()를 하면 -9시간이 된 GMT 00:00 기준의 시간을 생성해준다)

 

참고로 브라우저에서는 localtime존에 맞춰서 시간을 연산한 결과를 반환한다. (브라우저에서 new Date()는 +09:00 한 시간이 표시된다.

 

node 환경의 타임존 알아내는법

console.log(Intl.DateTimeFormat().resolvedOptions().timeZone) // Asia/Seoul

Z는 Zulu 는 Z의 나토 포네틱 코드로 군사용어라고 합니다.

즉, Zero를 의미한다.

이를 이해하려면 세계협정시를 알아야한다.

중학교때 사회시간에서 배웠던 기억이 나는데, 전세계의 기준이되는 시간이 영국의 그리니치 천문대를 기준으로 한 시간이다.

이른 GMT(Greenwich Mean Time) 이라고하는데, 이 시간을 기준으로 몇시간이 차이가 나는지를 계산한다.

그럼 GMT는 기준이니까 0시간이 차이가 나는것이다.

이표현을 Z로하거나 마지막에 GMT +00:00 이런식으로 표현할 수 있다.

 

시간끝에 붙은 Z는 군사용어를 뜻한다고 했는데, Z는 zulu 를 표현하고, 다른 표현들도 있다.

https://en.wikipedia.org/wiki/List_of_military_time_zones

 

toString(), toISOString()

toString()으로 문자열로 변경하면 프로그램이 돌아가는 timezone을 따라서 변경되고,

GMT 00:00 을 기준으로 변경하려면 toISOString()을 사용한다.

const event = new Date('05 October 2011 14:48 UTC');
console.log(event.toString());
// expected output: Wed Oct 05 2011 16:48:00 GMT+0200 (CEST)
// (note: your timezone may vary)

console.log(event.toISOString());
// expected output: 2011-10-05T14:48:00.000Z

new Date(milliseconds)

숫자를 인자로 넣으면 UTC+0 기준(1970년 1월 1일 0시 0분 0초)으로 milliseconds 만큼 후의 시점을 반환한다.

console.log(new Date(0)) // 1970-01-01T00:00:00.000Z
console.log(new Date(1000 * 60 * 60 * 24)) // 1970-01-02T00:

이렇게 1970년 첫날을 기준으로 흘러간 밀리초를 나타내는 정수를 timestamp 라고한다.

현재의 timestamp를 알아내는것이 Date.now() 이다.

 

그래서 정신건강이 편하려면 내 머리속의 시간을 모두 UTC+0 에 맞추면되고, 여러 클라이언트에서 혹시나 localtimezone의 시간을 보내면 어떻하나? 라는 걱정이있다면, 그냥 timestamp로 시간을 주고받으면 조금 낮다..

 

이렇게 그냥 문자열로 생성해도, UTC +0의 시간으로 생성한다.

console.log(new Date('2022-02-03')) // 2022-02-03T00:00:00.000Z

 

주의할 점은 string인자 하나로 작성할때는, 마지막에 Z의 유무가 중요하다.

z가 붙으면 이미 UTC+0 시간임을 명시해서, 그대로 만들지만, 그렇지않다면

localtime에서 만들어진 것을 감안하여, 차이나는 시간만큼 차감하여 UTO +0 으로 변경한다.

console.log(new Date('2022-02-03T00:00:00.000Z')) // 2022-02-03T00:00:00.000Z
console.log(new Date('2022-02-03T00:00:00.000')) // 2022-02-02T15:00:00.000Z  : -9시간

Date.parse(str)

위에서 string으로 날짜객체를 만드는 알고리즘은 Date.parse에 정의되어있다.

명시적으로 생성할 수도있다.

let msUTC = Date.parse('2012-01-26T13:51:50Z');
let msLOCAL = Date.parse('2012-01-26T13:51:50');

console.log(msUTC) // 1327585910000
console.log(msLOCAL) // 1327553510000

console.log(((msLOCAL - msUTC) / 1000) / 3600) // -9

 

 

Date객체에서 timestamp 추출하기

new Date('2022-02-03').getTime()

 

숫자인자 각각으로 생성하기

year : YYYY

month : 0  ~ 11

day: : 0 ~ 6

date : default  = 1

hours,minutes,seconds,ms : default = 0

console.log(new Date(2022,2,4)) // 2022-03-03T15:00:00.000Z

 

해깔리지 않기

나는 처음에 이런 개념들을 잘 모르고 왜자꾸 9시간 차이가 나는거지? 라는 생각으로 계속 시간을 변경햇다.

그런데 여러 시행착오를 겪다보니, 결국 표현방식만 다를뿐이지 같은 순간을 나타내는 시간들이었다.

내가 착각을 했던것이, UTC+0 시간이 사용자에게 보여지는 것 때문에 혼란을 야기할 수 있다 생각했는데,

그냥 모든 시간을 주고받을때는 UTC+0 기준으로 주고받고, 연산하고, 결과값만 클라이언트의 localtimezone에 맞추면 되었다.

그러니 시간에서 항상 Z가 붙어있는지의 여부를 보고 내 로컬타임에 맞게 변경해야하구나를 생각해야한다.

 

Date 메서드는 모두 local time 기준이다.

그냥 new Date() 를 출력하면 UTC+0 시간대가 출력되지만,

브라우저에서 사용할때, 혹은 이런 메서드들로 년,월,일,시간,요일 을 뽑아낼때는 현재 시간 기준으로 나타낸다.

즉, 사용할때는 현지시간을 기준으로 표현해준다고 생각하면된다.

 

그래서 명시적으로 UTC가 붙은 메서드들을 제공해준다.

let now = new Date();
console.log(now) // 2022-08-30T07:57:35.930Z
console.log(now.getTime()) // timestamp 반환
console.log(now.getTimezoneOffset()) // -540 (60 * 9) local time과 UTC+0 시간 차를 반환함

// local timezone 기준
console.log(now.getFullYear())
console.log(now.getMonth())
console.log(now.getDate())
console.log(now.getHours()) // 16

// UTC+0 기준
console.log(now.getUTCFullYear())
console.log(now.getUTCMonth())
console.log(now.getUTCDate())
console.log(now.getUTCHours()) // 7

 

set도 마찬가지이다.

now.setUTCHours(23)
now.setHours(23)

 

자동고침

Date객체는 범위를 벗어나는 값을 설정하려고하면 자동으로 보정된 값이 들어간다.

let date = new Date(2016, 1, 28);
date.setDate(date.getDate() + 2);

alert( date ); // 2016년 3월 1일

 

이부분이 해깔릴 수 있는데, 아까 month 는 1~12 가아니라 0~11 의 범위를 가진다는 점을 유의해야한다.

let date = new Date(2016, 1, 28);
date.setDate(date.getDate() + 2);

console.log( date.toLocaleString() ); // 2016년 3월 1

 

또한 날짜를 음수로, 즉 1일이 최솟값인데, 0을 넣으면 전달 마지막날짜로 변환한다.

let date = new Date(2016, 0, 2); // 2016년 1월 2일

date.setDate(1); // 1일로 변경합니다.
alert( date ); // 01 Jan 2016

date.setDate(0); // 일의 최솟값은 1이므로 0을 입력하면 전 달의 마지막 날을 설정한 것과 같은 효과를 봅니다.
alert( date ); // 31 Dec 2015

 

timestamp를 구하는 또다른 방법

명시적으로 getTime()을 호출해도되지만, 자료형을 숫자로 변경해도 같은결과를 반환한다.

parseInt()는 문자열이포함되어있어 NaN이다.

let date = new Date(2016, 1, 28);

console.log(date) // 2016-02-27T15:00:00.000Z
console.log(date.getTime()) // 1456585200000
console.log(+date) // 1456585200000
console.log(Number(date)) // 1456585200000
console.log(parseInt(date)) // NaN

실행시간 구하기

let start = Date.now(); // 1970년 1월 1일부터 현재까지의 밀리초

// 원하는 작업을 수행
for (let i = 0; i < 100000; i++) {
  let doSomething = i * i * i;
}

let end = Date.now(); // done

alert( `반복문을 모두 도는데 ${end - start} 밀리초가 걸렸습니다.` );

 

참고사항

day 는 0 ~ 6인데, 0은 일요일이다.

그래서 유럽국가의 달력은 월요일부터 시작인데, 그래서 0을 7로 치환해주어야한다.

function getLocalDay(date) {

  let day = date.getDay();

  if (day == 0) { // 일요일(숫자 0)은 유럽에선 7입니다.
    day = 7;
  }

  return day;
}

상대날짜 변환하기 예제

function formatDate(date) {
  let diff = new Date() - date; // 차이(ms)

  if (diff < 1000) { // 차이가 1초 미만이라면
    return '현재';
  }

  let sec = Math.floor(diff / 1000); // 차이를 초로 변환

  if (sec < 60) {
    return sec + '초 전';
  }

  let min = Math.floor(diff / 60000); // 차이를 분으로 변환
  if (min < 60) {
    return min + '분 전';
  }

  // 날짜의 포맷을 변경
  // 일, 월, 시, 분이 숫자 하나로 구성되어있는 경우, 앞에 0을 추가해줌
  let d = date;
  d = [
    '0' + d.getDate(),
    '0' + (d.getMonth() + 1),
    '' + d.getFullYear(),
    '0' + d.getHours(),
    '0' + d.getMinutes()
  ].map(component => component.slice(-2)); // 모든 컴포넌트의 마지막 숫자 2개를 가져옴

  // 컴포넌트를 조합
  return d.slice(0, 3).join('.') + ' ' + d.slice(3).join(':');
}

alert( formatDate(new Date(new Date - 1)) ); // "현재"

alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30초 전"

alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5분 전"

// 어제의 날짜를 31.12.2016 20:00 형태로 출력
alert( formatDate(new Date(new Date - 86400 * 1000)) );

 

끝내며

속이 후련하다

이때까지 애매하게 할고있던 Date를 한번 정리해서 이젠좀 덜해깔릴 것 같다.

반응형

'Archive' 카테고리의 다른 글

재귀와 실행컨텍스트  (0) 2022.08.31
JSON 과 메서드  (0) 2022.08.30
{}의 원래목적 과 비구조분해 할당  (0) 2022.08.30
map과 객체  (0) 2022.08.30
자바스크립트 배열과 객체  (0) 2022.08.30