0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GASで「複数枠・連続予約」対応の予約システムを爆速構築した話(Google Meet自動発行対応)

0
Last updated at Posted at 2026-02-26

はじめに

暇なので作りました。

技術スタック

GAS + HTML(Bootstrap) + Google Calendar API。

こだわりポイント

コード部分はすべてGeminiに作ってもらいました。

  • スプレッドシートで「稼働時間」「バッファ」「最大連続枠」を管理
  • JSでの複数スロット選択と、バックエンドでの1つのイベントへの集約

ハマったところ

Calendar.Events.patch でのGoogle Meet発行とAPI有効化の罠

罠:createEvent だけではMeetURLが作れない

GASの標準機能である CalendarApp.createEvent には、2026年現在も「Google Meetを発行する」というオプションがありません。
そのため、一度予定を作成した後に Google Calendar API (Advanced Service) を使って「会議データ」を後付けする必要があります。

APIの有効化が「2箇所」必要

ここが最大のハマりポイントでした。

  • GASエディタ上の設定: 左メニューの「サービス +」から「Google Calendar API」を追加

  • Google Cloud Console (旧APIコンソール): 以前はここも必須でしたが、現在はGASエディタ側だけで済むことが多いです。ただし、他人に配布して「コピー」させた場合、コピーした側のエディタで 再度「サービス追加」をしないと400エラーで落ちます

  • createEvent だけではMeetのURLが作れない
    GASの標準機能である CalendarApp.createEvent には、2026年現在も「Google Meetを発行する」というオプションがありません。
    そのため、一度予定を作成した後にGoogle Calendar API (Advanced Service)を使って「会議データ」を後付けする必要があります。

  • event.getId()の末尾問題
    event.getId()で取得できるIDには@google.comが付いていますが、Calendar.Events.patchに渡すIDにはこれを含めてはいけません。
    .split('@')[0]で削らないと、これもまた400 Bad Requestを食らうことになります。

実装コード

Meetを確実に発行するためのパッチ処理がこちらです。

calendar.html
// 1. まず普通に予定を作る
const event = calendar.createEvent("予約完了", startTime, endTime, options);

// 2. Meet発行用のリソースを定義
const resource = {
  conferenceData: { 
    createRequest: { 
      requestId: "req_" + Date.now(), 
      conferenceSolutionKey: { type: "hangoutsMeet" } 
    } 
  }
};

// 3. APIを叩いてMeetを紐付ける
// ※ここで Calendar API (Advanced Service) を追加していないと ReferenceError になります
Calendar.Events.patch(
  resource, 
  calendar.getId(), 
  event.getId().split('@')[0], // IDの末尾の「@google.com」を削るのがコツ
  { conferenceDataVersion: 1 }
);

new Date() の型変換とミリ秒計算のデバッグ

予約システムで「30分枠を2つ選んだら1時間にする」といった計算をする際、JavaScriptのDate型はそのまま足し算ができません。
ここでハマらないためのポイントをまとめました。

  • Date同士の計算は「数値」に直さないとズレる
    GAS(JavaScript)では、Dateオブジェクトに数値を足しても時間は増えません。一度1970/1/1からの経過ミリ秒に変換するのが最も安全です。
test.html
// 30分(slotDuration)を足したい場合
const durationMs = config.slotDuration * 60 * 1000; // 分をミリ秒に変換
const startTime = new Date("2026/02/26 10:00");

// ❌ これではダメ(文字列結合や予期せぬ挙動の原因)
// const endTime = startTime + durationMs; 

// ✅ これが正解(getTime()で数値にしてから足し、Dateに戻す)
const endTime = new Date(startTime.getTime() + durationMs);
  • GASの Utilities.formatDate とタイムゾーン
    計算した結果をスプレッドシートやフロントエンドに返す際、単に .toString()するとサーバーのタイムゾーンに引っ張られることがあります。
test.html
// 日本時間(JST)で確実にフォーマットする
const formattedTime = Utilities.formatDate(new Date(), "JST", "HH:mm");
  • スプレッドシートからの「時刻」読み込み
    スプレッドシートのセルからgetValues()で時刻を取得すると、GAS側では自動的にDateオブジェクトとして認識されます。
    しかし、たまに「文字列」として入ってくることもあるため、以下のようなガード句を挟みます。
test.html
const formatTime = (val) => {
  if (val instanceof Date) {
    // Date型ならフォーマット
    return Utilities.formatDate(val, "JST", "HH:mm");
  }
  // 文字列ならそのまま、または正規表現でチェック
  return String(val); 
};
  • ループ処理での「数値比較」
    「稼働開始時間から終了時間まで30分刻みでボタンを作る」といったループでは、Dateオブジェクトを直接比較するのではなく、数値(ミリ秒)で比較すると無限ループを防げます。
test.html
let currentPos = startLimit.getTime();
const endPos = endLimit.getTime();

while (currentPos + durationMs <= endPos) {
  // 処理...
  currentPos += durationMs; // ミリ秒単位で加算していく
}

コード

こちらからどうぞ
https://github.com/kuranku817/personal-development/tree/main/%E8%87%AA%E5%8B%95%E4%BA%88%E7%B4%84Web%E3%82%A2%E3%83%97%E3%83%AA

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?