Date-fns

도입배경

여행상품 검색결과에 노출되는 항공권 최저가 API 작업을 할때
날짜를 많이 다룰일이 있었습니다

원래대로라면 날짜관련 부가기능이 많이 들어있는 moment.js 를 사용했을텐데요
moment project status 를 보니 moment.js 가 사실상 추가 개선이 없는 legacy 모드로 전환되었습니다

이에 따른 대안으로 dayjs 사용을 생각해보았으나
필요로 하는 날짜계산 관련 기능이 조금 충분치 않았습니다

그래서 다른 대안을 찾아보던중 date-fns 라는 라이브러리를 사용해보게 되었고, 작업하면서 꽤 만족스럽게 사용했어서 간단히 공유드리려고 합니다

date-fns 특장점

date-fns 는 moment.js, dayjs 와 같은 날짜 라이브러리입니다
날짜 관련 util 이 대동소이 하겠지만
date-fns 특징을 다음과 같이 요약해볼수 있을것같습니다

  • 다양한 날짜 계산 기능 제공
  • Native Date 객체를 사용한다
  • 함수형으로 사용하기 용이하다

다양한 기능 제공

작업하면서 날짜를 다루는데 필요한 기능들을 date-fns 에서 대부분 발견할수 있었습니다

예시로 아래와 같이 `eachDayOfInterval` 함수를 사용해서
시작일-종료일 사이에 날짜들을 쉽게 구할수 있었습니다

test('get days from today to 2 month after', () => {
// given
const start = new Date(2022, 7, 29); // '20220829'
const end = endOfMonth(addMonths(start, 2));
// when
const intervalDays = eachDayOfInterval({ start, end });
// then
expect(format(intervalDays[0], 'yyyyMMdd')).toBe('20220829');
expect(format(intervalDays[intervalDays.length - 1], 'yyyyMMdd')).toBe(
'20221031',
);
expect(intervalDays).toHaveLength(64);
});

eachWeekOfInterval, eachMonthOfInterval 같은 함수를 통해서 주,월 단위도 조회 가능합니다

또한 ‘다음주 토요일’ 같이 꽤 세부적인 요구사항의 경우도
`nextSaturday` 함수를 통해 바로 구할수 있었습니다

test('get next saturday', () => {
// given
const somedayOfweek = new Date(2022, 8, 27); // '20220927'
// when
const nextSat = nextSaturday(somedayOfweek);
// then
expect(format(nextSat, 'yyyyMMdd')).toBe('20221001');
expect(getDay(nextSat)).toBe(6);
});

가이드 문서를 참고하시면
이외에도 제공하는 다양한 기능들을 확인하실수 있습니다

Native Date 객체 사용

Moment.js, day.js 는 라이브러리 생성자를 통해
Moment, Day 같은 wrapper object를 만들고
해당 object의 함수를 통해서 날짜를 계산하거나 원하는 값들을 얻습니다

// moment
var day = new Date(2011, 9, 16);
var dayWrapper = moment(day);
// dayjs
dayjs('2018–04–04T16:00:00.000Z')

이를통해 기존 자바스크립트 Date 에서 제공하지 않는 기능들을
wrapper 객체에서 바로 사용할수 있게 하는건 장점이라고 할수 있지만

반면에 해당 라이브러리의 기능을 사용하기 위해서는
wrapper 객체로 변환이 필요하다는점이 단점이 되기도 합니다

DB에 데이터를 저장하거나, API 응답값으로 날짜를 전달할때
날짜형태 호환을 위해서 Date object 로 변환해야한다는 점도 다소 번거로울수 있겠습니다

반면에 date-fns 는 wrapper 없이 기존 Date 객체를 다루는 함수들만 제공해주는데요
(그래서 이름을 date-fns 으로 지었을지도?)

진구님이 작성하신 date-fns util test 를 예시로 들어보면
날짜 관련 변수들을 wrapper 로 감싸지 않고도
필요한 연산들을 쉽게 수행할수있는것을 확인할수 있습니다

test('compare if the date has been updated since a certain time yesterday', () => {
//given
const today = new Date('2022–08–26T01:00:00');
const newUpdateDate = new Date('2022–08–26T05:00:00');
const oldUpdateDate = subDays(today, 10);
const yesterdayOneOclock = subDays(today, 1);
//when
const isAfterYesterday = isAfter(newUpdateDate, yesterdayOneOclock);
const isBeforeYesterday = isBefore(oldUpdateDate, yesterdayOneOclock);
//then
expect(isAfterYesterday).toBeTruthy();
expect(isBeforeYesterday).toBeTruthy();
});

취향의 영역이겠지만 개인적으로는 기본 js Date 객체를 활용할수있는게
호환성 측면에서 장점이 있다고 생각합니다

함수형으로 쓰기 쉬움

Moment.js 의 mutability 특징은 함수형으로 코딩하는걸 다소 어렵게 만드는데요
date-fns 함수들은 pure function 으로 Input Date 조작없이 새로운 Date 객체를 반환하도록 되어있어
함수형으로 쓰기 좋게 되어있습니다

date-fns 함수를 간단하게 `Input(Date) -> Output(new Date)` 으로 표현해볼수 있겠는데요
lodash flow 를 사용해서 아래와 같이 함수 chaining 도 해볼수 있습니다

_.flow(addMonths, endOfMonth)(searchDate, 2);

함수형 관련 내용은 date-fns 문서의 FP-Guide 를 참조해주시면 되겠습니다

--

--