複数のGoogleカレンダーを統合して共有する
やりたいこと
「これだけ見れば袴谷の予定がわかるカレンダー」を作成する
背景
僕は元インターン先と現職、プライベートで合計3つのGoogleアカウントを所有しています。日程調整のためにカレンダーを見てもらうことが多いのですが、複数のカレンダーを見てもらうのは手間をかけてしまうため気が引けますよね。そこで、「これだけ見れば袴谷の予定がわかるカレンダー」の作成に至りました。
はじめに
早めに白状しておくと、この記事はパクリです。以下の記事を参考にしつつ、自分がよりしっくりくる実装に微調整しただけなので、パクリ反対派の方は元記事をご覧ください。
既知の問題
以下の問題は未解決です。これらを実装するために来てくださった方は、残念ですがお帰りください。
- 更新のタイミングで一時的(1分未満)に統合用カレンダーのイベントが見えなくなる
- 出欠回答に "いいえ" || "未定" と回答したイベントも "Busy" 扱いになってしまう
下準備
まずは、共有専用の統合カレンダー (integratedCalendar) を新規作成する必要があります。以下を参考に、好きな名前のカレンダーを作成してください。ちなみに自分は 袴谷優介 (Integrated)
にしています。
結論
最終的なコードは下記の通りです。最新版はこちらからご覧ください。
const calendarIntegrated = CalendarApp.getCalendarById('統合用カレンダーのID');
const calendarPersonal = CalendarApp.getCalendarById('個人用カレンダーのID');
const calendarWork = CalendarApp.getCalendarById('仕事用カレンダーのID');
function syncToday() {
deleteEventsForDays(calendarIntegrated, 1);
copyEventsForDays(calendarPersonal, calendarIntegrated, 'Personal', 1);
copyEventsForDays(calendarWork, calendarIntegrated, 'Work', 1);
}
function syncThisWeek() {
deleteEventsForDays(calendarIntegrated, 7);
copyEventsForDays(calendarPersonal, calendarIntegrated, 'Personal', 7);
copyEventsForDays(calendarWork, calendarIntegrated, 'Work', 7);
}
function syncThisMonth() {
deleteEventsForDays(calendarIntegrated, 30);
copyEventsForDays(calendarPersonal, calendarIntegrated, 'Personal', 30);
copyEventsForDays(calendarWork, calendarIntegrated, 'Work', 30);
}
function deleteEventsForDays(calendar, days) {
let date = new Date();
for (let i = 0; i < days; i++) {
const events = calendar.getEventsForDay(date);
for (const event of events) {
event.deleteEvent();
}
date.setDate(date.getDate() + 1);
}
}
function copyEventsForDays(calendarOrigin, calendarIntegrated, organization, days) {
let date = new Date();
for (let i = 0; i < days; i++) {
const events = calendarOrigin.getEventsForDay(date);
for (const event of events) {
let startTime, endTime;
startTime = event.getStartTime();
endTime = event.getEndTime();
calendarIntegrated.createEvent(
setTitle(organization),
startTime,
endTime
)
}
date.setDate(date.getDate() + 1);
}
}
function setTitle(organization) {
return `Busy (${organization})`
}
実装内容の説明
main.gs
まず、カレンダーのIDを設定します。ここでは統合用と個人用、仕事用の3つを用意しています。統合したいカレンダーが3つ以上ある場合は、その分増やしてください。
const calendarIntegrated = CalendarApp.getCalendarById('統合用カレンダーのID');
const calendarPersonal = CalendarApp.getCalendarById('個人用カレンダーのID');
const calendarWork = CalendarApp.getCalendarById('仕事用カレンダーのID');
次に、ベースとなる関数を記述します。基本的な流れは以下の通りです。
- 統合用カレンダーを全て削除する
- 個人用カレンダーのイベントを統合用カレンダーにコピーする
- 仕事用カレンダーのイベントを統合用カレンダーにコピーする
毎回全削除 → 全コピーを行うという不健全な実装になっている理由は、カレンダーの追加/削除を検知するAPIが用意されていないためです。もしこれよりも良いアイデアがあればお知らせください。
function syncToday() {
// 今日のイベントをcalendarIntegratedから全て削除
deleteEventsForDays(calendarIntegrated, 1);
// 今日のイベントをcalendarPersonalからcalendarIntegratedへ全てコピー
copyEventsForDays(calendarPersonal, calendarIntegrated, 'Personal', 1);
// 今日のイベントをcalendarWorkからcalendarIntegratedへ全てコピー
copyEventsForDays(calendarWork, calendarIntegrated, 'Work', 1);
}
function syncThisWeek() {
deleteEventsForDays(calendarIntegrated, 7);
copyEventsForDays(calendarPersonal, calendarIntegrated, 'Personal', 7);
copyEventsForDays(calendarWork, calendarIntegrated, 'Work', 7);
}
function syncThisMonth() {
deleteEventsForDays(calendarIntegrated, 30);
copyEventsForDays(calendarPersonal, calendarIntegrated, 'Personal', 30);
copyEventsForDays(calendarWork, calendarIntegrated, 'Work', 30);
}
sync.gs
ここが本プログラムの中核を成す部分です。関数2つの機能はそれぞれ以下の通りです。
-
deleteEventsForDays()
- 第一引数: 削除対象のカレンダーオブジェクトです。ここでは、calendarIntegratedのみが渡されます
- 第二引数: 日数です。この日数分のイベントを全て削除します
-
copyEventsForDays()
- 第一引数: コピー元のカレンダーオブジェクトです。ここでは、calendarPersonalもしくはcalendarWorkのいずれかが渡されます
- 第二引数: コピー先のカレンダーオブジェクトです。ここでは、calendarIntegratedのみが渡されます
- 第三引数: 日数です。この日数分のイベントを全てコピーします。
function deleteEventsForDays(calendar, days) {
// dateに今日の日付を入れる
let date = new Date();
// 渡された日数分繰り返す
for (let i = 0; i < days; i++) {
const events = calendar.getEventsForDay(date);
// その日のイベントを全て削除する
for (const event of events) {
event.deleteEvent();
}
// dateを1日進める
date.setDate(date.getDate() + 1);
}
}
function copyEventsForDays(calendarOrigin, calendarIntegrated, organization, days) {
let date = new Date();
// 渡された日数分繰り返す
for (let i = 0; i < days; i++) {
const events = calendarOrigin.getEventsForDay(date);
// その日のイベントを全て削除する
for (const event of events) {
let startTime, endTime;
startTime = event.getStartTime();
endTime = event.getEndTime();
calendarIntegrated.createEvent(
setTitle(organization),
startTime,
endTime
)
}
// dateを1日進める
date.setDate(date.getDate() + 1);
}
}
utils.gs
utils
と言いつつも関数は一つだけです。setTitle()
は上述の copyEventsForDays()
から呼び出されます。たとえばPersonalの時は、イベントのタイトルが Busy (Personal)
になります。
function setTitle(organization) {
return `Busy (${organization})`
}
トリガーのセット
まずは上記のプログラムをScript Editorにコピペし、一度実行してみてください。それが成功したら、最後にトリガーをセットし、自動化します。先述のエラーを防ぐため、最も負荷の高い syncThisMonth()
は頻度を低く、syncToday()
は最新の情報であることが求められるため頻度を高く設定します。これはご自身の感覚で設定してください。参考までに自分の設定をお示しします。
-
syncToday()
: 1時間おき -
syncThisWeek()
: 12時間おき -
syncThisMonth()
: 24時間おき(毎朝6~7時の間)
main.gs
に記載した関数3点に対して、それぞれトリガーをセット
たとえば、syncToday()
は1時間おきに実行する
参考にした記事
先述の通り、この記事は他人のパクリです。私の説明が不足している点は、ぜひこちらと併せてご確認ください。
Discussion