GASでカレンダー予定を新しいシートに保存する方法

GASカレンダー自動化
GASでカレンダー予定を新しいシートに保存する方法のサムネイル

カレンダー予定を、元の予定に触れずに保存する

おじさんが今回紹介するのは、Googleカレンダーの今後7日間の予定を取得し、新しいGoogleスプレッドシートに保存するGASです。予定の自動登録や更新そのものは行いません。変更を自動化する前に、予定の一覧を手元で確認したい方に向いています。

専用の連携SaaSを追加契約せず、Googleアカウントの利用枠内で動かせます。ただし、Apps ScriptやGoogle APIには実行時間・呼び出し回数の上限があります。

初期設定と必要な権限

  1. Googleドライブで新しいApps Scriptプロジェクトを作り、下のコードを貼り付けます。
  2. エディタ左側の「サービス」から「Google Calendar API」を追加します。標準のGoogle Cloudプロジェクトを使っている場合は、Cloud側でもCalendar APIを有効にします。
  3. saveUpcomingCalendarEventsを選んで一度実行し、カレンダーへのアクセスとスプレッドシート作成に必要な権限を確認します。所属組織の制限がある場合は管理者の方針に従ってください。
  4. 作成されたファイルはGoogleドライブで探します。共有設定は変更しません。予定名・説明・場所には個人情報が含まれるため、共有先を慎重に確認してください。

対象は実行するアカウントのメインカレンダーです。アクセスできないカレンダーや取得エラーがあれば、書き出しファイルを作る前に処理が止まります。

空の予定表や大量の予定にも対応するコード

取得は1ページ250件、最大20ページまでです。上限に達してまだ続きがある場合は、不完全な一覧を保存せずに止めます。保存先は毎回新しいファイルなので、既存の予定やシートを上書きしません。

function saveUpcomingCalendarEvents() {
  const lock = LockService.getScriptLock();
  if (!lock.tryLock(10000)) {
    throw new Error('別の処理が実行中です。時間を置いて実行してください。');
  }
  try {
    const calendarId = 'primary';
    const start = new Date();
    const end = new Date(start.getTime() + 7 * 24 * 60 * 60 * 1000);
    const rows = [];
    let pageToken;
    for (let page = 0; page < 20; page++) {
      const options = {
        timeMin: start.toISOString(),
        timeMax: end.toISOString(),
        singleEvents: true,
        orderBy: 'startTime',
        maxResults: 250
      };
      if (pageToken) options.pageToken = pageToken;
      const result = Calendar.Events.list(calendarId, options);
      for (const event of (result.items || [])) {
        if (!event.id || !event.start || !event.end) {
          throw new Error('必須項目のない予定があります。取得元を確認してください。');
        }
        rows.push([
          event.id, event.summary || '',
          event.start.dateTime || event.start.date || '',
          event.end.dateTime || event.end.date || '',
          event.description || '', event.location || ''
        ]);
      }
      pageToken = result.nextPageToken;
      if (!pageToken) break;
    }
    if (pageToken) {
      throw new Error('取得上限を超えました。対象期間を短くしてください。');
    }
    // 数式として解釈される文字列は、文字列として保存します。
    const safeText = value => {
      const text = String(value);
      return /^[=+@-]/.test(text) ? "'" + text : text;
    };
    const stamp = Utilities.formatDate(
      new Date(), Session.getScriptTimeZone(), 'yyyyMMdd_HHmmss'
    );
    const output = SpreadsheetApp.create('CalendarList_' + stamp);
    const sheet = output.getSheets()[0];
    sheet.getRange(1, 1, 1, 6).setValues([
      ['予定ID', '予定名', '開始', '終了', '説明', '場所']
    ]);
    if (rows.length > 0) {
      sheet.getRange(2, 1, rows.length, 6)
        .setValues(rows.map(row => row.map(safeText)));
    }
    // 予定本文、カレンダーID、保存先URLはログに出しません。
    console.log('新しい保存用シートを作成しました。');
  } finally {
    lock.releaseLock();
  }
}

予定が0件でも、見出し行だけの新しいファイルが作られます。複数ページに分かれた予定はページをたどって取得し、書き込みはまとめて行います。繰り返し予定は指定期間内の各予定として展開されます。

取得期間に開始前から重なる予定も含まれることがあります。終日予定は日付で保存され、その終了日は翌日などの排他的な日付です。時刻付き予定の日時はAPIが返したタイムゾーン情報を含む文字列のまま残します。

定期実行と、失敗したときの扱い

手動で動作を確認してから、Apps Scriptの「トリガー」でこの関数を実行する時間主導型のインストール可能トリガーを設定できます。トリガーは作成した人の権限で実行されます。毎回新しいファイルが増えるため、保存頻度と保管期間を先に決めてください。

エラーが出たら、実行履歴で原因を確認します。途中で作られた保存用ファイルがある場合は、完全な一覧か確認するまでは使わず、対象期間や権限を確認して再実行してください。元のカレンダーを変更していないので、カレンダーを元に戻す操作は不要です。トリガーを止めれば定期実行も止まります。

この一覧は確認用のコピーであり、参加者、通知設定、添付ファイルなどを含む完全な復元用バックアップではありません。予定の自動登録・更新へ進む場合は、別途、重複防止・変更前後の照合・復元手順を設計してください。

例えば、一覧作成に毎日10分かかり、作業単価を時給2,000円、月20日と仮定すれば、10÷60×2,000×20で約6,667円相当の作業時間になります。実際に削減できる時間は、確認作業や運用によって変わります。

GASおじラボのツールや運用例を詳しく知りたい方は、メンバーシップの案内をご覧ください。提供内容・料金は案内ページでご確認ください。


GASおじラボで500ツール使い放題 →