Michi's Tech Blog

一人前のWebエンジニアを目指して

JavaScriptの日付型の罠

この記事は「つながる勉強会 Advent Calendar 2022」の12日目の記事です。

こんにちは!
スマレジ テックファームのMichiです!

今回はJavaScriptの日付型でハマった罠についてご紹介します。

やりたいこと

とある勤怠管理システムで、検索の対象範囲として「開始日」と「終了日」を入力できるものとする。
画面から入力された日付の範囲に応じて、表示される勤怠情報を絞り込みたい。

変数の説明

  • fromDate ... 検索開始日。YYYY-MM-DD形式。例)2022-12-01
  • toDate ... 検索終了日。YYYY-MM-DD形式。例)2022-12-31
  • attendances ... 各日付ごとの勤怠情報を格納する配列。配列の各要素はオブジェクトになっており、 {targetDate: 'YYYY/MM/DD'}の形式で対象日付を指定している。 例){targetDate: '2022/12/12'}

コード

// 入力された値をDateオブジェクトに変換する
const parsedFromDate = new Date(fromDate)
const parsedToDate = new Date(toDate)

// targetDateで検索対象のattendanceを絞り込む
const targetAttendances = attendances.filter((attendance) => {
  const targetDate = new Date(attendance.targetDate)
  return targetDate >= parsedFromDate && targetDate <= parsedToDate
})

問題

期待される結果に対して、検索後のデータが1つ足りないときがある。
どうやら、検索開始日に該当する勤怠情報が取得できていないっぽい。

調査

ということで、Dateオブジェクトの中身をすべてconsole.logして調べてみた。

console.log(parsedFromDate)
// 結果:Thu Dec 01 2022 09:00:00 GMT+0900 (GMT+09:00)
console.log(parsedToDate)
// 結果:Thu Dec 01 2022 09:00:00 GMT+0900 (GMT+09:00)

console.log(targetDate);
// 結果:Mon Dec 01 2022 00:00:00 GMT+0900 (GMT+09:00)
// 結果:Mon Dec 02 2022 00:00:00 GMT+0900 (GMT+09:00)
// 結果:Mon Dec 03 2022 00:00:00 GMT+0900 (GMT+09:00)
// 結果:...

なんと、画面からの入力値と検索対象のオブジェクトで、日付の時間が違うじゃありませんか!
詳しく調べてみると、こういうことらしい。

  • YYYY/MM/DD形式 → UTC世界標準時)。時刻を指定しない場合はすべて00:00:00となる。
  • YYYY-MM-DD形式 → ローカル時間。今回はJST日本標準時)で解釈されたため、UTCからの時差+9時間が加算された。

解決策

つまり、YYYY/MM/DD形式 と YYYY-MM-DD形式 を比較していることが根本要因なので、どちらかに統一してやる必要がある。問題はどちらに統一するべきかということだが、MDNのリファレンスにはこんな記載があった。

メモ: Date コンストラクター(および Date.parse と同等)で日付文字列を解釈する際には、常に入力が ISO 8601 形式 (YYYY-MM-DDTHH:mm:ss.sssZ) であることを確認してください。他の形式で解釈した場合には、その挙動は実装によって定義されていて、すべてのブラウザーで動くとは限りません。多数の異なる形式に対応するためには、ライブラリーが役に立ちます。

というわけで、仰せの通りYYYY-MM-DD形式に統一する。

// 入力された値をDateオブジェクトに変換する
const parsedFromDate = new Date(fromDate)
const parsedToDate = new Date(toDate)

// targetDateで検索対象のattendanceを絞り込む
const targetAttendances = attendances.filter((attendance) => {
  // スラッシュをハイフンに変換してから、Dateオブジェクト化する
  const targetDate = new Date(attendance.targetDate.replace('/', '-'))
  return targetDate >= parsedFromDate && targetDate <= parsedToDate
})

これで期待通りの結果が抽出できた。

まとめ

今回はJavaScriptの日付型の罠について解説しました。
上の例では対処療法的にreplaceで表示形式を変更しましたが、そもそも設計段階で、APIから返却する値をすべてYYYY-MM-DD形式しておくのが良いと思います。