[Typescript]日付を扱う上で出会ったバグについて
最近業務で日付関連のフォームをいじることが多々あり、その際起きたバグについてシェアします。
new Date()でパースしたら挙動が異なる
yyyy-MM-dd
、yyyy.MM.dd
、yyyyMMdd
形式で入力した日付文字列を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