gasの日付計算で罠にハマった話

さて、私は前回、[gasで月初日と月末日を取得する](https://qiita.com/rikkeydevinfo/items/5f06ab7428f96746dede)ときに、`moment.js`という便利ライブラリを知りました。

これを使って、「当月の月初から月末までの日付を取得しよう」と思いました。
さて、普通に以下のようにすれば取得できると思って書いてみたら1時間くらいハマりましたね。

```js

function test()
{
  var today          = new Date(); // 今日
  var firstDate      = new Moment.moment(new Date(today.getFullYear(), today.getMonth(), 1)); // 月初
  var daysInMonth    = firstDate.daysInMonth(); // 今月の日数
  var thisMonthDates = []; // 今月の日付リスト

  // 30日分をthisMonthDatesに入れてく
  for (var i = 0; i < daysInMonth; i++) {
    thisMonthDates.push(firstDate.add(i, 'days'));
  }

  // 結果を確認
  for (var i in thisMonthDates) {
    Logger.log(thisMonthDates[i].date());
  }
}

```

このようにして、まず月初から当月の日数分だけループを回して、その結果を`thisMonthDates`って配列に入れようという訳ですな。

それが結果を見たらどうでしょうか。
なんと、全部`2020-08-09日`になってる訳ですよ。

```
[19-06-16 14:44:48:674 JST] 2020-08-09
[19-06-16 14:44:48:675 JST] 2020-08-09
[19-06-16 14:44:48:676 JST] 2020-08-09
[19-06-16 14:44:48:677 JST] 2020-08-09
[19-06-16 14:44:48:677 JST] 2020-08-09
[19-06-16 14:44:48:678 JST] 2020-08-09
[19-06-16 14:44:48:679 JST] 2020-08-09
[19-06-16 14:44:48:679 JST] 2020-08-09
[19-06-16 14:44:48:680 JST] 2020-08-09
[19-06-16 14:44:48:681 JST] 2020-08-09
[19-06-16 14:44:48:681 JST] 2020-08-09
[19-06-16 14:44:48:682 JST] 2020-08-09
[19-06-16 14:44:48:683 JST] 2020-08-09
[19-06-16 14:44:48:683 JST] 2020-08-09
[19-06-16 14:44:48:684 JST] 2020-08-09
[19-06-16 14:44:48:685 JST] 2020-08-09
[19-06-16 14:44:48:686 JST] 2020-08-09
[19-06-16 14:44:48:686 JST] 2020-08-09
[19-06-16 14:44:48:687 JST] 2020-08-09
[19-06-16 14:44:48:688 JST] 2020-08-09
[19-06-16 14:44:48:688 JST] 2020-08-09
[19-06-16 14:44:48:689 JST] 2020-08-09
[19-06-16 14:44:48:690 JST] 2020-08-09
[19-06-16 14:44:48:691 JST] 2020-08-09
[19-06-16 14:44:48:691 JST] 2020-08-09
[19-06-16 14:44:48:692 JST] 2020-08-09
[19-06-16 14:44:48:693 JST] 2020-08-09
[19-06-16 14:44:48:693 JST] 2020-08-09
[19-06-16 14:44:48:694 JST] 2020-08-09
[19-06-16 14:44:48:694 JST] 2020-08-09
```

なんでや、と思って途中でLogを仕込んでみたら、どうもループの中で上書きされてるんですね。
つまり、for文の中で`Logger.log(thisMonthDates[0].format('YYYY-MM-DD')`とかってやると、こんな感じで、`0番目`がどんどん書き換わってる訳です。

これよく見ると、規則性があります。

* 6/1 => `1+0`
* 6/2 => `1+1`
* 6/4 => `2+2`
* 6/7 => `4+3`
* 6/11 => 7+4`


うん。すべて`firstDate`に入ってるはずの`2019-06-01`に対して足し算してってる訳じゃなくて、直前の日付に対して足していってるみたいです。

```
[19-06-16 14:46:52:601 JST] 2019-06-01
[19-06-16 14:46:52:602 JST] 2019-06-02
[19-06-16 14:46:52:603 JST] 2019-06-04
[19-06-16 14:46:52:604 JST] 2019-06-07
[19-06-16 14:46:52:605 JST] 2019-06-11
[19-06-16 14:46:52:606 JST] 2019-06-16
[19-06-16 14:46:52:607 JST] 2019-06-22
[19-06-16 14:46:52:608 JST] 2019-06-29
[19-06-16 14:46:52:609 JST] 2019-07-07
[19-06-16 14:46:52:610 JST] 2019-07-16
[19-06-16 14:46:52:612 JST] 2019-07-26
[19-06-16 14:46:52:613 JST] 2019-08-06
...続く
```

なんでだ！？！？！

### GASのMoment.jsは元のオブジェクトを更新するらしい

こんな非常にわかりやすい記事を見つけました。

[GAS版Moment.jsの破壊的な日時計算メソッドの注意点とcloneメソッドによる回避](https://tonari-it.com/gas-moment-destructive-method-clone/)

これによると、今回みたいに`firstDate`みたいなオブジェクトに対して`add`とか`set`すると、元の`firstDate`を更新しちゃうので、ループとかで同じオブジェクトに足し算とかしてると意図通りにならないと。

まさに今回の事例と一緒ですね！

### cloneしたオブジェクトに対してaddしてく

結局、元に`firstDate`オブジェクトにaddすると、元のオブジェクトを更新してしまうようなので、cloneしたオブジェクトに対してaddして、`firstDate`オブジェクトはそのままにしておくことにしました。

結果がこんな感じ。

```js

function test()
{
  var today          = new Date(); // 今日
  var firstDate      = new Moment.moment(new Date(today.getFullYear(), today.getMonth(), 1)); // 月初
  var daysInMonth    = firstDate.daysInMonth(); // 今月の日数
  var thisMonthDates = []; // 今月の日付リスト

  // 30日分をthisMonthDatesに入れてく
  for (var i = 0; i < daysInMonth; i++) {
    // ※ firstDate.clone()に対してaddする
    thisMonthDates.push(firstDate.clone().add(i, 'days'));
  }

  // 結果を確認
  for (var i in thisMonthDates) {
    Logger.log(thisMonthDates[i].format('YYYY-MM-DD'));
  }
}
```

↓ 結果

```
[19-06-16 14:52:45:298 JST] 2019-06-01
[19-06-16 14:52:45:299 JST] 2019-06-02
[19-06-16 14:52:45:300 JST] 2019-06-03
[19-06-16 14:52:45:301 JST] 2019-06-04
[19-06-16 14:52:45:301 JST] 2019-06-05
[19-06-16 14:52:45:302 JST] 2019-06-06
[19-06-16 14:52:45:303 JST] 2019-06-07
[19-06-16 14:52:45:303 JST] 2019-06-08
[19-06-16 14:52:45:304 JST] 2019-06-09
[19-06-16 14:52:45:305 JST] 2019-06-10
[19-06-16 14:52:45:306 JST] 2019-06-11
[19-06-16 14:52:45:306 JST] 2019-06-12
[19-06-16 14:52:45:307 JST] 2019-06-13
[19-06-16 14:52:45:308 JST] 2019-06-14
[19-06-16 14:52:45:308 JST] 2019-06-15
[19-06-16 14:52:45:309 JST] 2019-06-16
[19-06-16 14:52:45:310 JST] 2019-06-17
[19-06-16 14:52:45:311 JST] 2019-06-18
[19-06-16 14:52:45:311 JST] 2019-06-19
[19-06-16 14:52:45:312 JST] 2019-06-20
[19-06-16 14:52:45:313 JST] 2019-06-21
[19-06-16 14:52:45:313 JST] 2019-06-22
[19-06-16 14:52:45:314 JST] 2019-06-23
[19-06-16 14:52:45:314 JST] 2019-06-24
[19-06-16 14:52:45:315 JST] 2019-06-25
[19-06-16 14:52:45:316 JST] 2019-06-26
[19-06-16 14:52:45:317 JST] 2019-06-27
[19-06-16 14:52:45:317 JST] 2019-06-28
[19-06-16 14:52:45:318 JST] 2019-06-29
[19-06-16 14:52:45:319 JST] 2019-06-30
```

やったね！！