dayjs('2') 를 출력해보면 "2001-02-01T00:00:00+09:00" 가 나오는 이유

요즘 나는 회사에서 날짜와 시간을 계산할 때 사용하는 라이브러리를 교체하는 작업을 하고 있다.
현재 moment 라는 라이브러리를 사용하고 있는데 유지보수가 중단되는 이슈가 발생했다.
그래서 moment 라이브러리를 dayjs 라이브러리로 교체하기로 결정했다.

교체 작업을 어느 정도 진행하고 테스트 코드를 작성하던 중에 이상한 걸 발견했다.
실패하는 테스트 케이스를 작성 중이였는데, 나는 당연히 dayjs가 문자열 '2'를 유효하지 않은 포맷의 문자열이라고 판단할 줄 알았다.
그래서 dayjs(’2’).isValid() 의 결과값을 false라고 예상했다.
근데 테스트를 돌려보니 결과값이 true가 나왔다.

뭐지???? 하고 dayjs('2')를 콘솔에 출력해보니 “2001-02-01T00:00:00+09:00” 가 화면에 출력되었다.

이번엔 dayjs(’3’) 를 콘솔에 출력해보니 “2001-03-01T00:00:00+09:00”가 출력되었다.

당황스러웠다;;;

왜 dayjs가 문자열 ‘2’를 2001년 02월 01일 00시 00분 00초 KST로 파싱하는 건지 이해가 안됐다.

이 현상의 원인을 알아내고자 개발 인생 처음으로 깃허브에 이슈를 남겼다.
파파고의 힘을 빌려 내가 발견한 이 현상에 대해 영어로 열심히 설명했다.

(혹시 내 이슈에 답변 안해줄까봐 라이브러리를 만들고 유지보수 해줘서 고맙다는 인사와 함께 밑밥부터 깔았다)

이슈를 남긴 후 4일 뒤에 답변이 달렸다.
고맙게도 dayjs 라이브러리에 기여하고 계신 한국분께서 답변을 해주셨다.

위의 스샷을 통해 알 수 있듯이, 자바스크립트의 Date 클래스도 dayjs와 마찬가지로 문자열 '2'를 2001년 02월 01일 00시 00분 00초 KST로 파싱하고 있었다.
dayjs 공식 문서에 따르면 Dayjs 클래스는 자바스크립트의 Date 클래스를 감싼 랩퍼 클래스라고 한다.
이런 사실들을 통해서 추측해봤을 때, dayjs 라이브러리는 (포맷을 따로 설정해주지 않으면) 별도의 파싱 로직을 사용하지 않고 Date 클래스의 파싱 기능을 그대로 사용하는거 같다.
Date 클래스에서 문자열 '2'를 2001년 02월 01일 00시 00분 00초 KST로 파싱하기 때문에 dayjs의 파싱 결과도 그렇게 나온 것이다.

그러므로 dayjs 라이브러리가 아니라 Date 클래스가 왜 문자열 '2'를 그런식으로 파싱하는지 알아봐야 했다.

결국 자바스크립트 스팩 문서까지 찾아보게 되었다.

스팩 문서를 완벽하게 이해한건 아니지만, Date 클래스 생성자 함수의 작동 방식을 간략하게 설명해보면 다음과 같다.

  1. Date 클래스 생성자가 new 키워드 없이 호출되면 현재 UTC 시간을 문자열로 리턴한다. (오, 몰랐다 🤔)
  2. Date 클래스 생성자가 new 키워드와 함께 호출되면 Date 클래스 인스턴스를 리턴하는데, 생성자를 호출할 때 전달된 인자의 개수와 타입에 따라 완전히 다르게 동작한다.
    1. 0개의 인자가 전달되면 현재 시간으로 설정된 Date 클래스 인스턴스를 리턴한다.
    2. 1개의 Date 클래스 타입의 인자가 전달되면 인자로 전달된 Date 클래스 인스턴스를 클론한 새로운 인스턴스를 리턴한다.
    3. 1개의 숫자 타입의 인자가 전달되면 인자값을 유닉스 타임스탬프라고 판단하고 날짜를 계산해서 Date 클래스 인스턴스를 리턴한다.
    4. 1개의 문자열 타입의 인자가 전달되면 해당 문자열을 파싱해서 Date 클래스 인스턴스를 리턴한다. (중요) 문자열을 파싱하는 방식은 Date 클래스 parse 메서드의 방식과 동일하다.
    5. 기타 생략...

스팩 문서에 따르면, Date 클래스의 parse 메서드는 문자열을 먼저 ISO 8601 포맷에 따라 파싱을 한다고 한다.
그런데 만약에 문자열이 ISO 8601 포맷이 아니면, 스팩을 구현하는 곳마다 정해놓은 방식대로 문자열을 파싱한다고 한다.
그러니까 스팩 상으로는 ISO 8601 포맷이 아닌 문자열을 파싱하는 방법이 정해져있지 않은 것이다.

실제로 테스트해보니 브라우저마다 문자열 '2'를 파싱한 결과가 다 달랐다.

크롬에서는 문자열 '2'를 2001년 02월 01일 00시 00분 00초 KST로 파싱했다.

new Date('2'); // Thu Feb 01 2001 00:00:00 GMT+09:00 (한국 표준시)
new Date('2').getFullYear(); // 2001
new Date('2').getMonth(); // 1
new Date('2').getDate(); // 1
new Date('2').getHours(); // 0
new Date('2').getMinutes(); // 0
new Date('2').getSeconds(); // 0

반면에 사파리에서는 문자열 '2'를 0002년 01월 01일 08시 27분 52초 KST로 파싱했다.

new Date('2'); // Tue Jan 01 0002 08:27:52 GMT_0827 (KST)
new Date('2').getFullYear(); // 2
new Date('2').getMonth(); // 0
new Date('2').getDate(); // 1
new Date('2').getHours(); // 8
new Date('2').getMinutes(); // 27
new Date('2').getSeconds(); // 52

파이어폭스에서는 문자열 '2'를 유효하지 않은 포맷의 문자열이라고 판단했다.

new Date('2'); // Invalid Date
new Date('2').getFullYear(); // NaN
new Date('2').getMonth(); // NaN
new Date('2').getDate(); // NaN
new Date('2').getHours(); // NaN
new Date('2').getMinutes(); // NaN
new Date('2').getSeconds(); // NaN

Node.js 는 크롬 브라우저와 동일한 V8 자바스크립트 엔진으로 실행되기 때문에 Node.js 에서 문자열 '2'를 파싱하면 2001년 02월 01일 00시 00분 00초 KST 가 나왔던 것이다.

(...... 나도 왜 자바스크립트가 이 모양인지 궁금하다 😅)

Node.js 환경에서도 그렇지만, 특히 브라우저에서는 Date 클래스 사용을 지양해야 할거 같다.
ISO 8601 포맷이 아닌 문자열을 파싱하면 브라우저마다 다른 날짜 정보를 얻을 수 있으니 말이다.
그리고 Dayjs 라이브러리를 사용해서 문자열을 파싱할 때는 반드시 커스텀 포맷을 설정하고 strict 모드를 사용해야 할거 같다.
(자세히 안읽어봐서 몰랐는데, Dayjs 공식 문서에도 ISO 8601 포맷이 아닌 문자열을 파싱할 때는 일관된 파싱 결과를 위해서 반드시 커스텀 포맷을 설정하라고 되어 있다.)

좋은 웹페이지 즐겨찾기