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分単位の空き時間を確認できます)

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

セルB4(空き時間の開始時刻)、セルB5(空き時間の終了時刻)は文字列形式でご入力ください。
数字のみやスプレッドシートの「時刻形式」のままでは正しく読み取れない場合があります。

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 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;
  if (startTimeStr) {
    const [sHour, sMinute] = startTimeStr.split(":").map(Number);
    startHour = sHour;
    startMinute = sMinute || 0;
  }
  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);

    // 1. すべてのカレンダーからイベントを取得
    let allEvents = [];
    calendarIds.forEach(calendarId => {
      const calendar = CalendarApp.getCalendarById(calendarId);
      if (!calendar) return;
      const events = calendar.getEvents(dayStart, dayEnd);
      events.forEach(event => {
        allEvents.push({
          start: event.getStartTime(),
          end: event.getEndTime()
        });
      });
    });

    // 2. 開始時間順にソート
    allEvents.sort((a, b) => a.start - b.start);

    // 3. イベントをマージして「ふさがっている時間」を作る
    let mergedEvents = [];
    allEvents.forEach(event => {
      if (!mergedEvents.length) {
        mergedEvents.push({ start: event.start, end: event.end });
      } else {
        let last = mergedEvents[mergedEvents.length - 1];
        if (event.start <= last.end) {
          last.end = new Date(Math.max(last.end.getTime(), event.end.getTime()));
        } else {
          mergedEvents.push({ start: event.start, end: event.end });
        }
      }
    });

    // 4. 空き時間を計算
    let freeTimes = [];
    let prevEnd = new Date(dayStart);

    mergedEvents.forEach(event => {
      if (event.start > prevEnd) {
        freeTimes.push({ start: new Date(prevEnd), end: new Date(event.start) });
      }
      prevEnd = new Date(Math.max(prevEnd.getTime(), event.end.getTime()));
    });

    if (prevEnd < dayEnd) {
      freeTimes.push({ start: new Date(prevEnd), end: new Date(dayEnd) });
    }

    // 5. 分割せず、そのまま出力
    freeTimes.forEach(time => {
      const startStr = Utilities.formatDate(time.start, Session.getScriptTimeZone(), "H:mm");
      const endStr = Utilities.formatDate(time.end, 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("共通の空き時間が1セルずつ出力されました。");
}
スクリプトの説明
シートと設定情報の取得
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());
  • スクリプトの最初で、設定用のシートが存在するかどうかを確認します。
  • 存在する場合は、B2・B3セルから検索対象の開始日・終了日を取得します。
カレンダー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が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");
}

let startHour = 0, startMinute = 0, endHour = 23, endMinute = 59;
if (startTimeStr) {
  const [sHour, sMinute] = startTimeStr.split(":").map(Number);
  startHour = sHour;
  startMinute = sMinute || 0;
}
if (endTimeStr) {
  const [eHour, eMinute] = endTimeStr.split(":").map(Number);
  endHour = eHour;
  endMinute = eMinute || 0;
}
  • B4・B5セルから1日の空き時間抽出対象となる開始時刻・終了時刻を取得します。
  • 値がDate型の場合は「HH:mm」形式の文字列に変換し、時・分に分けて設定します。
  • 未入力時は0:00〜23:59がデフォルトとなります。
日付ごとの空き時間計算
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);
  • 設定された開始日から終了日まで、1日ずつループ処理を行い、
  • 各日の「日付」と「曜日」を取得・フォーマットします。
  • また、その日の空き時間を抽出する時間帯(dayStart〜dayEnd)を設定します。
カレンダーイベントの取得と空き時間の計算
  // 1. すべてのカレンダーからイベントを取得
  let allEvents = [];
  calendarIds.forEach(calendarId => {
    const calendar = CalendarApp.getCalendarById(calendarId);
    if (!calendar) return;
    const events = calendar.getEvents(dayStart, dayEnd);
    events.forEach(event => {
      allEvents.push({
        start: event.getStartTime(),
        end: event.getEndTime()
      });
    });
  });

  // 2. 開始時間順にソート
  allEvents.sort((a, b) => a.start - b.start);

  // 3. イベントをマージして「ふさがっている時間」を作る
  let mergedEvents = [];
  allEvents.forEach(event => {
    if (!mergedEvents.length) {
      mergedEvents.push({ start: event.start, end: event.end });
    } else {
      let last = mergedEvents[mergedEvents.length - 1];
      if (event.start <= last.end) {
        last.end = new Date(Math.max(last.end.getTime(), event.end.getTime()));
      } else {
        mergedEvents.push({ start: event.start, end: event.end });
      }
    }
  });

  // 4. 空き時間を計算
  let freeTimes = [];
  let prevEnd = new Date(dayStart);

  mergedEvents.forEach(event => {
    if (event.start > prevEnd) {
      freeTimes.push({ start: new Date(prevEnd), end: new Date(event.start) });
    }
    prevEnd = new Date(Math.max(prevEnd.getTime(), event.end.getTime()));
  });

  if (prevEnd < dayEnd) {
    freeTimes.push({ start: new Date(prevEnd), end: new Date(dayEnd) });
  }

  // 5. 分割せず、そのまま出力
  freeTimes.forEach(time => {
    const startStr = Utilities.formatDate(time.start, Session.getScriptTimeZone(), "H:mm");
    const endStr = Utilities.formatDate(time.end, Session.getScriptTimeZone(), "H:mm");
    freeTimeSlots.push([formattedDate, `${startStr} - ${endStr}`]);
  });
}
  • すべてのカレンダーから対象日のイベントを取得し、開始時刻順に並べます。
  • 重複・連続しているイベントをマージし、「ふさがっている(予定が入っている)時間帯」をまとめます。
  • ふさがっていない隙間時間=全員が共通で空いている時間をリスト化し、そのまま「HH:mm-HH:mm」形式でまとめて出力します。
共通の空き時間をスプレッドシートに出力
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");
  • 取得した空き時間リストをA9セル以降に書き込みます。
  • 空き時間が1つもなければ「空き時間が見つかりませんでした」と表示します。
  • また、日付・空き時間のヘッダー行に色付け&中央揃えを適用して、見やすく整えます。
STEP
スクリプトを保存

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

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

STEP
スクリプトを実行

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

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

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

許可の詳細手順

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

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

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

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

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

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

注意点

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

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

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

時間範囲の設定

空き時間の抽出対象とする時間帯(例:10:00〜18:00)を入力するB4・B5セルは、「文字列形式(10:00 など)」で入力してください。

Googleスプレッドシートで「時刻形式」で入力された場合、スクリプト内で意図しない値として処理され、正しい時間が取得できないことがあります。

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

カレンダーIDの確認

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

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

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

まとめ

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

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

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

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

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

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

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

お問い合わせはこちら

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

コメント

コメントする

目次