Javascriptを使ってフロントエンドで日付操作をする事って結構あると思いますが、Dateオブジェクトを使うとクセが強すぎて意図しない動きになったりしますよね。
最近会社のプロジェクトで日付操作をDateオブジェクトを使っていて悲鳴をあげてたんですが、date-fnsという便利ライブラリを知って一気に幸せになったので、今回はその幸せを皆さんにもお裾分けしていきたいと思います。

Dateオブジェクトの何が辛い?

月は0から始まる事

Javascriptの世界では月は0〜11で表します。 例えば2020年5月29日を取得したい時、下記のように書きます。getMonthで月を取得するときも同様に月数 - 1の数字が返却されます。

date = new Date(2020, 4. 29) // Fri May 29 2020 00:00:00 GMT+0900 (日本標準時)
date.getMonth // 4

フォーマットに関する独自プロパティが存在しない

日付フォーマットは規約ごとに色々ありますが、Dateオブジェクトではフォーマットに関しては一切フォローしてくれていません。 なので、ライブラリを使わない場合無理やりフォーマット化させる必要があります。

date = new Date(2020, 4. 29) // Fri May 29 2020 00:00:00 GMT+0900 (日本標準時)

// YYYY-MM-DD
`${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`

// YY/MM/DD
`${date.getFullYear() % 100}/${date.getMonth() + 1}/${date.getDate()}`

日付の加算減算が厄介

日付計算では純粋な+や-を使うだけでなく、getDate()のようなget系プロパティとsetDate()のようなset系プロパティを組み合わせて使う事で計算を行います。
そして、個人的にここがDateオブジェクト使う上で一番辛いところなのですが、set系プロパティは元の変数も破壊的に変更してしまうのです。

date = new Date(2020, 4, 29) // Fri May 29 2020 00:00:00 GMT+0900 (日本標準時)

// 1ヶ月後
date.setMonth(dt.getMonth() + 1); // Mon Jun 29 2020 00:00:00 GMT+0900 (日本標準時)

// 1日後
date.setDate(dt.getDate() + 1); // Tue Jun 30 2020 00:00:00 GMT+0900 (日本標準時)

// 1時間後
date.setHours(dt.getHours() + 1); // Tue Jun 30 2020 01:00:00 GMT+0900 (日本標準時)

date-fnsの導入

では、date-fnsを実際に導入していきたいと思います。
date-fnsの各プロパティはモジュールとして切り分けられており、必要なものだけimport出来るようになっています。

// 全てまとめてimportしたい場合
import { format, eachDayOfInterval } from 'date-fns'
// 個別にimportしたい場合
import { startOfMonth } from 'date-fns/startOfMonth'
import { endOfMonth } from 'date-fns/endOfMonth'
import { eachWeekendOfInterval } from 'date-fns/eachWeekendOfInterval'
import { differenceInCalendarDays } from 'date-fns/differenceInCalendarDays'

では、上でimportしたモジュールについて、簡単に紹介します!

format

formatは文字通りDateのフォーマットを指定した通りに生成してくれます。 引数に何を取ればどういう文字列にしてくれるかはこちらで見れます。

date = new Date() // Fri May 29 2020 00:00:00 GMT+0900 (日本標準時)
format(date, 'yyyy-MM-dd') // 2020-05-29
format(date, 'yy/MM/dd') // 20/05/29
format(date, 'iii MMM D yyyy') // Fri May 29 2020

eachDayOfInterval

eachDayOfIntervalは指定期間内の各日付を配列に格納して返却します。 こいつを使わずに例えば4月の全ての日付を取得する場合、下記のようなコードを書く必要がありました。

let dates = []
for(let d = new Date(2020, 3, 1); d <= new Date(2020, 3, 30); d.setDate(d.getDate()+1)) {
  dates.push(d)
}

eachDayOfIntervalを使うと上記のコードがなんとワンラインで書けてしまいます!

let dates = eachDayOfInterval({ start: new Date(2020, 3, 1), end: new Date(2020, 3, 30)})

たったこれだけで同じように4月の全ての日付を取得することができました!

startOfMonthとendOfMonth

これはその名の通り、指定した日付が属する月の最初と最後の日付を取得してくれます。

startOfMonth(new Date(2020, 4, 15)) // Fri May 1 2020 00:00:00 GMT+0900 (日本標準時)
endOfMonth(new Date(2020, 4, 15)) // Fri May 31 2020 00:00:00 GMT+0900 (日本標準時)

eachWeekendOfInterval

指定した期間の土日の日付を取得してくれます。 例えば下記のコードだけで、5月の土日を取得してくれます。

eachWeekendOfInterval({start: new Date(2020, 4, 1), end: new Date(2020, 4, 31)})

differenceInCalendarDays

指定した日付の差を返却してくれます。いわゆる日付の引き算ですね。

// ① 2019年5月1日23時00分と2020年5月1日00時00分の日付の差
differenceInCalendarDays(new Date(2020, 5, 1, 0, 0), new Date(2019, 5, 1, 23, 0)) // 366

// ② 2019年5月1日23時59分と2020年5月2日00時00分の日付の差
differenceInCalendarDays(new Date(2020, 5, 1, 23, 59), new Date(2019, 5, 2, 0, 0)) // 1

// ③ ②の逆
differenceInCalendarDays(new Date(2019, 5, 2, 0, 0), new Date(2020, 5, 1, 23, 59)) // -1

いかがでしたでしょうか?これが私が考える現時点での最強のJS日付ライブラリ、date-fnsです。
今回は日付のみの簡単な解説でしたが、また機会があれば時間などについても解説していこうと思います。