DTPab

DTPにまつわるあれこれ

日数単位でDateオブジェクト同士の差を求めたい

GASのリファクタリングをしていて実装したのですが、思ったより簡単だったのでメモとして残しておきます。

やりたかったこと

任意の日付(Dateオブジェクト)同士を比較して「あと何日か」を調べたい。

できたもの

function calculateDays(yy, mm, dd) {
    const today = new Date();
    const myDate = new Date(yy, mm - 1, dd);
    const dif = Math.floor((myDate - today) / 86400000);
    // 値がマイナスならnullを返す
    if (dif < 0) { return null; }
    return dif;
}

86400000という数字は、ミリ秒を日に換算した値です*1

注意点

「1日」の定義

ご覧の通り、ミリ秒を日数に換算していますので、todayオブジェクトに入っている日時(=取得したタイミングの日時)と、myDateオブジェクトに入っている日時(指定日の午前0時*2)とで24時間分の差がないと「1日」になりません。

Dateコンストラクタの月表記

できてしまえばこんなに簡単なコードだったんですが、月だけが0ベースインデックスだったことを忘れていて沼りました。
Date.getMonth()メソッドが0ベースインデックスの値を返すクソ仕様なのは割と知られるところかと思いますが、引数としてDateコンストラクタに値を渡す場合も同じルールなのでした。なので「2021年6月11日」を指定したい場合、new Date(2021, 6, 11)ではなくnew Date(2021, 5, 11)とする必要があるのです。こんなん普通にミスするやんけ。ということでわざわざ関数にして中で計算するようにしました。
注意すべきは、この関数のように引数を自分で書いて渡す場合はこの通りですが、仮にDateオブジェクトを取得した場合はその必要はないということです。
要するにnew Date()とnew演算子を付けて日時を取得したら、その月表記も0ベースインデックスになっています。なので調整の必要がないのです。

本来はもっと工夫が必要

サンプルなので何も処理していませんが、例外処理をしっかり作り込んだほうがよいです。例えば下記のようにDate()コンストラクタに値を渡してもエラーになりません。

new Date(2021, 16, 2)

結果はこうです。

Mon May 02 2022 00:00:00 GMT+0900 (日本標準時)

2021年15月2日は存在しませんが、得られる値は2022年5月2日になっています。このように意図しない結果になり得るので、それぞれに入力される値が正しい月日かどうかは調べる必要があるでしょう。

*1:=1000 * 60 * 60 * 24

*2:僕の実行環境は日本なのでGMT+9:00で取得