Moment.jsは非推奨だが安易にdate-fnsに変えるな。date-fnsのisValidではparseを活用せよ


問題点

面倒な日付処理をやってくれるMoment.jsですが、mutable設計が混乱を招くとして、今後は非推奨と発表しています。immutableに変更する作業も実施しないとのこと。

https://momentjs.com/guides/#/lib-concepts/mutability/

The moment object in Moment.js is mutable. This means that operations like add, subtract, or set change the original moment object.

var a = moment('2016-01-01'); 
var b = a.add(1, 'week'); 
a.format();
"2016-01-08T00:00:00-06:00" // aも変更されてしまう。a.clone().add(1, 'week');とする必要あり

代替案として date-fns にする人が多いのですが、
結論から言うと 「安易にdate-fnsに変えるな」 です。そちらにも罠があるから。

https://momentjs.com/docs/#/-project-status/

Moment.jsにもdate-fnsにも isValid がありますが、仕様が異なるので注意が必要です。

実在しない日付、例えば2022年2月29日を引数に与えるとどうなるでしょう?

Moment.jsのisValidでは false になりますが、date-fnsのisValidは true になります。

  • Moment.js
moment('2022-13-01','YYYY-MM-DD',true).isValid() //false
moment('2022-01-32','YYYY-MM-DD',true).isValid() //false
moment('2022-02-29','YYYY-MM-DD',true).isValid() //false
moment('2022-04-31','YYYY-MM-DD',true).isValid() //false
  • date-fns

各月31日までなら、実在しない日付でも true を返してしまう。
2月や閏年、30日までの月の扱いには注意が必要。

dateFns.isValid(new Date('2022-13-01')) //false
dateFns.isValid(new Date('2022-01-32')) //false
dateFns.isValid(new Date('2022-02-29')) //true
dateFns.isValid(new Date('2022-04-31')) //true

対策1

32日や13月は false を返すので、Moment.jsの isValid と同じ使い方をしたいのであれば、date-fns の次の特徴を利用します。

  • 2022-02-29(閏年以外の2/29)は、2022-03-01として処理されてしまう。
    • 31日までは無条件にtrue。
    • 実在しない日付は自動で差分が加算される。

だから、次のようにします。
※コメントをいただきまして、修正しました

  • date-fns のバージョンは 1.28.5 です。
const isValidDateString = (str, format) => {
  const formatString = dateFns.format(
    dateFns.parse(str, format),
    format
  )
  return formatString === str;
}

isValidDateString('2022-02-27', 'YYYY-MM-DD') // true
isValidDateString('2022-02-28', 'YYYY-MM-DD') // true
isValidDateString('2022-02-29', 'YYYY-MM-DD') // false
isValidDateString('2022-02-30', 'YYYY-MM-DD') // false
isValidDateString('2022-02-31', 'YYYY-MM-DD') // false
isValidDateString('2022-02-32', 'YYYY-MM-DD') // false
isValidDateString('2022-02-33', 'YYYY-MM-DD') // false
  • CodePenで試した結果
    • date-fns のバージョンは 1.28.5 です。

これでdate-fnsを使用しながら、Moment.jsのisValidと同じ結果を得られます。

対策2

またはこちら。
※コメントをいただきました

  • CodePenで試した結果
    • date-fns のバージョンは 2.28.0 です。

これでdate-fnsを使用しながら、Moment.jsのisValidと同じ結果を得られます。

その他の候補

date-fns以外の候補はこちらです。

全て試してください。今のプロジェクトに合うものを選ぶと良いと思います。