(追記 2020/09/19) Moment.js の開発が終了したようです。
↓ ここ参照
https://momentjs.com/docs/
今すぐライブラリが使えなくなることは無いと思いますが、この先、積極的にこのライブラリを選択するかどうかは考えものですね。
あと、GAS版のライブラリがどうなるのか、については情報が見つかりませんでした。(どなたか知ってたら教えてほしい)
GASで話をしますが、Javascriptでも同じです。
GAS版 MomentライブラリのIDは下記です。
MHMchiX6c1bwSqGM1PZiW_PxhMjh3Sh48
やりたかったこと
こんな表を作って、毎朝8時台にGASが起動して、「締め切りまであと3日」以内になったら「締め切り近いけど大丈夫?」というリマインドをしてくれる機能を作りたい。
そのため、Momentライブラリをつかって「日付の差分」を取りたかった。
ダメだった例
簡略化するためにA2セルの日付だけを対象にします。
始めはこう書きました。
function sample1() {
// 日付のセルからgetValue()したら Dateオブジェクト が取れるのでこう書いてしまいます
const deadline_date = new Date("2020-09-10");
const deadline_moment = Moment.moment(deadline_date);
const now_moment = Moment.moment();
const diff = deadline_moment.diff(now_moment, "days");
console.log(diff);
}
そして「今日は 2020/9/9 だとする」じゃないですか。
A2セルは 2020/9/10 なので 1 が出力されることを期待するじゃないですか。
だけど 出力結果は「0」
ダメだった理由
上記で 「今日は 2020/9/9 だとする」 と書いたのですが、
実際はこのGASが実行される時刻は 2020/09/09 12:34:56 だったりするわけです。
そして deadline として指定しているのは 「2020-09-10 00:00:00」 なのです。
実験してみるとこうなりました↓
function sample2() {
const deadline_date = new Date("2020-09-10 00:00:00");
const deadline_moment = Moment.moment(deadline_date);
const now_moment1 = Moment.moment("2020-09-09 00:00:00");
const diff1 = deadline_moment.diff(now_moment1, "days");
console.log(diff1); //=> 1
const now_moment2 = Moment.moment("2020-09-09 00:00:01");
const diff2 = deadline_moment.diff(now_moment2, "days");
console.log(diff2); //=> 0
}
now_moment1 と now_moment2では「1秒」異なります。
diff1 では deadlineである 2020-09-10 00:00:00 と 2020-09-09 00:00:00 の差分が 24:00:00 なので 1日。
diff2 では deadlineである 2020-09-10 00:00:00 と 2020-09-09 00:00:01 の差分が 23:59:59 なので 0日。
という動きのようだ。
どうすればいいか
「日付」で比較したいのだからこうしたらいいのでは。
「時分秒ミリ秒にゼロをセットしちゃう」作戦
function sample3() {
const deadline_date = new Date("2020-09-10 00:00:00");
const deadline_moment = Moment.moment(deadline_date);
// 時分秒ミリ秒にゼロをセットしちゃう
const today_moment = Moment.moment().hour(0).minutes(0).second(0).millisecond(0);
const diff = deadline_moment.diff(today_moment, "days");
console.log(diff);
}
これで、実行時刻が「2020-09-09 11:11:11」であっても「1」が出力されます。
↓ 念のため実験
function sample4() {
const deadline_date = new Date("2020-09-10 00:00:00");
const deadline_moment = Moment.moment(deadline_date);
const now = new Date("2020-09-09 11:11:11")
const today_moment = Moment.moment(now).hour(0).minutes(0).second(0).millisecond(0);
const diff = deadline_moment.diff(today_moment, "days");
console.log(diff); //=> 1
}
予想通り!
本当にそうなの?
"days" で比較するときに、「24時間未満かどうかで判定している」というのは、動作させた結果を見て私が推測しているところなのですが、実際どういう実装になってるのか momentライブラリのdiff.jsソースコードを見てみました。(これはmoment.jsのソースコードであってGASのライブラリのコードではないので、GASでは違う実装かもしれませんが、きっと同じであろう)
export function diff(input, units, asFloat) {
var that, zoneDelta, output;
if (!this.isValid()) {
return NaN;
}
that = cloneWithOffset(input, this);
if (!that.isValid()) {
return NaN;
}
zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4;
units = normalizeUnits(units);
switch (units) {
// 略
case 'day':
output = (this - that - zoneDelta) / 864e5;
break; // 1000 * 60 * 60 * 24, negate dst
// 略
}
return asFloat ? output : absFloor(output);
}
864e5
とは 1日をミリ秒にしたときの 86400000 の指数表記。
よって 時刻の差分を 1日を表すミリ秒で割ってる のね。
diff関数の第3引数である asFloat
は指定していないので、
return asFloat ? output : absFloor(output);
では absFloor(output)
が return される。
absFloor()
関数の定義は こちら
export default function absFloor(number) {
if (number < 0) {
// -0 -> 0
return Math.ceil(number) || 0;
} else {
return Math.floor(number);
}
}
Math.ceil() : 引数として与えた数以上の最小の整数を返します。
Math.floor() : 引数として与えた数以下の最大の整数を返します。
ということなので、output(時刻の差分を 1日を表すミリ秒で割った数値) が
- 0.999 であれば 0 を返すし、
- 1.234 なら 1 を返すし、
- -3.55 なら -3 を返す。
私の推測はあってたようです!(...というここまでの流れが間違ってたら教えてほしいです)
結論
ということで 「日にち」を比較したいのであれば、日にち(date)より小さい単位(hour, minutes, second, millisecond)はゼロで埋めてあげる
補足ですが、ミリ秒がでてくるなら
const deadline_date = new Date("2020-09-10 00:00:00.000");
としたほうが粒度は揃いますね。
(ミリ秒まで指定してDateオブジェクトをnewしたことが無いのでへんな感じする)