Googleカレンダーの空き時間を抽出する方法|GASで複数人の共通スケジュールを管理

チームでスケジュールを調整する際、全員のカレンダーを開いて空き時間を探すのに手間がかかることありませんか?

Googleカレンダーには複数の予定を確認できる機能がありますが、各メンバーのスケジュールを並べて見ていると見づらくなることがあります。

そこで、Google Apps Script (GAS) を使って、各メンバーのGoogleカレンダーから共通の空き時間を抽出し、スプレッドシートで管理する方法をご紹介します。

この方法を使えば、打ち合わせや会議の日程調整がより簡単になります。

目次

完成イメージ

選択した期間内で共通の空き時間を一覧表示

指定した開始日から終了日までの間で、各日ごとの共通の空き時間を表示します。

各行には日付とその日の空き時間をリスト化し、複数人のスケジュールから重ならない時間帯を抽出します。

複数のGoogleカレンダーの予定から共通の空き時間を簡単に確認でき、打ち合わせや会議のスケジュール調整がスムーズに行えます。

調べたい空き時間の間隔(分)をB6セルで指定でき、画像の例では20分単位で空き時間を表示しています。

必要な準備

GASスクリプトを作成する前に以下2つの準備が必要です。

すでに設定済みの場合は、次のステップに進みます。

Googleカレンダーのアクセス権設定

アクセス権の設定方法

Googleカレンダーを開き、画面右上の設定をクリックします。

左側メニューの「マイカレンダーの設定」を確認し、使用したいカレンダーをクリックします。

カレンダーの詳細設定ページで、下にスクロールしていくと「特定のユーザーまたはグループと共有する」 が表示されます。

その項目内の「ユーザーやグループを追加」をクリックします。

権限レベルを「予定の表示(すべての予定の詳細)」に設定して共有します。

共有されたカレンダーは「他のカレンダー」に追加されます。

共有されているのに表示されていない場合は、以下をお試しください。

  • メールで届く共有通知リンクをクリックする
  • 「他のカレンダー」ー「 + 」を開くー「カレンダーに登録」ー「カレンダーを追加」-カレンダーIDを入力

カレンダーIDの確認

カレンダーIDの調べ方

Googleカレンダーを開き、画面右上の設定をクリックします。

左側メニューの「マイカレンダーの設定」を確認し、使用したいカレンダーをクリックします。

カレンダーの詳細設定ページで、下にスクロールしていくと「カレンダーID」 が表示されます。

通常、カレンダーIDは「xxx@gmail.com」形式や「xxxx@group.calendar.google.com」形式です。

手順

STEP
スプレッドシートを準備

スプレッドシートを開き、A列に項目名、B列に以下の内容を入力します。

入力内容
B1取得したいGoogleカレンダーのID
(複数の場合はB1から右方向に入力)
B2取得期間の開始日
B3取得期間の終了日
B4空き時間の開始時刻
B5空き時間の終了時刻
B6時間間隔(分)
(例えば「30」と入力すると30分単位の空き時間を確認できます)

シート名:今回の例ではシート名を「空き時間確認」と設定し、スクリプト実行時に参照します。

STEP
GASエディタを開く
画像に alt 属性が指定されていません。ファイル名: GAS.png

Googleスプレッドシートを開き、メニューの「拡張機能」から「Apps Script」をクリックしてGASエディタを開きます。

STEP
GASスクリプトの作成

function myFunction(){

}

が最初から入っているため、消去して以下のスクリプトを貼り付けます。

長いスクリプトですが、黒い画面の右上にコピーボタンがあります。

function findCommonFreeTime() {
  const sheetName = "空き時間確認"; // シート名を指定
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);

  if (!sheet) {
    Logger.log(`Error: シート「${sheetName}」が見つかりません。スプレッドシートにシートを作成してください。`);
    return;
  }

  const startDate = new Date(sheet.getRange("B2").getValue()); // 開始日付
  const endDate = new Date(sheet.getRange("B3").getValue());   // 終了日付
  const interval = parseInt(sheet.getRange("B6").getValue());  // B6セルの時間間隔(分)

  if (isNaN(interval) || interval <= 0) {
    Logger.log("Error: B6セルに有効な時間間隔(分)を入力してください。");
    return;
  }

  const calendarIds = sheet.getRange("B1:1").getValues()[0].filter(id => id);

  if (calendarIds.length === 0) {
    Logger.log("Error: カレンダーIDが指定されていません。B1から右方向にカレンダーIDを入力してください。");
    return;
  }

  // B4とB5セルの値を取得して、対象時間を設定
  let startTimeStr = sheet.getRange("B4").getValue();
  let endTimeStr = sheet.getRange("B5").getValue();

  // B4, B5 がDate型の場合、文字列に変換
  if (startTimeStr instanceof Date) {
    startTimeStr = Utilities.formatDate(startTimeStr, Session.getScriptTimeZone(), "H:mm");
  }
  if (endTimeStr instanceof Date) {
    endTimeStr = Utilities.formatDate(endTimeStr, Session.getScriptTimeZone(), "H:mm");
  }
  
  let startHour = 0, startMinute = 0, endHour = 23, endMinute = 59;

  // B4(開始時間)が入力されている場合にその値を使用
  if (startTimeStr) {
    const [sHour, sMinute] = startTimeStr.split(":").map(Number);
    startHour = sHour;
    startMinute = sMinute || 0;
  }

  // B5(終了時間)が入力されている場合にその値を使用
  if (endTimeStr) {
    const [eHour, eMinute] = endTimeStr.split(":").map(Number);
    endHour = eHour;
    endMinute = eMinute || 0;
  }

  const weekdays = ["日", "月", "火", "水", "木", "金", "土"]; // 日本語の曜日
  const freeTimeSlots = [];

  for (let day = new Date(startDate); day <= endDate; day.setDate(day.getDate() + 1)) {
    const dayString = Utilities.formatDate(new Date(day), Session.getScriptTimeZone(), "yyyy/MM/dd");
    const weekdayString = `(${weekdays[day.getDay()]})`;
    const formattedDate = `${dayString} ${weekdayString}`;
    
    // 1日の開始・終了時刻
    const dayStart = new Date(day);
    dayStart.setHours(startHour, startMinute, 0, 0);
    const dayEnd = new Date(day);
    dayEnd.setHours(endHour, endMinute, 59, 999);
    
    let freeTimes = [{ start: new Date(dayStart), end: new Date(dayEnd) }];
    
    calendarIds.forEach(calendarId => {
      const calendar = CalendarApp.getCalendarById(calendarId);
      if (!calendar) {
        Logger.log(`Error: 指定されたカレンダーID「${calendarId}」が見つかりません。`);
        return;
      }

      const events = calendar.getEvents(dayStart, dayEnd);
      events.forEach(event => {
        const eventStart = event.getStartTime();
        const eventEnd = event.getEndTime();
        
        freeTimes = freeTimes.flatMap(time => {
          if (eventEnd <= time.start || eventStart >= time.end) {
            return [time];
          } else if (eventStart > time.start && eventEnd < time.end) {
            return [
              { start: time.start, end: eventStart },
              { start: eventEnd, end: time.end }
            ];
          } else if (eventStart <= time.start && eventEnd < time.end) {
            return [{ start: eventEnd, end: time.end }];
          } else if (eventStart > time.start && eventEnd >= time.end) {
            return [{ start: time.start, end: eventStart }];
          } else {
            return [];
          }
        });
      });
    });

    let currentStart = null;
    let currentEnd = null;

    freeTimes.forEach(time => {
      let startTime = new Date(time.start);
      while (startTime < time.end) {
        let endTime = new Date(startTime);
        endTime.setMinutes(endTime.getMinutes() + interval);
        if (endTime > time.end) endTime = time.end;

        const freeTimeDuration = (endTime - startTime) / (1000 * 60);
        if (freeTimeDuration >= interval) {
          if (currentStart === null) {
            currentStart = startTime;
          }
          currentEnd = endTime;
        } else {
          if (currentStart !== null) {
            const startStr = Utilities.formatDate(currentStart, Session.getScriptTimeZone(), "H:mm");
            const endStr = Utilities.formatDate(currentEnd, Session.getScriptTimeZone(), "H:mm");
            freeTimeSlots.push([formattedDate, `${startStr} - ${endStr}`]);
            currentStart = null;
            currentEnd = null;
          }
        }
        
        startTime = endTime;
      }
    });

    if (currentStart !== null) {
      const startStr = Utilities.formatDate(currentStart, Session.getScriptTimeZone(), "H:mm");
      const endStr = Utilities.formatDate(currentEnd, Session.getScriptTimeZone(), "H:mm");
      freeTimeSlots.push([formattedDate, `${startStr} - ${endStr}`]);
    }
  }

  sheet.getRange("A9:B").clear();
  
  if (freeTimeSlots.length === 0) {
    sheet.getRange("A9:B9").setValues([["空き時間が見つかりませんでした", ""]]);
  } else {
    const outputRange = sheet.getRange(9, 1, freeTimeSlots.length, 2);
    outputRange.setValues(freeTimeSlots);
  }

  const headerRange = sheet.getRange("A8:B8");
  headerRange.setValues([["日付", "空き時間"]]);
  headerRange.setHorizontalAlignment("center");
  headerRange.setBackground("#e6f4ea");

  const timeColumnRange = sheet.getRange(9, 2, freeTimeSlots.length, 1);
  timeColumnRange.setHorizontalAlignment("right");

  Logger.log(`${interval}分単位の共通の空き時間が出力されました。`);
}
スクリプトの説明
シートと設定情報の取得
const sheetName = "空き時間確認";
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);

if (!sheet) {
  Logger.log(`Error: シート「${sheetName}」が見つかりません。スプレッドシートにシートを作成してください。`);
  return;
}

const startDate = new Date(sheet.getRange("B2").getValue());
const endDate = new Date(sheet.getRange("B3").getValue());
const interval = parseInt(sheet.getRange("B6").getValue());
  • スプレッドシートから設定情報を取得します。
  • シートの存在を確認し、開始日、終了日、空き時間の間隔などの設定をB1~B6セルから取得します。
  • sheetName:スプレッドシート内で対象となるシート名を指定します
  • startDate, endDate:検索期間の開始日と終了日を取得します
  • interval:空き時間の間隔(分)を指定し、スクリプトで使えるように数値として取得します
カレンダーIDの取得
const calendarIds = sheet.getRange("B1:1").getValues()[0].filter(id => id);

if (calendarIds.length === 0) {
  Logger.log("Error: カレンダーIDが指定されていません。B1から右方向にカレンダーIDを入力してください。");
  return;
}
  • B1セルから右方向に入力されている複数のカレンダーIDを取得します。
  • カレンダーIDが設定されていない場合、エラーメッセージを出力します。
  • calendarIds:複数のカレンダーIDを配列として取得します。
  • エラーチェック:カレンダーIDが1つも設定されていない場合に警告メッセージを表示します。
時間範囲の設定
let startTimeStr = sheet.getRange("B4").getValue();
let endTimeStr = sheet.getRange("B5").getValue();

if (startTimeStr instanceof Date) {
  startTimeStr = Utilities.formatDate(startTimeStr, Session.getScriptTimeZone(), "H:mm");
}
if (endTimeStr instanceof Date) {
  endTimeStr = Utilities.formatDate(endTimeStr, Session.getScriptTimeZone(), "H:mm");
}
  • B4とB5セルに入力されている開始時間と終了時間を取得し、スクリプト内で使用できる形式に変換します。
  • 時間が設定されていない場合は、デフォルトの時間(0:00~23:59)を使用します。
  • startTimeStr, endTimeStr:開始時間と終了時間の設定。
  • 時間のフォーマット変換:日付型の場合は文字列形式に変換して時間指定に対応。
日付ごとの空き時間計算
for (let day = new Date(startDate); day <= endDate; day.setDate(day.getDate() + 1)) {
  const dayString = Utilities.formatDate(new Date(day), Session.getScriptTimeZone(), "yyyy/MM/dd");
  const weekdayString = `(${weekdays[day.getDay()]})`;
  const formattedDate = `${dayString} ${weekdayString}`;
  • 設定された期間内の日付ごとにループ処理を行い、その日ごとの共通の空き時間を確認します。
  • 各日ごとの「日付」と「曜日」をフォーマットし、日付範囲の開始・終了時間を設定します。
  • dayString, weekdayString:日付と曜日を取得し、表示フォーマットに変換します。
  • formattedDate:日付と曜日を組み合わせて出力用の形式にします。
カレンダーイベントの取得と空き時間の計算
calendarIds.forEach(calendarId => {
  const calendar = CalendarApp.getCalendarById(calendarId);
  if (!calendar) {
    Logger.log(`Error: 指定されたカレンダーID「${calendarId}」が見つかりません。`);
    return;
  }

  const events = calendar.getEvents(dayStart, dayEnd);
  events.forEach(event => {
    const eventStart = event.getStartTime();
    const eventEnd = event.getEndTime();
    
    freeTimes = freeTimes.flatMap(time => {
      // 空き時間計算のロジック
    });
  });
});
  • 各カレンダーIDに対して、指定された日付と時間範囲内のイベントを取得し、空き時間を計算します。
  • 取得したイベントによって空き時間を区切り、共通の空き時間をリスト化します。
  • calendarIds.forEach:各カレンダーIDに対して空き時間を確認します。
  • イベントの取得:日付範囲内のイベントを取得し、空き時間リストを更新。
共通の空き時間をスプレッドシートに出力
sheet.getRange("A9:B").clear();
  
if (freeTimeSlots.length === 0) {
  sheet.getRange("A9:B9").setValues([["空き時間が見つかりませんでした", ""]]);
} else {
  const outputRange = sheet.getRange(9, 1, freeTimeSlots.length, 2);
  outputRange.setValues(freeTimeSlots);
}

const headerRange = sheet.getRange("A8:B8");
headerRange.setValues([["日付", "空き時間"]]);
headerRange.setHorizontalAlignment("center");
headerRange.setBackground("#e6f4ea");
  • 最終的に取得した共通の空き時間をスプレッドシートに書き出します。
  • A9セルから出力を開始します。
  • 空き時間がない場合は「空き時間が見つかりませんでした」と表示されます。
  • freeTimeSlots:最終的に取得した空き時間の一覧。
  • ヘッダーの装飾:日付と空き時間のヘッダー行に背景色とセンタリングを設定して見やすくします。
STEP
スクリプトを保存

スクリプトを書いたら、名前を付けて保存します。

(例:「カレンダー空き時間」)

STEP
スクリプトを実行

スクリプトを実行すると、各カレンダーのスケジュールから共通空き時間を探します。

初めてそのスクリプトを実行する場合は権限の確認が必要です。

そのため、『権限を確認』を押します。

許可の詳細手順

「詳細」をクリックします。

無題のプロジェクト(安全ではないページ)に移動」をクリックします。

許可」をクリックします。

STEP
スプレッドシートで結果を確認

スクリプトが正常に実行されると、「空き時間確認」シートの9行目以降に各日付の共通の空き時間が出力されます。

全員のスケジュールが重なっており、共通の空き時間が存在しない場合は「空き時間が見つかりませんでした」というメッセージが表示されます。

注意点

全日イベントに関する注意

カレンダー内で「終日」設定のスケジュール(例:自宅外出など終日の予定)は、空き時間抽出の際に他のスケジュールと重なると正常に空き時間が取得できなくなります。

これを防ぐには、終日のイベントを「時間指定」のイベントに変更するか、特定のカレンダーのみを空き時間抽出の対象とすることをおすすめします。

時間範囲の設定

B4セル(開始時間)とB5セル(終了時間)が空の場合は、24時間全体が対象範囲となります。

カレンダーIDの確認

カレンダーIDが正確でない場合、正しいデータを取得できないことがあります。

複数のカレンダーがある場合は、B1セルから横に並べてIDを入力します。

必要なすべてのIDが含まれているかご確認ください。

まとめ

GASを使ってGoogleカレンダーの共通の空き時間をスプレッドシートにまとめることで、複数メンバーのスケジュールを効率よく確認できるようになります。

打ち合わせや会議のスケジュール調整がスムーズに進み、時間の無駄を減らすことができます。

ただし、全日のスケジュールが入っている場合は共通の空き時間が取得できなくなるのでご注意ください。

弊社では、Google Apps Script(GAS)を活用した業務効率化のサポートを提供しております。

GASのカスタマイズやエラー対応にお困りの際は、ぜひお気軽にご相談ください。

また、ITツールの導入支援やIT導入補助金の申請サポートも行っております。

貴方の業務改善を全力でサポートいたします。

お問い合わせはこちら

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次