【豆知識】Googleスプレッドシートを使ってGoogleカレンダーに予定をワンクリックで追加・削除する方法【第二弾】

作業効率化
この記事は約23分で読めます。

前回はGoogleカレンダーにGoogleスプレッドシートで書いた予定を追加・登録するということを紹介いたしました。

今回はその第二弾!

Googleカレンダーに日々の予定を一括で追加、削除できたら便利ですよね。
手間のかかる作業とはおさらば!Googleスプレッドシートを使って日々の予定を一括でワンクリックで追加・削除する方法について解説していきたいと思います。

最近、第三弾も書かせていただきました。よろしくお願いいたします

スポンサーリンク

Google スプレッドシートを開く

Googleのサイトを開き、右上の3つの「」をクリックして、下にスクロールして出てくる「スプレッドシート」を選択します。

スプレッドシートを開いて、「空白のスプレッドシート」を作成します。

新しいスプレッドシートが開けました。

表を作る

まず表を作成します。まずタイトルを付けます。「カレンダー追加・削除用」とでもしましょう。次にシート名も変更しておきます。わかりやすく「カレンダー」とでも付けておくとよいでしょう。行番号と列番号に気をつけて表を作成してください。カレンダーを登録するユーザーの「メールアドレス」も登録します。メールアドレスは「B列7行目」に記述するようにしてください。「9行目」に「連携」「日付」「曜日」「開始時間」「終了時間」「タイトル」「場所」「説明」の項目を作り、幅や色、枠線などを付けて成形します。

特定のユーザーまたはグループと共有する(登録アカウントが異なる場合)

登録用に使用しているスプレッドシートのユーザーと、登録先のカレンダーのユーザーが異なる場合には、次の設定を行う必要があります。

設定と共有を開く

マイカレンダーの登録先のカレンダーのユーザー名の横にある三点リーダー「」をクリックし、「設定と共有」を開きます。

予定のアクセス権限にユーザーを追加する

ユーザー名の「予定のアクセス権限」をクリックし、画面右側の「ユーザーやグループを追加」でスプレッドシートのユーザーを登録します。

次に与える権限として、「予定の変更」を選択します。

拡張機能を使ってプログラムを組み込む

メインプログラム

拡張機能」>「Apps Script」を開きます。

無題のプロジェクト」が開きます。タイトルを変更して「カレンダー追加・削除用マクロ」とでもしておいてください。

画像に alt 属性が指定されていません。ファイル名: 2024-02-16_09h55_26-1024x630.jpg

次の文章をコピペします。(バグ修正等の情報はこちら

/**
 * スプレッドシートを開いたときにカスタムメニューを追加する関数
 */
function onOpen() {
  const ui = SpreadsheetApp.getUi();
  ui.createMenu('カレンダーツール')
    .addItem('予定を作成', 'createSchedule')
    .addItem('予定を削除', 'deleteSchedule')
    .addToUi();
}

/**
 * カレンダーを追加登録する関数
 * この関数は一度だけ実行することを推奨します
 */
function addCalendar(id) {
  try {
    CalendarApp.subscribeToCalendar(id);
    Logger.log(`カレンダー ${id} を登録しました。`);
  } catch (e) {
    Logger.log(`カレンダー登録中にエラーが発生しました: ${e}`);
  }
}

/**
 * 時間を "HH:mm" 形式にフォーマットする関数
 * @param {Date} date 
 * @returns {string}
 */
function timeFormatter(date) {
  if (date instanceof Date && !isNaN(date)) {
    return Utilities.formatDate(date, Session.getScriptTimeZone(), 'HH:mm');
  } else {
    return '00:00';
  }
}

/**
 * 予定を作成する関数
 */
function createSchedule() {
  
  // 初期設定
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = spreadsheet.getSheetByName('カレンダー');
  
  // 連携するカレンダーIDを取得
  const gAccount = sheet.getRange("B7").getValue();  // セル B7 にカレンダーIDを入力
  Logger.log("gAccount=" + gAccount);
  
  // カレンダーを登録(必要に応じて一度だけ実行)
  addCalendar(gAccount);
  
  // 読み取り範囲(表の始まり行と終わり列)
  const topRow = 10;  // データの開始行
  const lastCol = 9;  // データの終了列(I列)
  
  // 列インデックス(0ベース)
  const STATUS_COL = 1;         // B列
  const DAY_COL = 2;            // C列
  const START_TIME_COL = 4;     // E列
  const END_TIME_COL = 5;       // F列
  const TITLE_COL = 6;          // G列
  const LOCATION_COL = 7;       // H列
  const DESCRIPTION_COL = 8;    // I列
  const EVENT_ID_COL = 9;       // J列(イベントIDを保存する列)
  
  // 予定の最終行を取得
  const lastRow = sheet.getLastRow();
  const numRows = lastRow - topRow + 1;
  if (numRows <= 0) {
    SpreadsheetApp.getUi().alert("データが存在しません。");
    return;
  }
  
  // 予定の一覧を取得
  const contents = sheet.getRange(topRow, 1, numRows, lastCol).getValues();
  
  // Googleカレンダーの取得
  const calendar = CalendarApp.getCalendarById(gAccount);
  if (!calendar) {
    Logger.log("カレンダーが取得できませんでした。カレンダーIDを確認してください。");
    SpreadsheetApp.getUi().alert("カレンダーが取得できませんでした。カレンダーIDを確認してください。");
    return;
  }
  
  // ユーザーインターフェースの取得
  const ui = SpreadsheetApp.getUi();
  
  // 予定を作成
  for (let i = 0; i < numRows; i++) {
    
    // 「済」または「空白」の場合は飛ばす
    const status = contents[i][STATUS_COL];
    const dayValue = contents[i][DAY_COL];
    if (
      status === "済" ||
      status === "済み" ||
      dayValue === ""
    ) {
      continue;
    }
    
    Logger.log(`Row ${topRow + i}: ステータス=${status}, 日付=${dayValue}`);
    
    // データの取得と検証
    const day = new Date(dayValue);
    const startTime = contents[i][START_TIME_COL];
    const endTime = contents[i][END_TIME_COL];
    const title = contents[i][TITLE_COL] || "タイトルなし";
    const location = contents[i][LOCATION_COL];
    const description = contents[i][DESCRIPTION_COL];
    const eventId = contents[i][EVENT_ID_COL];
    
    // オプションをセット
    const options = {
      location: location,
      description: description
    };
    
    try {
      let event;
      if (!startTime || !endTime) {
        // 終日イベントとして作成
        Logger.log(`終日イベント作成: タイトル=${title}, 日付=${day}`);
        event = calendar.createAllDayEvent(title, day, options);
      } else {
        // 開始時間と終了時間が有効なDateオブジェクトであることを確認
        if (!(startTime instanceof Date) || !(endTime instanceof Date)) {
          throw new Error("開始時間または終了時間が有効な日時形式ではありません。");
        }
        
        // 時間指定イベントとして作成
        const startDate = new Date(day);
        startDate.setHours(startTime.getHours());
        startDate.setMinutes(startTime.getMinutes());
        
        const endDate = new Date(day);
        endDate.setHours(endTime.getHours());
        endDate.setMinutes(endTime.getMinutes());
        
        Logger.log(`時間指定イベント作成: タイトル=${title}, 開始=${startDate}, 終了=${endDate}`);
        event = calendar.createEvent(title, startDate, endDate, options);
      }
      
      // 無事に予定が作成されたら「済」にする
      sheet.getRange(topRow + i, STATUS_COL + 1).setValue("済");
      
    } catch (e) {
      Logger.log(`Row ${topRow + i} の予定作成中にエラーが発生しました: ${e}`);
      ui.alert(`Row ${topRow + i} の予定作成中にエラーが発生しました:\n${e.message}`);
    }
  }
  
  // 完了通知
  ui.alert("予定の追加が完了しました。");
}

/**
 * 予定を削除する関数
 */
function deleteSchedule() {
  
  // 初期設定
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = spreadsheet.getSheetByName('カレンダー');
  
  // 連携するカレンダーIDを取得
  const gAccount = sheet.getRange("B7").getValue();  // セル B7 にカレンダーIDを入力
  Logger.log("gAccount=" + gAccount);
  
  // カレンダーを登録(必要に応じて一度だけ実行)
  addCalendar(gAccount);
  
  // 読み取り範囲(表の始まり行と終わり列)
  const topRow = 10;  // データの開始行
  const lastCol = 7;  // データの終了列(G列)
  
  // 列インデックス(0ベース)
  const STATUS_COL = 1;         // B列
  const DAY_COL = 2;            // C列
  const START_TIME_COL = 4;     // E列
  const END_TIME_COL = 5;       // F列
  const TITLE_COL = 6;          // G列
  const EVENT_ID_COL = 9;       // J列(イベントIDを保存している列)
  
  // 予定の最終行を取得
  const lastRow = sheet.getLastRow();
  const numRows = lastRow - topRow + 1;
  if (numRows <= 0) {
    SpreadsheetApp.getUi().alert("データが存在しません。");
    return;
  }
  
  // 予定の一覧を取得
  const contents = sheet.getRange(topRow, 1, numRows, lastCol).getValues();
  
  // Googleカレンダーの取得
  const calendar = CalendarApp.getCalendarById(gAccount);
  if (!calendar) {
    Logger.log("カレンダーが取得できませんでした。カレンダーIDを確認してください。");
    SpreadsheetApp.getUi().alert("カレンダーが取得できませんでした。カレンダーIDを確認してください。");
    return;
  }
  
  // ユーザーインターフェースの取得
  const ui = SpreadsheetApp.getUi();
  
  // 予定を削除
  for (let i = 0; i < numRows; i++) {
    
    // 「済」または「空白」の場合は飛ばす
    const status = contents[i][STATUS_COL];
    const dayValue = contents[i][DAY_COL];
    if (
      status === "済" ||
      status === "済み" ||
      dayValue === ""
    ) {
      continue;
    }
    
    const day = new Date(dayValue);
    let startTime = contents[i][START_TIME_COL];
    let endTime = contents[i][END_TIME_COL];
    const title = contents[i][TITLE_COL];
    const eventId = contents[i][EVENT_ID_COL];
    
    // イベントIDが存在する場合はIDで削除
    if (eventId) {
      try {
        const event = calendar.getEventById(eventId);
        if (event) {
          event.deleteEvent();
          Logger.log(`Row ${topRow + i} のイベントを削除しました: タイトル=${title}, 日付=${day}`);
          
          // 無事に予定が削除されたら「済」にする
          sheet.getRange(topRow + i, STATUS_COL + 1).setValue("済");
          
          // イベントIDをクリア
          sheet.getRange(topRow + i, EVENT_ID_COL).setValue("");
        }
      } catch (e) {
        Logger.log(`Row ${topRow + i} のイベント削除中にエラーが発生しました: ${e}`);
        ui.alert(`Row ${topRow + i} のイベント削除中にエラーが発生しました:\n${e.message}`);
      }
    } else {
      // イベントIDが存在しない場合はタイトルと時間で削除
      startTime = timeFormatter(startTime);
      endTime = timeFormatter(endTime);
      
      try {
        // 指定日のイベントを取得
        const events = calendar.getEventsForDay(day);
        
        for (let j = 0; j < events.length; j++) {
          const event = events[j];
          const eventTitle = event.getTitle();
          const eventStartTime = timeFormatter(event.getStartTime());
          const eventEndTime = timeFormatter(event.getEndTime());
          
          // 終日イベントの判定
          const isAllDay = event.isAllDayEvent();
          const formattedStartTime = isAllDay ? '00:00' : eventStartTime;
          const formattedEndTime = isAllDay ? '00:00' : eventEndTime;
          
          // タイトルと時間が一致する場合に削除
          if (title === eventTitle &&
              ((formattedStartTime === '00:00' && formattedEndTime === '00:00') ||
               (formattedStartTime === startTime && formattedEndTime === endTime))) {
            event.deleteEvent();
            Logger.log(`Row ${topRow + i} のイベントを削除しました: タイトル=${title}, 日付=${day}`);
            
            // 無事に予定が削除されたら「済」にする
            sheet.getRange(topRow + i, STATUS_COL + 1).setValue("済");
          }
        }
      } catch (e) {
        Logger.log(`Row ${topRow + i} の予定削除中にエラーが発生しました: ${e}`);
        ui.alert(`Row ${topRow + i} の予定削除中にエラーが発生しました:\n${e.message}`);
      }
    }
  }
  
  // 完了通知
  ui.alert("予定の削除が完了しました。");
}

コピペし終わったら、「プロジェクトを保存」しておきます。

jsonファイルの設定

次に、Googleカレンダーへの実行権限を許可するためにjsonファイルの編集を行います。jsonファイルを編集するには、下図の左にある設定歯車ボタンをクリックして、「appsscript.json」マニフェスト ファイルをエディタで表示するにチェックを入れます。

そして、以下の文章をappsscript.jsonsに書き込みます。appsscript.jsonsファイルは、<>をクリックしたところにあります。

{
  "timeZone": "Asia/Tokyo",
  "dependencies": {
  },
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",

  "oauthScopes": [
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/forms",
    "https://www.googleapis.com/auth/calendar"
 ]
}

コピペが終わったら、保存をしておいてください。

実行用のボタンを設置する(追加・削除)

ボタンを作成する

まず実行用のボタンを作成します。メニューバーの「挿入」>「図形描画」をクリックします。

図形」「テキストボックス」を駆使して、「追加」「削除」の実行ボタンを作成します。作成が終わったら、「保存して終了」をクリックします。※ご自身でわかればいいので、どんなボタンでも構いません。

ボタンを設置する

保存して終了」をクリックすると、自動で表にボタンが設置されますので、クリックしやすい位置に場所を移動し、大きさも調整してください。

ボタンにマクロを追加する(追加用マクロ)

実行ボタンの右上に縦に「」マークがあるのがわかるかと思います。

」マークをクリックし、「スクリプトを割り当て」をクリックします。

スクリプトを割り当てる画面になりますので、「createSchedule」と入力して「確定」をクリックしてください。

これで追加用の実行ボタンの設置ができました。※一度マクロを登録した実行用ボタンを移動したい場合は、CTRLを押しながらボタンをクリックすることで移動可能になります。

ボタンにマクロを追加する(削除用マクロ)

実行ボタンの右上に縦に「」マークがあるのがわかるかと思います。

」マークをクリックし、「スクリプトを割り当て」をクリックします。

スクリプトを割り当てる画面になりますので、「deleteSchedule」と入力して「確定」をクリックしてください。

これで削除用の実行ボタンの設置ができました。※一度マクロを登録した実行用ボタンを移動したい場合は、CTRLを押しながらボタンをクリックすることで移動可能になります。

ワンクリックで予定を組み込んでみる(初回だけの手順)

適当でも実務的でも良いので予定を記入してから、どちらでも良いので実行ボタンをクリックしてください。すると次のような許可画面が出ると思いますので、「OK」をクリックします。

次にアカウントの選択画面になりますので、アカウントを選択します。

すると次のような警告が出ますが、このプログラムをGoogleに登録をしているわけではない為、警告が出るのは当たり前なので、ここは無視して「詳細」をクリックします。

下に表示されるリンクの「カレンダー追加・更新用マクロ(安全ではないページ)に移動」をクリックします。

作成したマクロ「Google Apps Script(GAS)」を登録するので「許可」をクリックします。

このような面倒な許可が必要なのは一度目だけです。二度目以降は面倒な手続きはないので安心してください。

ワンクリックで予定を追加してみる

適当にデータを入れて登録をしてみます。

実行ボタンの「追加」ボタンをクリックすると、以下のような画面が出て、Google カレンダーに予定が組み込まれます。

Google カレンダーを見てみると、予定が反映されていることが確認できました。

ワンクリックで予定を削除してみる

先程登録したデータを利用します。「連携」の部分に「」という文字が入っていると思います。「」という文字を消してから、「削除」の実行ボタンを押します。

実行ボタンの「削除」ボタンをクリックすると、以下のような画面が出て、Google カレンダーから予定が削除されます。

Google カレンダーを見てみると、予定が削除されていることが確認できました。

バグ対応・バージョンアップ

  • 2024/09/14 19:38修正 varをletとconstに変更。その他改善。
  • 2024/09/24 22:24修正 削除で日付指定のときに削除が上手くいかないのを修正。暫定版。
  • 2024/10/02 14:26修正 カレンダーを追加登録する関数を追加し、他のユーザー情報にアクセスできるよう修正。「特定のユーザーまたはグループと共有する(登録アカウントが異なる場合)」の項目をブログに追加。

最後に

いかがでしたでしょうか?
表に書いてある日程でしか追加と削除はできませんが、ひとつずつ追加・削除を行うよりは遥かに楽に作業ができるようになったのではないでしょうか?

ちなみに表の横の行は無限に登録することができますが、管理しやすい範囲での作成をオススメいたします。(動作が劇的に遅くなるということではありません)
便利ですので、使用していただけると幸いです。

謎爺
謎爺

以下、この記事を書くにあたり、参考にしたサイトじゃ。

参考サイト

GASでGoogleカレンダーの予定を変更・削除する方法!件名や開始日・終了日(時間)を修正 | AutoWorker〜Google Apps Script(GAS)とSikuliで始める業務改善入門
Googleカレンダーの予定をすべてGASで削除する方法 | iwb.jp
Google Apps ScriptからGoogleカレンダーを削除する方法 | Kokoroe
スプレッドシートからGoogleカレンダーに予定を登録する方法(GAS) | Googleスプレッドシート 完全攻略
スプレッドシートからGoogleカレンダーに簡単に予定を登録する方法(初心者向け) | Googleスプレッドシート 完全攻略
スプレッドシートからカレンダーに連携してスケジュールを一括登録する【Google Apps Script(GAS)】 | 大阪のシステム開発なら 株式会社ウィズテクノロジー
【GAS】スプレッドシートに記載した予定をGoogleカレンダーにまとめて反映させたい #GAS - Qiita
GAS実行ユーザーのメールアドレス(Googleアカウント)を取得する方法 | AutoWorker〜Google Apps Script(GAS)とSikuliで始める業務改善入門
【Google Apps Script入門】セルの取得・変更をする | UX MILK
【GAS入門】変数/定数を宣言するvar・const・letの違い | Log Stocker
Googleスプレッドシートのスクリプト実行方法とボタンの実行方法 – Google Apps Script(GAS)を用いたシステム開発を支援
GASの配列の要素数(長さ)を取得する方法(lengthプロパティ) | AutoWorker〜Google Apps Script(GAS)とSikuliで始める業務改善入門
[GAS]Googleフォームで入力した時刻をスプレッドシートから取得したら1899/12/30になってしまう話 | たいぼんの社内SE blog
GoogleAppsScriptでGoogleカレンダーの予定をチャットワークに通知するやつ作る【GAS初心者向け】 #Chatwork - Qiita
【GAS】UtilitiesクラスのformatDate()メソッドで日付オブジェクトを文字列に変換する | EGUWEB(エグウェブ)
GASでGoogleCalendarにあっさりアクセス
【コピペで使える】GASでカレンダーのIDを取得してみる | 初心者でもわかるGoogle Apps Script活用のススメ
GoogleAppsの全ユーザーのカレンダー取得について

コメント

  1. りり より:

    こちらのマクロを非常に重宝させていただいております。
    1点ご質問がございます。こちらのマクロを使用し予定を削除する際に、表に記載していない「終日予定」の方を優先して削除されてしまうのですが、対処方法はございますでしょうか。削除したい予定は「終日」ではなく「時間指定」しています。
    もしご覧いただけておりましたら、ご回答いただけますと幸いです。

    • 白川秋 白川秋 より:

      りり様
      ご指摘いただき、ありがとうございます。
      ご指摘の件ですが、
      //予定を作成
      event = calendar.getEventsForDay(
      new Date(day)
      );
      event[0].deleteEvent();
      というプログラムになっており、現在は終日分削除というざっくりした作りとなっております。
      これは第一弾から引き継がれており、私の不手際でもあります。
      時間が空いた際に、修正させていただきますので、それまでお待ちいただけると幸いです。

      よろしくお願いいたします。

    • 白川秋 白川秋 より:

      りり様
      バグ対応させていただきました。
      思ったよりも、工数がかかり、時間もかかりましたが、恐らくはこれで動くのではないかと思います。

      よろしくお願いいたします。

  2. たつや より:

    こちらのマクロを使用させていただいておりまして、非常に重宝させていただいております。

    私からも1点ご質問がございます。

    スプレッドシートを別アカウントに共有した際に、共有された側のアカウントで追加ボタンを押すとスクリプトは実行されるのですが、「済」の文字が追加されず、Googleカレンダーにも連携が行われません。

    ぜひ別アカウントから追加作業ができると助かるのですが、対処方法はございますでしょうか。

    恐れ入りますが、ご確認お願い致します。

    • 白川秋 白川秋 より:

      たつや様
      マクロをご使用いただき、誠にありがとうございます。
      取り急ぎご挨拶までとし、詳細の挙動については、後日調べてみたいと思います。

      貴重なご報告ありがとうございます。

  3. えり より:

    はじめまして。

    9/8からこちらのマクロを使用させていただいているのですが、本日10/1に改めて共有先のアカウントから追加作業をしようとしたら、スクリプトの実行はされるのですが、「済」の入力とGoogleカレンダーへの連携がされませんでした。

    バグ対応・バージョンアップの際になにか誤作動が働いたのかなと考えたのですが、いかがでしょうか?

    もしご覧いただけておりましたら、ご回答いただけますと幸いです。

    • 白川秋 白川秋 より:

      えり様
      マクロをご使用いただき、誠にありがとうございます。
      取り急ぎご挨拶までとし、詳細の挙動については、後日調べてみたいと思います。
      バグ対応・バージョンアップの影響というのは、十分考えられるかと思います。

      貴重なご報告ありがとうございます。

  4. 白川秋 白川秋 より:

    たつや様・えり様
    プログラムの修正と、ブログに追加をいたしました。
    これでおそらく改善されるのではないかと思います。

    よろしくお願いいたします。

  5. kk より:

    はじめまして

    このマクロを使用させていただいているのすが、重宝してしてます。ありがとうございます。
    そこでご質問なのですが、シートを変えずに複数のカレンダーIDを読み取って反映させることはできますでしょうか?
    お手数をおかけしますが、ご確認いただけますと助かります

    • 白川秋 白川秋 より:

      kk様
      マクロをご使用いただき、誠にありがとうございます。
      さて、早速ご質問の件ですが、1枚のシートで複数のカレンダーIDを読み取って反映できないかということでしょうか?
      カレンダーIDとはメールアドレスのことでよろしかったですか?

      それでしたら、既に作成したものがございます。

      【複数対応版】Googleスプレッドシートを使って複数名のGoogleカレンダーに予定をワンクリックで追加・削除する方法【第三弾】
      https://shirakawaroom.com/2024/08/23/post-work-4634/

      こちらをご覧ください。
      見た目の問題で、別シートにメールアドレスを書くような設定ではありますが、ご期待の動作はするはずです。

      よろしくお願いいたします。

  6. kk より:

    早速のご返信ありがとうございます!

    このような記事もあったのですね!助かります。
    ですが、そちらだとメールアドレスシートに登録した全てのGoogleカレンダーに追加・削除がされるようでして・・

    カレンダーシートにメールアドレスを記入する列等を設け、そのメールアドレス別に追加・削除できるようなことができれば大変助かるのですが、どうでしょうか・・・

    お手数をおかけしますが、ご確認いただけますと助かります。

    • 白川秋 白川秋 より:

      kk様
      お返事いただき誠にありがとうございます。
      kk様のご要望ですと、メールアドレス毎にプログラムの関数を作成しなくてはならなくなるような気がします。
      イメージとしては、メールアドレス毎に後ろに「追加」「削除」を追加する形となりますが、実行する関数は同じなので、個別での対応は難しいです。
      ボタンセルの位置も踏まえて処理を変更するというのは、私の知識では思いつきません。
      一般的な使い方というより、コアな使い方になりますので、別途スプレッドシート別に分けて使用したほうが良いように思います。

      力不足で申し訳ございません。このような回答でよろしいでしょうか?

タイトルとURLをコピーしました