Google App Maker + CalendarApp の日付フィルタ調整

#Google App Maker の CalendarSample 
https://developers.google.com/appmaker/samples/calendar/

このサンプルの日付フィルタ機能を試すと、指定した開始日の前日夕方のイベントも表示されます。
逆に、指定した終了日の夕方以降のイベントが取得されません。

お察しの通り、ここに~~は~~もタイムゾーンの問題がありました。
（念のため：2017年9月現在の話です。）

#原因

サーバ側で動作するスクリプトの getEvents_() を見てみると、検索範囲の開始日と終了日の時刻を 00:00 にセットしている箇所があります。

```js:[ServerScript]
function getEvents_(query) {
  var startDate = query.parameters.StartDate;
  startDate.setHours(0, 0, 0, 0); //<===========attn

  var endDate = query.parameters.EndDate;
  endDate.setDate(endDate.getDate() + 1);
  endDate.setHours(0, 0, 0, 0); //<===========attn

  if (startDate.getTime() > endDate.getTime()) {
    return [];
  }

  var results = [];
  var events = CalendarApp.getDefaultCalendar().getEvents(startDate, endDate);
```

重要なのは、サーバー側でスクリプトが動作する際、日付はすべて太平洋標準時PST（夏ならPDT）の扱いになっているという前提です。

そのため、```startDate.setHours(0, 0, 0, 0)``` すると ```startDate``` は PST/PDT 00:00、つまり日本標準時JST 17:00（夏は16:00）にセットされます。

結果、対象カレンダーの予定が JST で作成されている場合、```getEvents(startDate, endDate)``` は「開始日前日17:00 ～ 終了日当日17:00」（夏は 16:00）の検索結果を返してくる、というのが本現象の動きです。

#対処

× ServerScript 内で日付を PST/PDT ⇒ JST へ変換して処理する
○ ClientScript 内であらかじめクエリ時刻を 00:00 に設定する

前者は、ユーザーが JST 環境限定ならよいのですが汎用性に欠けます。
まして、サーバーが今後も PST/PDT で動作を続ける保証もありません。

一方、クライアント側スクリプトはユーザーのタイムゾーンで動作します。
そのため 00:00 のセットはユーザー側で行うのが最良です。

##初期化時の 00:00 セット

まず、サーバー側のスクリプトから ```setHours(0, 0, 0, 0)``` をコメントアウトしておきます。

```js:[ServerScript]
function getEvents_(query) {
  var startDate = query.parameters.StartDate;
  //startDate.setHours(0, 0, 0, 0); //<===========comment-out

  var endDate = query.parameters.EndDate;
  endDate.setDate(endDate.getDate() + 1);
  //endDate.setHours(0, 0, 0, 0); //<===========comment-out
```

続いて、クライアント側スクリプトの loadEvents() より前に、「クエリ中の日時を 00:00 にセットし直す関数」を作成します。
本例では関数名を updateQueryDates() としましたが、お好みでどうぞ。

```js:[ClientScript]
function updateQueryDates(){
  var ds = app.datasources.Events;
  
  //Copy dates from query params and set time to: 00:00
  var sd = new Date( ds.query.parameters.StartDate.getTime() );
  var ed = new Date( ds.query.parameters.EndDate.getTime() );
  sd.setHours( 0, 0, 0, 0 );
  ed.setHours( 0, 0, 0, 0 );
  
  //Set back to query
  ds.query.parameters.StartDate = sd;
  ds.query.parameters.EndDate = ed;
}
```

そして、loadEvents() 内では初期化後にこの関数を呼びます。

```js:[ClientScript]
/**
 * Loads Events datasource.
 */
function loadEvents() {
  var ds = app.datasources.Events;
  ds.query.parameters.StartDate = new Date();
  ds.query.parameters.EndDate = new Date();
  updateQueryDates(); //<===========insert
  ds.load();
}

```

ここまでで、ロード時の初回検索は正常に動作するようになるはずです。

「初期化時に 00:00 をセットすれば？何故一度保存して上書きなんて面倒なことを？」
⇒後述の onValueEdit イベントでも同関数を利用する前提で、汎用化を図った結果です。
onValueEdit での処理を採用しない場合は、loadEvents() 内で new Date() したものを直接 setHours() で下処理するだけでOKです。

##Date Box 操作時の 00:00 セット


*<2017/09/22追記>
Date Box については、Date Picker からの選択時に 00:00 が自動的にセットされるので、ここから先は本来不要です。
（コメント欄参照。howdy39さんご指摘ありがとうございます。）
勝手に DateTime Picker に進化するとか、そんな将来の仕様変更を恐れる方は、適用しておくと安心でしょう。
</追記ここまで>*

![changeEvent1.png](https://qiita-image-store.s3.amazonaws.com/0/146352/b6f676ec-c75f-905e-fd92-61316f887f53.png)
![changeEvent2.png](https://qiita-image-store.s3.amazonaws.com/0/146352/c9a0ac14-56bd-712d-3f94-66124e5f4872.png)

対象の Date Box を選択して、Events > onValueEdit を、プリセットアクションの Reload Datasource からカスタムアクションに変更し、上で作成した関数を load() の前で呼ぶようにします。

```js:[CustomAction]
updateQueryDates(); //<===========insert
widget.datasource.load();
```

終了日指定の Date Box にも、同様のイベント調整を行ってください。

以上でOK！

---
（雑感）
Googleの仕組みはすべてシリコンバレー仕様。
グローバル企業なのだから UTC にしてくれればよいのに・・・
そうすればサンプルを作るGoogle社員も、こんな問題は予め気づいてくれたことでしょうに。