GASで月末日付を計算する方法と注意点
GASで月末日付を計算する方法を紹介します。
GASで日付を扱う場合、いくつか罠がありますので、その罠に、はまらないように気を付けましょう。
標準のDateオブジェクトを使って月末日を取得する
まずは、GAS(JavaScript)標準のDateオブジェクトを使って月末日を取得する方法を2つ紹介します。
ただし、標準のDateオブジェクトを使う方法は、どちらも罠が多いため、あまりおすすめはしません。
基本的には、このページの後半に紹介する「Day.js」を使って加工する方法をおすすめします。
ただ、ライブラリを使いたくないなどの理由で、どうしても標準のDateオブジェクトで処理をしたい、という場合には「方法1」を使うことをおすすめします。
方法1:年・月を指定して新しいDateオブジェクトを作る
function endOfMonthTest1() {
//元の日付
const d = new Date('2020/01/30');
//「新たに」月末日のDateオブジェクトを作成
const d_endOfMonth = new Date(d.getFullYear(), d.getMonth() + 1, 0);
//月末日をスプレッドシートに出力
SpreadsheetApp.getActiveSheet().getRange(1, 1).setValue(d_endOfMonth);
}
6行目の右辺で、元々のDateオブジェクトの「年」「月」を使って、月末日を計算しています。
そのとき、日付部分(3つ目の引数)を「0」にしているのがポイントです。「0」を指定すると、「1日の1日前」=「前月末日」の日付を計算することができます。
この方法を使うと、変数「d1」、変数「d1_endOfMonth」には、別々のDateオブジェクトが格納されます。そのため、「d1_endOfMonth」の内容を変えても「d1」は連動して変わらず、安心して使えます。
方法2:Dateオブジェクトに「翌月0日」をセットする
function endOfMonthTest2() {
//元の日付
const d = new Date('2020/01/30');
//「元のDateオブジェクト」を「翌月0日」に修正
d.setMonth(d.getMonth() + 1, 0);
//月末日をスプレッドシートに出力
SpreadsheetApp.getActiveSheet().getRange(1, 1).setValue(d);
}
Dateオブジェクトの「setMonth」メソッドを使うと、元々のDateオブジェクトの月と日を変えることができます。
上記プログラムでは、6行目で「setMonth」メソッドを使って、変数dに格納されたDateオブジェクトを「翌月0日」の日付になるように修正しています。
なお、この「方法2」は、気を付けて使わないと誤動作をすることがあります。この方法を使うことはおすすめしませんし、この方法を使わざるを得ない場合には、細心の注意を払いましょう。
ダメな例1:元の変数が変わる
function endOfMonthTest2_2() {
//元の日付
const d = new Date('2020/01/30');
//月末日付格納用の変数を準備し、元の変数をコピー
const d_endOfMonth = d;
//「元のDateオブジェクト」も「翌月0日」に変更される
d_endOfMonth.setMonth(d.getMonth() + 1, 0);
//当初の変数「d」の値は?
SpreadsheetApp.getActiveSheet().getRange(1, 1).setValue(d);
}
元の日付とは、別の変数に月末日付を格納するために、6行目で変数「d_endOfMonth」を作り、変数「d」の内容をコピーしています。
その後、変数「d_endOfMonth」で、月末日付を計算しています。
一見、これで意図通りに動きそうですが、実際に動かしてみると、元々の変数「d」も、月末の日付に変わってしまいます。
原因は「6行目」です。
//月末日付格納用の変数を準備し、元の変数をコピー
const d_endOfMonth = d;
今回のように、単に変数を代入すると、変数「d」と変数「d_endOfMonth」は、同じDateオブジェクトが変数に格納された状態になります。
この状態で、9行目のように変数「d_endOfMonth」を操作すると、変数「d」の内容も連動して変わってしまうことに注意が必要です。
//「元のDateオブジェクト」も「翌月0日」に変更される
d_endOfMonth.setMonth(d.getMonth() + 1, 0);
ダメな例2:setMonth、setDateを別々に実行する
function endOfMonthTest2_3() {
//元の日付
const d = new Date('2020/01/30');
//「元のDateオブジェクト」を「2020/2/30」(=「2020/3/1」)に変更
d.setMonth(d.getMonth() + 1);
//「元のDateオブジェクト」を「2020/3/0」(=「2020/2/29」)に変更
d.setDate(0);
//月末日をスプレッドシートに出力?
SpreadsheetApp.getActiveSheet().getRange(1, 1).setValue(d);
}
「方法2」を少し変えて、「setMonth」「setDate」の各メソッドで、月・日を別々に指定しています。
実は、このようにしてしまうと、誤動作の原因となります。
実際、このプログラムを動かしてみると「2020/02/29」と表示され、月末日が正しく計算できていないことがわかります。
うまく動かない原因は、「setMonth」メソッドの挙動にあります。
月以外は、既存のDateオブジェクトの値が使われる
//「元のDateオブジェクト」を「2020/2/30」(=「2020/3/1」)に変更
d.setMonth(d.getMonth() + 1);
「setMonth」メソッドで、「月」だけを指定した場合、「日」の部分は、元のDateオブジェクトの値がそのまま使われます。
今回の例であれば、元々、「日」には「30」が指定されていたので、それがそのまま使われます。つまり、元々の日付が「2020/1/30」だったので、その「月」の部分を「2」に変えた「2020/2/30」の日付データが作成されます。
存在しない日付を指定すると、自動的に補正がかかる
//「元のDateオブジェクト」を「2020/3/0」(=「2020/2/29」)に変更
d.setDate(0);
そして、このような存在しない日付が指定された場合、自動的に補正がかかります。その結果、「2020/2/30」は「2020/2/29」の1日後ということで、「2020/3/1」のデータが作成されてしまうのです。
日付を「0」にすると「2020/2/29」のデータになってしまう
その後、「setDate」メソッドを実行すると、日付部分が「0」になります。このメソッドを実行する直前の、変数dの値は「2020/3/1」ですから「2020/3/0」の日付を指定したことになります。
これも、存在しない日付ですので、自動的に日付の補正がかかり、「2020/3/1」の1日前である「2020/2/29」の日付データができあがります。
このようにして、本来は「2020/1/31」の日付データが欲しかったのに、実際には「2020/2/29」の日付データができあがってしまいました。
「Day.js」を使って月末日を取得する
JavaScript標準のDateオブジェクトを使って月末日付を計算しようとすると、様々な罠にはまりがちです。
ですから、日付関連の処理を書くときには、基本的には、日付処理用ライブラリを使うことをおすすめします。
ここでは、GASで簡単に使える日付処理ライブラリ「Day.js」を使って、処理を書いていきます。
GASでDay.jsを使う方法は、下記をご覧ください↓
方法3:1か月後の0日の日付を求める
function dayjsTest() {
//元の日付
const d = new Date('2020/01/30');
//元のDateオブジェクトの月末日付を計算
const d_endOfMonth = dayjs.dayjs(d) //Day.jsオブジェクトを作成
.add(1, 'month') //1か月後の日付「2020/2/29」を作成
.date(0) //0日の日付「2020/2/0」(=「2020/1/31」)を作成
.toDate(); //Dateオブジェクトに戻す
//月末日をスプレッドシートに出力
SpreadsheetApp.getActiveSheet().getRange(1, 1).setValue(d_endOfMonth);
}
いったん、DateオブジェクトからDayjsオブジェクトを作成し、日付データを加工した後に、最後にDateオブジェクトに戻す処理をしています。
「日付データを加工」する処理では、「2020/1/30の1か月後の日付」を作成し、「日の部分を0にする」ことで、「2020/1/31」の日付を計算しています。
実は、この考え方は、さきほど紹介した「方法2:Dateオブジェクトに「翌月0日」をセットする」の「ダメな例2:setMonth、setDateを別々に実行する」と、ほとんど同じです。なぜ、Dateオブジェクトではうまくいかなかったのに、Day.jsを使う方法でうまくいくのでしょうか?
それは、Day.jsの「add」メソッドの挙動が、Dateオブジェクトの「month」メソッドと違うからです。実は、Day.jsで「add」メソッドで「2020/1/30」の1か月後の日付を計算すると「2020/2/29」の日付が得られます。
あとは、日付の部分を「0」にすることで、「2020/2/0」→「2020/1/31」の日付が得られるのです。
方法4:endOfメソッドを使って月末日付を計算する
function endOfMonthTest4() {
//元の日付
const d = new Date('2020/01/30');
//元のDateオブジェクトの月末日付を計算
const d_endOfMonth = dayjs.dayjs(d) //Day.jsオブジェクトを作成
.endOf('month') //月末日付「2020/1/31 23:59:59.999」を作成
.millisecond(0) //秒未満の端数を0にする「2020/1/31 23:59:59.000」
.toDate(); //Dateオブジェクトに戻す
//月末日をスプレッドシートに出力
SpreadsheetApp.getActiveSheet().getRange(1, 1).setValue(d_endOfMonth);
}
Day.jsには、endOfメソッドがあり、それを使うと、月末日付を直接計算することができます。
ただし、endOfメソッドを使うと、時間以下が自動的に「23:59:59.999」に設定されてしまうことに注意してください。
上のプログラムでは、millisecondメソッドを使って秒未満を「000」に修正したうえで、Dateオブジェクトを作成しています。
こうすることで、スプレッドシートに「2020/1/31 23:59:59」の日付データとして出力することができます。
秒未満の端数に注意
この方法を使うときには、秒未満の端数に注意してください。
たとえば、下記のプログラムを実行すると、コンソールには「2020/01/31 23:59:59.999」と表示されるにもかかわらず、スプレッドシートには「2020/2/1」と表示されます。
function endOfMonthTest5() {
//元の日付
const d = new Date('2020/01/30');
//元のDateオブジェクトの月末日付を計算
const dayjs_endOfMonth = dayjs.dayjs(d) //Day.jsオブジェクトを作成
.endOf('month') //月末日付「2020/1/31 23:59:59.999」を作成
//計算した月末日付をコンソールに出力
//「2020/01/31 23:59:59.999」
Logger.log(dayjs_endOfMonth.format('YYYY/MM/DD HH:mm:ss.SSS'));
//Dateオブジェクトに変換
const d_endOfMonth = dayjs_endOfMonth.toDate();
//月末日をスプレッドシートに出力(※秒未満は四捨五入される)
//「2020/02/01 00:00:00」
SpreadsheetApp.getActiveSheet().getRange(1, 1).setValue(d_endOfMonth);
}
スプレッドシートにDateオブジェクトを転記するときに、1秒未満の端数は小数第1位で四捨五入されます。
そのため、endOfメソッドで作成した「2020/1/31 23:59:59.999」のDateオブジェクトをスプレッドシートに書き込むと「2020/2/1 00:00:00」に変化してしまいます。
これでは、月末日の日付になりませんので、millisecondメソッドを使って秒未満を「000」にしておく必要があることに注意してください。
まとめ
月末日付の計算をするときには、Day.jsを使うと、少し楽ができます。ただしendOfメソッドを使う場合には、millisecondメソッドを使って、millisecondを「000」にしないと、不具合が出ますので気を付けてください。