プライベートと仕事のダブルブッキングをGASで解消した

#GAS#Googleカレンダー#自動化

はじめに

fuku株式会社の山田です。今回は、私自身が長らく悩んでいた「プライベートの予定と仕事の予定がダブルブッキングしてしまう」問題と、それを解決するために試行錯誤した末にたどり着いたGoogle Apps Script(以下、GAS)での自動化手法について紹介します。

背景:プライベートと仕事の予定がよくバッティングする

私は個人用と仕事用の2つのGoogleカレンダーを使っています。個人用には家族や友人と過ごす予定、仕事用には商談や打ち合わせ、会食、出張などを記載しています。

仕事上の日程調整は主に日程調整ツールを使っています。指定した曜日、時間帯の中で仕事用カレンダーで空いている枠を相手に提示することができます。立場上、土日祝日や早朝・深夜などの営業時間外にも仕事の予定を入れたい場合もあるため、日程調整ツールから営業時間外のプライベートの予定を参照させる必要があります。しかし現在利用しているツールは複数のカレンダーを参照させることができないため、プライベートの予定が入ったら会社用のカレンダーに「私用」として手動で登録していました。

このような手作業が事故を起こすことは火を見るよりも明らかです。プライベートの予定の転記を再三確認したにも関わらずダブルブッキングが何度かありました。特に一度登録した予定の日時を変更するときに事故率が高まります。日程再調整はもっともげんなりする仕事の1つであり、いいかげん耐えられなくなったので自動化を試みました。

試したけど上手くいかなかったこと

1. Googleカレンダーの共有機能

まず最初に、Googleカレンダーのデフォルトの共有機能を使って以下の設定を試みました。

  • **個人用カレンダーに仕事用アカウントを招待
    ** 権限を「予定の表示」に設定。仕事用カレンダーにプライベートの予定が「busy」として表示される。予定の詳細は伏せてその時間に予定があることだけわかる。

上記設定で自分自身がカレンダーを見るときはプライベートの予定が入っていることがわかります。しかし当然ながら日程調整ツールからは参照されません(Other calendarではなくMy calendarとして登録することで解決するか試したが無理でした)。また、Google Workspaceに所属しているメンバーからもこのカレンダーは閲覧できません。私が手作業で日程調整をする場合に限り意味がありますが、他のツールやメンバーに参照してもらうため手作業で予定をコピーする必要があり、手間は減りませんでした。

2. Zapierで予定を反映する

次にIFTTTやZapierなどの外部連携サービスを使って、自動的に「プライベートの予定が入ったら、仕事用カレンダーにもブロックを作る」仕組みを作れないか試してみました。今回はZapierを選択しましたが、結論から言うとダメでした。

まず細かい条件を設定することが難しいです。ZapierではFilter stepやFormat stepで簡単な条件を記述することができ、複雑なことはCode stepにてJavaScriptかPythonで記述することができます。あくまで仕組み上は 。ウェブアプリケーション上の小さい画面で本格的なコーディングをするのは非常に辛いです。普段のコーディングで如何にIDE+Copilotに助けられているかを実感できます

ここでコードを書くのはIDE+Copilotに甘やかされた精神では耐えられない…

ここでコードを書くのはIDE+Copilotに甘やかされた精神では耐えられない…

加えて致命的な点として、一度コピーされた予定を後から変更したいケース(「やっぱり30分後ろにずらそう」など)が発生した場合に、Zapier経由で反映済みのコピーイベントをうまく同期するのが難しく、どんどんダミーのイベントが増えてしまいました。解決するためには元の個人用カレンダーの予定のidを取得し、仕事用カレンダーに予定を作成する際にそのidを埋め込むなどして、対応付けをする必要があります。

なんてことを考えたあたりで「あの小さい画面で大した補完もなくコード書くのしんどいなぁ」と心が折れてZapierは断念しました。

最終的な解決策

3. Google Apps Script(GAS)を使った自動化

結論として、GASで「プライベート→仕事」「仕事→プライベート」の両方向で予定を自動的に反映する のが一番しっくりきました。

以前にGASを使った経験もあるのですが、正直「書きにくい」「バージョン管理が面倒」「Drive上にあって見通しが悪い」という理由で敬遠していました。しかしZapierでの辛い開発経験を経たことでGASの気に食わない点を受け入れられる心情になり 、再チャレンジしてみることにしました。

最終的にはgoogle-calendar-sync-business2private とgoogle-calendar-sync-private2business という2つのGASを作成し以下の機能を実現しました。

  1. 個人用カレンダーに予定が作成 or 変更された場合、その時間を「私用」として仕事用カレンダーに反映(詳細は共有しない)。

  2. 仕事用カレンダーに予定が作成された場合、その中でも妻が把握しておく必要があるもの(営業時間外の予定:主に出張や会食、ミーティング)だけ個人用カレンダーに反映。タイトル、場所、日時だけわかればOK。詳細は共有しない。

  3. 予定が変更・削除された場合、コピー先のイベントも同様に変更・削除する。ダミーのイベントを増やさない

開発はChatGPT o1 proに主導してもらいました。JavaScriptもGASもノウハウはありませんでしたが、詰まるところはありませんでした。

コードはIDEで書き、GitHubでバージョン管理しつつ、claspでGASにデプロイするという流れで開発しました。GASのエディタ上では一切編集しないことを徹底 したことでだいぶ開発体験は改善されました。

実現できたこと

会社用カレンダーにプライベートな予定が反映される

会社用カレンダーにプライベートな予定が反映される

個人用カレンダーには営業時間外を含む予定が反映される

個人用カレンダーには営業時間外を含む予定が反映される

(ChatGPTが)工夫したところ

  • 予定のdescriptionに“Created by Google App Script”という文言を入れることで一括削除の条件として使えるようにした。

  • Googleカレンダー上からは閲覧できないプロパティextendedProperties.privateに元のカレンダーのidを埋め込むことでコピー元とコピー先の対応づけを記録した。

雑感

  • 「ちょっとしたコードだから」と機能の少ないブラウザ(Zapier然りGAS然り)で書き始めると、思いがけずコードが膨らんで途中から苦しくなることを何度か繰り返し、「最初から綺麗に書いた方がいい」という人生何度目かになる学びを得ました

  • claspを使ったけどやっぱり微妙。Calendar APIの有効化はGASのブラウザ上で手作業でやる必要あり片手落ち感。IaCチックにすべてコードで制御できないもんか。

  • コピー対象の予定に繰り返しイベントがある場合は気をつけてください 。開発途中、コピー元とコピー先の予定を対応づけてなかったときは、繋がりのない大量の予定が作成されてしまい大変でした。一括削除のコードを書いて対処しましたが、手間が増えました。dry runオプションをつけるのが吉。