Googleカレンダーで複数のカレンダーを物理的に統合して表示する

サーバーサイド
複数の職場があり、それぞれでGoogle Calenderを利用している時に「月曜の12時から予定取らせてもらいました」と両方から言われないようにするには、Google Apps Scriptが有効です。
Google Apps Scriptでは「特定のカレンダーの更新」をトリガーとしてスクリプトを動かせるため、例えば「Aのカレンダーが更新された時、その更新内容をSlackに通知」というテクニックが作例で挙げられます。

そのケースではCalender APIのEvents:ListのAPIを利用して、「前回スクリプト実行時よりカレンダーが更新された差分を取得」するのだが、この時に時間や内容を変更した場合、「変更前はどのようなデータだったのか」という情報が得られないので、カレンダーのイベントのユニークIDに対してデータを管理する中間データをSpreadSheetやDBで保持する必要があるなど、愚直に実装しようとすると手がかかる。そのため、「定期的に直近2週間の予定を取得し、その結果を反映する」動きであれば擬似的に自分のサブカレンダーのデータをメインカレンダーに作ることができる。

以下作例。
const subCalendarId = "myCalender@example.com"; //同期させたいカレンダーIDを入れる
const mainCalendarId = 'myCalender2@example.com';

function syncSubCalendar() {
  var timeMin = new Date().toISOString();
  var timeMax = new Date(new Date().getTime() + 86400 * 1000 * 14).toISOString();// 2週間後の今
  var options = {
    singleEvents: true,
    timeMin: timeMin,
    timeMax: timeMax
  }
  var subEventList = Calendar.Events.list(subCalendarId, options);
  var mainEventList = Calendar.Events.list(mainCalendarId, options);
  
  // Sub Eventのidをリスト化
  var subEventSummaries = {};
  for (var i=0; i < subEventList.items.length; i++ ) {
    subEventSummaries['private: [' + subEventList.items[i].id + ']'] = subEventList.items[i];
  }

  // Main Eventsに存在する「private: [...]」のイベントをリスト化して、subEventSummariesと比較
  for (var i=0; i < mainEventList.items.length; i++) {
    if(!mainEventList.items[i].organizer.self || mainEventList.items[i].summary.indexOf('private: [') < 0 ||
      Object.keys(mainEventList.items[i].start).indexOf('dateTime') < 0 ||
      Object.keys(mainEventList.items[i].end).indexOf('dateTime') < 0) {
      continue;
    }
    // private eventの場合
    if (Object.keys(subEventSummaries).indexOf(mainEventList.items[i].summary) > 0) {
      var subEvent = subEventSummaries[mainEventList.items[i].summary];
      // 開始・終了時間が一致しないならばアップデート
      if ( mainEventList.items[i].start.dateTime!== subEvent.start.dateTime ||
      mainEventList.items[i].end.dateTime!== subEvent.end.dateTime){
        console.log('時間が違うのでアップデート');
        Calendar.Events.update(
          {
            start: subEvent.start,
            end: subEvent.end,
            summary: mainEventList.items[i].summary
          },
          mainCalendarId,
          mainEventList.items[i].id,
          {},
          {'If-Match': mainEventList.items[i].etag}
        );
      }
      
      // subEventSummariesから値をdelete
      delete subEventSummaries[mainEventList.items[i].summary];
    } else {
      // 同じidのイベントが存在しないので、削除として判定
      console.log('delete event: ' + mainEventList.items[i].summary);
      Calendar.Events.remove(mainCalendarId, mainEventList.items[i].id);
    }
  }

  // ここまでに残ったsubEventSummariesは、新規追加分のイベントとして判定
  for (var i=0; i<Object.keys(subEventSummaries).length;i++) {
    var event = subEventSummaries[Object.keys(subEventSummaries)[i]];
    if (Object.keys(event.start).indexOf('dateTime') < 0 ||
       Object.keys(event.end).indexOf('dateTime') < 0) {
      // 1日中やるイベントは追加しない
      continue;
    }
    console.log('add event: ' + event.id);
    Calendar.Events.insert({
      end: event.end,
      start: event.start,
      summary: 'private: [' + event.id + ']'
      }, mainCalendarId);
  }
}
カレンダーイベントの同一性を示すプロパティは id なので、それを利用してAPIコールの回数を必要最小限にしつつ同期を取るような実装にしている。
変数名はサブカレンダーの内容をメインカレンダーに移すようなイメージ。
メインカレンダーの所有者のロールでGoogle Apps Scriptを作成して、30分おきの定期実行で仕込んでおくと良い感じにプライベートの予定を仕事相手に見せておく事ができます。

都合、summary(イベントのタイトル)にidを書いてしまったのですがdescriptionもEventList APIから取得できるので、説明欄にidを書いて同期しておいて、summaryは単純に[予定あり]とかにしちゃうのが外部への見え方としては綺麗かも。

あとは物理的なカレンダー統合の手段として複数のカレンダーをキュレーションするためのスクリプトとしても生きるかも。そういう時はちゃんとCalender APIを呼ぶ時にtry-catchで囲ってあげてください(?)