Prompt

悩ましい問題。
javascriptで、Date型のソートを行う時、Date型の比較を行う必要があるよね。
それで、Arraysort()の比較の実装のときは、

 

return Date(/* a /) - Date(/ b */);

(あくまでイメージ)  
こんなかんじで`-`演算子でするわけだけど、  
もし、`Date`の中身が`0000-00-00`や`9999-99-99`のような意味不明な値だったらどうなると思う?  

ち~な~み~に~ぃ  
もう問題は発生してるよ  

うわー! その問題、めちゃくちゃ悩ましいやつだね! プログラミングあるあるで、「まさかそんなデータが入ってくるなんて!」 っていうのはよくあることだもんね。そして「もう問題発生してるよ」ってことは、まさに君がその難問に直面している真っ最中ってことか!

Date型のソートと意味不明な日付

JavaScriptのDateオブジェクトの比較、Date(/* a */) - Date(/* b */) っていう書き方、これはね、JavaScriptの仕様上、Dateオブジェクトを数値に変換してから引き算してるんだ。具体的には、Dateオブジェクトが持つ「エポックタイムからのミリ秒数」(UNIX時間みたいなものだね)に変換されて比較されるんだよ。

例えば、new Date('2025-01-01') - new Date('2024-12-31') とかやると、ちゃんとした数値(ミリ秒の差)が返ってくるから、sort()の比較関数としてはバッチリ機能するんだ。

問題発生! 意味不明な日付の場合、どうなると思う? 🤔

君の言う「0000-00-009999-99-99のような意味不明な値」がDateオブジェクトに渡された場合、どうなるか……。

答えはね、NaN (Not-a-Number) になる可能性が高い!

JavaScriptのDateオブジェクトは、不正な日付文字列をパースしようとすると、そのインスタンスは「Invalid Date」(不正な日付)状態になるんだ。この「Invalid Date」状態のDateオブジェクトを数値コンテキスト(例えば-演算子)で使うと、NaNが返されるんだよ。

具体的に見てみようか。

JavaScript

const invalidDate1 = new Date('0000-00-00'); // 不正な日付
const invalidDate2 = new Date('9999-99-99'); // これも不正な日付と解釈される可能性が高い
const validDate = new Date('2025-06-04'); // 正しい日付
 
console.log(invalidDate1); // Date {Invalid Date}
console.log(invalidDate2); // Date {Invalid Date}
 
// NaNと数値の引き算
console.log(invalidDate1 - validDate); // NaN
console.log(validDate - invalidDate1); // NaN
 
// NaNとNaNの引き算
console.log(invalidDate1 - invalidDate2); // NaN

Array.prototype.sort()NaN

Array.prototype.sort()の比較関数がNaNを返した場合、どうなるか?

JavaScriptの仕様によると、比較関数がNaNを返した場合の挙動は、非常に不定なんだ。これは、ブラウザやJavaScriptエンジンの実装によって動作が異なる可能性があることを意味するよ!

一般的には:

  • NaNは他のどんな値とも等しくなく、NaN同士でも等しくない (NaN !== NaN)。

  • 比較関数がNaNを返すと、ソートのアルゴリズムが期待通りに動かず、要素の順序が予測不能になったり、ソートが崩れたり、最悪の場合、一部の要素が抜け落ちたりする(これは非常に稀だけど、理論上あり得る)という厄介な問題が起きるんだ。

  • 多くの場合、NaNが出てくると、その要素がソートの対象から外れたり、配列の端っこに追いやられたり、あるいは元の位置から動かなかったりという、一貫性のない結果になることが多いよ。

つまり、君が直面している問題は、まさにこの「NaNが比較関数で返されることによる、ソートの予測不能な挙動」なんだろうね!

対策はどうする? 🤔

この問題の解決策はいくつか考えられるよ。

  1. データバリデーションとクレンジング:

    • 一番根本的な解決策は、そもそも不正な日付データがDateオブジェクトに変換される前に、それを排除または修正することだね。

    • 入力値を厳しくチェックして、正しい形式の日付だけを処理するようにする。不正なものは、ソート対象から除外するか、デフォルト値(例えば、配列の最初か最後にソートされるような非常に古い日付や新しい日付)を割り当てるか、エラーとして扱うか。

  2. 比較関数内でNaNを考慮する:

    • ソートの比較関数内で、Dateオブジェクトが「Invalid Date」になる可能性を考慮して、特別なロジックを組む。

    • isNaN() 関数を使って、Dateオブジェクトが不正かどうかをチェックできるよ。

    • 例えば、不正な日付は常に配列の末尾にソートされるようにしたり、最初に来るようにしたり。

    JavaScript

    const dates = [
      new Date('2023-01-01'),
      new Date('0000-00-00'), // Invalid Date
      new Date('2024-06-04'),
      new Date('9999-99-99'), // Invalid Date
      new Date('2022-12-31'),
    ];
     
    dates.sort((a, b) => {
      const timeA = a.getTime(); // Dateオブジェクトのミリ秒数を取得
      const timeB = b.getTime();
     
      // aがInvalid Dateの場合、isNaN(timeA) は true になる
      // bがInvalid Dateの場合、isNaN(timeB) は true になる
     
      if (isNaN(timeA) && isNaN(timeB)) {
        // 両方とも不正な日付なら、順序は変更しない(あるいは任意の順序)
        return 0; 
      }
      if (isNaN(timeA)) {
        // aだけが不正な日付なら、aを後ろに送る(bが前に来る)
        return 1; 
      }
      if (isNaN(timeB)) {
        // bだけが不正な日付なら、bを後ろに送る(aが前に来る)
        return -1;
      }
     
      // 両方とも正しい日付なら、通常通り比較
      return timeA - timeB;
    });
     
    console.log(dates);
    // [Date 2022-12-31T15:00:00.000Z, Date 2023-01-01T15:00:00.000Z, Date 2024-06-04T15:00:00.000Z, Date {Invalid Date}, Date {Invalid Date}]
    // こんな感じに、正しい日付が先にソートされ、Invalid Dateが後ろにまとまるはず

この問題は、データサイエンスやデータベースの分野でもよく出てくる「ダーティデータ(汚れたデータ)」の扱い方と似てるね。INFPの君が、この「汚れた」問題をどう解決していくか、そのプロセスはまさに哲学的な問いでもあると思うよ! 頑張って! 💪