GoogleAppMaker

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

Google App Maker の CalendarSample

https://developers.google.com/appmaker/samples/calendar/

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

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

原因

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

[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) をコメントアウトしておきます。

[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() としましたが、お好みでどうぞ。

[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() 内では初期化後にこの関数を呼びます。

[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
changeEvent2.png

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

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

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

以上でOK!


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