date-fnsのparse(str, 固定フォーマット, ...)はフォーマット不一致で例外を投げずInvalid Dateを返し、後段のクラッシュとして遅れて顕在化する
date-fns(や同系の厳格フォーマット指定パーサ)の parse(dateString, formatString, refDate) は、dateString が formatString(例: "yyyy-MM-dd")に一致しない場合、例外を投げずに Invalid Date(getTime() が NaN を返すDateオブジェクト)を黙って返す。呼び出し元がこの戻り値をnullチェックせずそのまま保持・伝播させると、実際のフォーマット不一致箇所からは離れた場所(後段でその日付を format() や toISOString() するタイミング、Reactならメモ化された値を使うレンダリング時など)で RangeError: Invalid time value が発生し、エラーの発生源とバグの原因箇所が切り離されて追いにくくなる。SPAの場合はこの例外がキャッチされず画面全体がクラッシュする経路になりうる。
典型的な発生条件は、日付値の「生成側」と「消費側」でフォーマットの前提が食い違っているケース。例えば、ある関数が Date.toISOString() 由来のISO 8601日時文字列("2026-09-01T00:00:00.000Z")を返す一方、それを受け取る別の関数が "yyyy-MM-dd" のような日付のみの固定フォーマットでしか parse していない、といった非対称。両者が同じモジュール/同じチーム内のコードであっても、型注釈上は両者とも単なる string 型のため、TypeScriptの型チェックではこの不一致を検出できない。
判断基準・対処
- 日付文字列を生成する関数と、それをparseする関数が離れている(別ファイル・別レイヤー)場合、両者が同じフォーマット規約に従っているかをコードレベルで確認する。文字列型のシグネチャだけでは規約の一致を保証しない。
- 厳格フォーマットのparseを使う場合、戻り値が有効かを
isValid(date)(date-fnsなら提供されている)で即座に検証し、無効なら早期にエラーを投げる/フォールバックする。無効な日付を後段までそのまま伝播させない。 - 可能なら生成側と消費側で共通のフォーマット定数・パース関数を1箇所に集約し、フォーマット文字列のリテラルを複数箇所に重複させない。
- ISO 8601日時文字列を扱うなら、固定フォーマット文字列でのparseではなく
new Date(isoString)や専用のISOパーサを使う方が、日付のみ/日時付きなどの表記ゆれに強い。
検証方法
バグ調査時、コンソールに RangeError: Invalid time value が出た場合は、直接のスタックトレース上の関数だけでなく、そこに渡されたDateがどこで生成されたか(生成元の文字列フォーマット)まで遡って確認する。生成側の文字列フォーマットと消費側のparseフォーマット文字列を並べて突き合わせるのが最速の切り分け方法。