📅

[Typescript]日付を扱う上で出会ったバグについて

2024/11/25に公開

最近業務で日付関連のフォームをいじることが多々あり、その際起きたバグについてシェアします。

new Date()でパースしたら挙動が異なる

yyyy-MM-ddyyyy.MM.ddyyyyMMdd形式で入力した日付文字列をyyyy/MM/ddにフォーマットする処理で出会いました。


// yyyy-MM-dd形式

// Chrome
new Date('2024-11-20')
// => Wed Nov 20 2024 09:00:00 GMT+0900 (日本標準時)

new Date('2023-02-29')
// => Wed Mar 01 2023 09:00:00 GMT+0900 (日本標準時)

// safari
new Date('2024.11.20')
// => Wed Nov 20 2024 00:00:00 GMT+0900 (日本標準時)

new Date('2023-02-29')
// => Invalid Date

// yyyy.MM.dd形式

// Chrome
new Date('2024.11.20')
// => Wed Nov 20 2024 00:00:00 GMT+0900 (日本標準時)

// safari
new Date('2024.11.20')
// => Invalid Date 

// yyyyMMdd形式

// Chrome
new Date('20241120')
// => Invalid Date

// safari
new Date('20241120')
// => Invalid Date

問題点

  • 存在しない日付を入力してもChromeではInvalid Dateにならない
  • ブラウザのJSエンジンの違いで挙動が異なる
  • yyyyMMdd形式でのパースがInvalid Dateとして扱われる

解決策

date-fnsライブラリでパースしてフォーマットするようにしました。


import { parse } from 'date-fns';

// yyyy-MM-dd形式
parse('2024-11-20', 'yyyy-MM-dd', new Date());
// =>  Wed Nov 20 2024 09:00:00 GMT+0900 (日本標準時)

parse('2023-02-29', 'yyyy-MM-dd', new Date());
// =>  Invalid Date

// yyyy.MM.dd形式
parse('2024.11.20', 'yyyy.MM.dd', new Date());
// =>  Wed Nov 20 2024 09:00:00 GMT+0900 (日本標準時)

// yyyyMMdd形式
parse('20241120', 'yyyyMMdd', new Date());
// =>  Wed Nov 20 2024 09:00:00 GMT+0900 (日本標準時)

動的な値が入ってくる場合だとパースする型を指定しないといけないのは条件分岐が少し面倒だったりしますが、今のところ日付のパースを安全に扱うには日付計算系のライブラリを使うしかないのかなって思ってます。

setMonthを使って日付のリストを作ったら月末だけ表示がおかしくなった

検索フォームで現在の日付から過去12ヶ月分のリストを選択できるものがあり、最初setMonth関数を使ったやり方で対応していました。

// 現在の月から過去12ヶ月分の年月のリストを表示
export const generateDateList = () => {
  // ※日付重複の再現のために'2024-10-31'を入れています
  const currentDate = new Date('2024-10-31');

  const dateList = [];

  for (let i = 0; i <= 12; i++) {
    const newDate = new Date(currentDate);
    //  月の減算
    newDate.setMonth(currentDate.getMonth() - i);
    const year = newDate.getFullYear();
    const month = newDate.getMonth() + 1;
    // yyyy-MM型に変換
    dateList.push(`${year}-${String(month).padStart(2, '0')}`);
  }

  return dateList;
};

特に問題ないだろうと思っていましたが偶然月末にリストを確認すると、ところどころ重複して表示される年月が出るようになってしまいました。(2024-10、2024-07、2024-05、2024-03、2023-12)

理由としては月によって日数が異なるからです。
例えば2024-10が重複して表示される件に関しては、setMonthの計算で2024年10月31日の1ヶ月前ということで2024年9月31日になりますが、9月に31日はないので自動的に繰り越して10月1日として扱うことになってしまいます。他の月に関しても同様の問題が起こり、重複して表示されてしまいます。

解決策

これもdate-fnsライブラリのsubMonthsを使って解決しました。

import { subMonths } from 'date-fns';

// 現在の月から過去12ヶ月分の年月のリストを表示
export const generateDateList = () => {
  const currentDate = new Date('2024-10-31');

  const dateList = [];

  for (let i = 0; i <= 12; i++) {
    //  月の減算
    const newDate = subMonths(currentDate, i);
    const year = newDate.getFullYear();
    const month = newDate.getMonth() + 1;
    dateList.push(`${year}-${String(month).padStart(2, '0')}`);
  }

  return dateList;
};

ちゃんと過去12ヶ月分表示されましたね。

※setMonthで一時的に日付を月初にすれば、この問題は解決するらしいですがライブラリを使った方がシンプルなのと何をやっているかが明確なのでdate-fnsを使ったやり方を採用しました。

まとめ

以上、私が日付を扱う時に出会ったバグについての紹介でした。

Dateオブジェクトを使うと思わぬバグに出会う可能性があるので、今はdate-fnsなどの日付計算系ライブラリを使って対応する方が良いと思いました。

Discussion