- 技術 💻
GASでGoogleカレンダーに予定を登録・取得|スプレッドシート一括登録
GAS(Google Apps Script)でGoogleカレンダーに予定を登録するには、CalendarApp でカレンダーを取り出し、createEvent(タイトル, 開始日時, 終了日時) を呼びます。 取得は getEvents(開始, 終了)、変更は setTime や setTitle、削除は deleteEvent と、予定に対する操作はひととおりメソッドで用意されています。
この記事では、まず主要メソッドを早見表にまとめ、続いてスプレッドシートの一覧をカレンダーへ一括登録するコードを載せます。登録したイベントのIDをシートに書き戻すので、同じ行を二重に登録せず、シートを直せば予定も変わり、チェックを付ければ予定が消えるつくりです。
確認環境:メソッド名・引数・戻り値・上限値は Google Apps Script の公式リファレンスと公式サンプル(2026年10月5日に閲覧)で確認。掲載コードは、GAS のサービスを置き換えた簡易スタブを使い Node.js 22 で動かして分岐を確かめ、型定義 @types/google-apps-script 2.0.13 による型チェック(TypeScript 7.0.2)を通しています。「スタブでの実行ログ」と書いた出力は、このスタブで動かしたときのものです。
CalendarAppの主要メソッド早見表
cal は CalendarApp.getDefaultCalendar() などで取り出したカレンダー、event は取り出した1件の予定です。
| やりたいこと | 書き方 | 引数 | 戻り値 |
|---|---|---|---|
| 自分のカレンダーを取り出す | CalendarApp.getDefaultCalendar() |
なし | Calendar |
| IDを指定してカレンダーを取り出す | CalendarApp.getCalendarById(id) |
カレンダーID(文字列) | Calendar |
| 時刻つきの予定を登録する | cal.createEvent(title, startTime, endTime) |
文字列, Date, Date | CalendarEvent |
| 説明・場所・ゲストを付けて登録する | cal.createEvent(title, startTime, endTime, options) |
上に加えて options(オブジェクト) | CalendarEvent |
| 終日の予定を登録する(1日) | cal.createAllDayEvent(title, date) |
文字列, Date | CalendarEvent |
| 終日の予定を登録する(複数日) | cal.createAllDayEvent(title, startDate, endDate) |
文字列, Date, Date(終了日は最終日の翌日) | CalendarEvent |
| 繰り返しの予定を登録する | cal.createEventSeries(title, startTime, endTime, recurrence) |
文字列, Date, Date, EventRecurrence | CalendarEventSeries |
| 期間内の予定を取得する | cal.getEvents(startTime, endTime) |
Date, Date | CalendarEvent の配列 |
| 条件を付けて取得する | cal.getEvents(startTime, endTime, options) |
上に加えて options(search など) | CalendarEvent の配列 |
| 1日分の予定を取得する | cal.getEventsForDay(date) |
Date | CalendarEvent の配列 |
| IDで1件取り出す | cal.getEventById(iCalId) |
イベントID(文字列) | CalendarEvent(見つからないときは null) |
| イベントIDを調べる | event.getId() |
なし | 文字列(iCalUID) |
| 日時を変える | event.setTime(startTime, endTime) |
Date, Date | CalendarEvent |
| 終日の予定に変える | event.setAllDayDate(date) |
Date | CalendarEvent |
| タイトル・説明・場所を変える | event.setTitle(t) / event.setDescription(d) / event.setLocation(l) |
文字列 | CalendarEvent |
| 予定を削除する | event.deleteEvent() |
なし | なし |
同じメソッドは CalendarApp にもあり、CalendarApp.createEvent(...) と書くと自分の既定のカレンダーが対象になります。set で始まるメソッドは CalendarEvent を返すので、event.setTitle('A').setLocation('B') のようにつなげて書けます。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「GASでGoogleカレンダーに予定を登録・取得|スプレッドシート一括登録」
準備:スクリプトを開き、カレンダーを取り出す
スプレッドシートのメニューから「拡張機能」→「Apps Script」を開くと、そのシートに結び付いたスクリプトを書けます。初回の実行時には、カレンダーへのアクセスを許可する画面が表示されます。
カレンダーの取り出し方は2通りです。
| 対象 | 書き方 | 向いている場面 |
|---|---|---|
| 自分の既定のカレンダー | CalendarApp.getDefaultCalendar() |
まず動かしてみる、自分用の予定 |
| 共有カレンダーなど特定のもの | CalendarApp.getCalendarById(id) |
チームのカレンダーに登録する |
カレンダーIDは、Googleカレンダーで対象のカレンダーの設定画面を開くと確認できます。IDはコードに直接書かず、スクリプト プロパティ(エディタの「プロジェクトの設定」→「スクリプト プロパティ」)に CALENDAR_ID という名前で保存しておくと、カレンダーを切り替えるときにコードを触らずに済みます。
予定を登録する
時刻つきの予定を1件登録する
// 1件登録する(時刻あり)
function createOne() {
const cal = CalendarApp.getDefaultCalendar();
const start = new Date(2026, 9, 20, 10, 0); // 月は0始まり。9 は10月
const end = new Date(2026, 9, 20, 11, 0);
const event = cal.createEvent('定例ミーティング', start, end);
console.log(event.getId());
}
new Date(年, 月, 日, 時, 分) の月は 0 から数えるので、10月は 9 と書きます。ここがずれると1か月先の予定になるため、最初の1件はカレンダーの画面で日付を確かめておくと安心です。
戻り値の CalendarEvent から getId() でイベントIDを取り出せます。後から変更・削除したい予定は、このIDを控えておきます。
説明・場所・ゲストを付ける(options)
4つ目の引数にオブジェクトを渡すと、説明や場所をまとめて指定できます。
| キー | 型 | 内容 |
|---|---|---|
| description | 文字列 | 予定の説明。改行は \n |
| location | 文字列 | 場所 |
| guests | 文字列 | ゲストのメールアドレス。複数はカンマ区切りで1つの文字列にする |
| sendInvites | 真偽値 | true にするとゲストへ招待メールを送る |
// 説明・場所・ゲストを付けて登録する
function createWithOptions() {
const cal = CalendarApp.getDefaultCalendar();
const event = cal.createEvent(
'備品予約システム 仕様確認',
new Date(2026, 9, 21, 14, 0),
new Date(2026, 9, 21, 15, 0),
{
description: '議題:予約画面の項目\n資料は前日までに共有します。',
location: '会議室A',
guests: 'sato@example.com,suzuki@example.com',
sendInvites: true,
}
);
console.log(event.getTitle() + ' / ' + event.getLocation());
}
guests は配列ではなくカンマ区切りの文字列です。配列で持っているときは list.join(',') でつなげてから渡します。sendInvites を true にすると実際にメールが届くので、試すときは自分のアドレスだけにしておくと落ち着いて確認できます。
終日の予定を登録する
// 終日の予定(1日・複数日)
function createAllDay() {
const cal = CalendarApp.getDefaultCalendar();
cal.createAllDayEvent('棚卸し', new Date(2026, 9, 22));
// 10/26〜10/28 の3日間。終了日は「最終日の翌日」を渡す
cal.createAllDayEvent('研修', new Date(2026, 9, 26), new Date(2026, 9, 29));
}
複数日の終日予定では、終了日は予定に含まれません。10月28日まで表示したいときは 10月29日を渡します。終日予定にも options(description・location・guests・sendInvites)を最後の引数として渡せます。
繰り返しの予定を登録する
繰り返しのルールは CalendarApp.newRecurrence() で組み立てて、createEventSeries に渡します。
// 繰り返しの予定(毎週火曜を10回)
function createWeekly() {
const cal = CalendarApp.getDefaultCalendar();
const recurrence = CalendarApp.newRecurrence()
.addWeeklyRule()
.onlyOnWeekday(CalendarApp.Weekday.TUESDAY)
.times(10);
const series = cal.createEventSeries(
'週次ふりかえり',
new Date(2026, 9, 20, 16, 0),
new Date(2026, 9, 20, 16, 30),
recurrence
);
console.log(series.getId());
}
| ルールの始め方 | 意味 |
|---|---|
addDailyRule() |
毎日 |
addWeeklyRule() |
毎週 |
addMonthlyRule() |
毎月 |
addYearlyRule() |
毎年 |
| 続けて付ける条件 | 意味 |
|---|---|
times(n) |
n 回で終える |
until(date) |
指定した日まで続ける |
interval(n) |
n 回に1回(隔週なら 2) |
onlyOnWeekday(day) / onlyOnWeekdays(days) |
曜日を限定する |
onlyOnMonthDay(day) |
毎月の日にちを限定する |
戻り値は CalendarEventSeries で、まとめて消すときは series.deleteEventSeries() を使います。同じ予定を10件並べて登録する代わりに、1回の呼び出しで作れます。
予定を取得する
期間を指定して一覧を出す
// 期間を指定して取得し、一覧を出す
function listEvents() {
const cal = CalendarApp.getDefaultCalendar();
const from = new Date(2026, 9, 19);
const to = new Date(2026, 9, 26); // 10/25 の終わりまで
const events = cal.getEvents(from, to);
const tz = Session.getScriptTimeZone();
events.forEach(function (ev) {
const when = ev.isAllDayEvent()
? Utilities.formatDate(ev.getAllDayStartDate(), tz, 'MM/dd') + ' 終日'
: Utilities.formatDate(ev.getStartTime(), tz, 'MM/dd HH:mm') +
'-' + Utilities.formatDate(ev.getEndTime(), tz, 'HH:mm');
console.log(when + ' ' + ev.getTitle());
});
}
スタブでの実行ログ(ここまでの登録コードを順に動かしたあと):
10/20 10:00-11:00 定例ミーティング
10/20 16:00-16:30 週次ふりかえり
10/21 14:00-15:00 備品予約システム 仕様確認
10/22 終日 棚卸し
終了の日時は「その時刻より前」までが対象と考えて、10月25日までを取りたいときは 10月26日 0時を渡します。日時を文字にするときは Utilities.formatDate(日時, タイムゾーン, 書式) を使うと、表示のタイムゾーンをはっきり指定できます。
キーワードで絞る・1日分だけ取る
// キーワードで絞る・1日分だけ取る
function searchEvents() {
const cal = CalendarApp.getDefaultCalendar();
const hits = cal.getEvents(new Date(2026, 9, 1), new Date(2026, 10, 1), { search: '仕様確認' });
console.log('仕様確認を含む予定: ' + hits.length + '件');
const today = cal.getEventsForDay(new Date(2026, 9, 21));
console.log('10/21 の予定: ' + today.length + '件');
}
getEvents と getEventsForDay の options には次のキーを指定できます。
| キー | 内容 |
|---|---|
| search | この文字列を含む予定だけを返す |
| max | 返す件数の上限 |
| start | 何件目から返すか |
| author | 作成者のメールアドレスで絞る |
| statusFilters | ゲストとしての回答状況で絞る |
取得した予定からよく使う値
| 知りたいこと | メソッド | 戻り値 |
|---|---|---|
| タイトル | getTitle() |
文字列 |
| 開始・終了の日時 | getStartTime() / getEndTime() |
Date |
| 終日かどうか | isAllDayEvent() |
真偽値 |
| 終日予定の開始日・終了日 | getAllDayStartDate() / getAllDayEndDate() |
Date |
| 説明・場所 | getDescription() / getLocation() |
文字列 |
| ゲストの一覧 | getGuestList() |
EventGuest の配列 |
| 最終更新日時 | getLastUpdated() |
Date |
| 繰り返しの一部かどうか | isRecurringEvent() |
真偽値 |
| イベントID | getId() |
文字列 |
取得した予定をシートに書き出す
// 取得した予定をシートに書き出す
function exportEventsToSheet() {
const cal = CalendarApp.getDefaultCalendar();
const events = cal.getEvents(new Date(2026, 9, 1), new Date(2026, 10, 1));
const rows = events.map(function (ev) {
return [ev.getTitle(), ev.getStartTime(), ev.getEndTime(), ev.getLocation(), ev.getId()];
});
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('取得結果');
sheet.getRange(1, 1, 1, 5).setValues([['タイトル', '開始', '終了', '場所', 'イベントID']]);
if (rows.length > 0) {
sheet.getRange(2, 1, rows.length, 5).setValues(rows);
}
console.log(rows.length + '件を書き出しました');
}
行を配列にためてから setValues で一度に書くと、件数が増えても書き込みは1回で済みます。「取得結果」という名前のシートを先に作っておいてください。
予定を変更・削除する
イベントIDが分かっていれば、getEventById で1件を取り出して操作できます。
// IDで1件取り出して時間を変える・消す
function updateAndDelete(eventId) {
const cal = CalendarApp.getDefaultCalendar();
const event = cal.getEventById(eventId);
if (!event) {
console.log('予定が見つかりません: ' + eventId);
return;
}
event.setTime(new Date(2026, 9, 20, 13, 0), new Date(2026, 9, 20, 14, 0));
event.setTitle('定例ミーティング(時間変更)');
console.log(event.getTitle() + ' ' + event.getStartTime().getHours() + '時開始');
event.deleteEvent();
console.log('削除後に取得: ' + cal.getEventById(eventId));
}
スタブでの実行ログ:
定例ミーティング(時間変更) 13時開始
削除後に取得: null
getEventById は、そのカレンダーの中にIDが見つからないと null を返します。 取り出したあとに if (!event) で確かめてから操作すると、カレンダーの画面で先に消されていた場合でもスクリプトが止まりません。別のカレンダーに登録した予定は、登録したのと同じカレンダーから取り出します。
スプレッドシートから一括登録する(新規・更新・削除を判定)
ここからが本題です。シートの1行を1つの予定として扱い、実行するたびにシートの内容をカレンダーへ反映します。
シートの列構成
シート名を「予定」とし、1行目に見出しを置きます。次のひな形はタブ区切りなので、A1 セルを選んでそのまま貼り付けられます。
タイトル 日付 開始時刻 終了時刻 場所 説明 ゲスト 削除 イベントID 状態 同期キー
定例ミーティング 2026/10/20 10:00 11:00 会議室A 進み具合の共有
備品予約システム 仕様確認 2026/10/21 14:00 15:30 会議室B 予約画面の項目 sato@example.com
棚卸し 2026/10/22 倉庫
| 列 | 見出し | 入力する人 | 内容 |
|---|---|---|---|
| A | タイトル | 自分 | 予定の名前。空の行は読み飛ばす |
| B | 日付 | 自分 | 2026/10/20 のような日付 |
| C | 開始時刻 | 自分 | 10:00 の形。空にすると終日の予定 |
| D | 終了時刻 | 自分 | 11:00 の形。空にすると開始の1時間後 |
| E | 場所 | 自分 | 任意 |
| F | 説明 | 自分 | 任意 |
| G | ゲスト | 自分 | メールアドレス。複数はカンマ区切り |
| H | 削除 | 自分 | チェックボックス。オンにすると予定を消す |
| I | イベントID | スクリプト | 登録時に書き戻される。手で触らない |
| J | 状態 | スクリプト | 「新規済 10/05 21:23」やエラーの内容 |
| K | 同期キー | スクリプト | 行の内容をまとめた短い文字列。変更の判定に使う |
H列は範囲を選んで「挿入」→「チェックボックス」でチェックボックスにしておきます。I〜K列はスクリプトが書く列なので、列を非表示にしておくと誤って消す心配が減ります。
新規・更新・削除の判定ルール
| H列(削除) | I列(イベントID) | カレンダー側 | K列(同期キー) | 動き |
|---|---|---|---|---|
| オフ | 空 | — | — | 新規:登録してIDを書き戻す |
| オフ | あり | 予定がある | 前回と同じ | 変更なし:何もしない |
| オフ | あり | 予定がある | 前回と違う | 更新:タイトル・日時・場所・説明を上書き |
| オフ | あり | 予定が消えている | — | 新規:登録し直してIDを書き換える |
| オン | あり | 予定がある | — | 削除:予定を消してIDを空にする |
| オン | 空 | — | — | 変更なし:何もしない |
「追加済み」の印だけを付ける方法と比べると、IDを持っているので後からの変更と削除にも対応でき、同期キーのおかげで変わっていない行にはカレンダーへの書き込みをしません。
コード全体
// ===== 設定 =====
const SHEET_NAME = '予定';
const MAX_MS = 5 * 60 * 1000; // 5分で区切る(1回の実行は6分まで)
const WAIT_MS = 300; // カレンダーへ書き込むたびに待つ時間
const COL = { TITLE: 0, DATE: 1, START: 2, END: 3, LOCATION: 4, DESC: 5,
GUESTS: 6, DELETE: 7, ID: 8, STATUS: 9, KEY: 10 };
function syncSheetToCalendar() {
const started = Date.now();
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName(SHEET_NAME);
const lastRow = sheet.getLastRow();
if (lastRow < 2) return;
const range = sheet.getRange(2, 1, lastRow - 1, 11);
const values = range.getValues(); // 日付は Date で受け取る
const shown = range.getDisplayValues(); // 時刻は表示どおりの文字で受け取る
const tz = ss.getSpreadsheetTimeZone();
const cal = getCalendar_();
// 書き戻す3列(イベントID・状態・同期キー)。変更のない行は元の値のまま
const out = values.map(function (r) { return [r[COL.ID], r[COL.STATUS], r[COL.KEY]]; });
const count = { 新規: 0, 更新: 0, 削除: 0, 変更なし: 0, エラー: 0 };
try {
for (let i = 0; i < values.length; i++) {
if (Date.now() - started > MAX_MS) {
console.log((i + 2) + '行目で区切りました。もう一度実行すると続きから進みます');
break;
}
if (!values[i][COL.TITLE]) continue;
try {
const r = syncRow_(cal, values[i], shown[i], tz);
count[r.action]++;
if (r.action === '変更なし') continue;
out[i] = [r.id, r.action + '済 ' + stamp_(tz), r.key];
Utilities.sleep(WAIT_MS);
} catch (e) {
count.エラー++;
out[i][1] = 'エラー: ' + e.message;
console.log((i + 2) + '行目 ' + e.message);
}
}
} finally {
// 途中で止まっても、作成済みのイベントIDは必ずシートに残す
sheet.getRange(2, COL.ID + 1, out.length, 3).setValues(out);
}
console.log(JSON.stringify(count));
}
// 1行を見て「新規・更新・削除・変更なし」を決めて実行する
function syncRow_(cal, row, shownRow, tz) {
const id = String(row[COL.ID] || '');
const event = id ? cal.getEventById(id) : null;
// 削除:H列にチェックがある行
if (row[COL.DELETE] === true) {
if (!event) return { action: '変更なし' };
event.deleteEvent();
return { action: '削除', id: '', key: '' };
}
const when = toDates_(row[COL.DATE], shownRow[COL.START], shownRow[COL.END], tz);
const key = makeKey_([row[COL.TITLE], when.start.getTime(), when.allDay ? '' : when.end.getTime(),
row[COL.LOCATION], row[COL.DESC]]);
// 新規:IDが空、またはカレンダー側で消されていた行
if (!event) {
const options = { location: String(row[COL.LOCATION]), description: String(row[COL.DESC]) };
if (row[COL.GUESTS]) options.guests = String(row[COL.GUESTS]);
const created = when.allDay
? cal.createAllDayEvent(row[COL.TITLE], when.start, options)
: cal.createEvent(row[COL.TITLE], when.start, when.end, options);
return { action: '新規', id: created.getId(), key: key };
}
// 変更なし:前回書き戻した同期キーと同じ
if (key === row[COL.KEY]) return { action: '変更なし' };
// 更新:内容が変わった行
event.setTitle(row[COL.TITLE]);
if (when.allDay) {
event.setAllDayDate(when.start);
} else {
event.setTime(when.start, when.end);
}
event.setLocation(String(row[COL.LOCATION]));
event.setDescription(String(row[COL.DESC]));
return { action: '更新', id: id, key: key };
}
// 日付セル(Date)と時刻の文字(9:00)を1つの日時にまとめる
function toDates_(dateCell, startText, endText, tz) {
if (!(dateCell instanceof Date)) {
throw new Error('B列を日付の形式で入力してください');
}
const ymd = Utilities.formatDate(dateCell, tz, 'yyyy/M/d').split('/').map(Number);
if (!startText) {
return { allDay: true, start: new Date(ymd[0], ymd[1] - 1, ymd[2]) };
}
const s = parseTime_(startText);
const start = new Date(ymd[0], ymd[1] - 1, ymd[2], s.h, s.min);
let end = new Date(start.getTime() + 60 * 60 * 1000); // 終了が空なら1時間
if (endText) {
const e = parseTime_(endText);
end = new Date(ymd[0], ymd[1] - 1, ymd[2], e.h, e.min);
}
if (end.getTime() <= start.getTime()) {
throw new Error('終了時刻を開始時刻より後にしてください');
}
return { allDay: false, start: start, end: end };
}
function parseTime_(text) {
const m = String(text).trim().match(/^(\d{1,2}):(\d{2})/);
if (!m) throw new Error('時刻は 9:00 の形で入力してください(入力値: ' + text + ')');
return { h: Number(m[1]), min: Number(m[2]) };
}
// 行の内容を短い文字列にまとめる(変わったかどうかの判定用)
function makeKey_(parts) {
const bytes = Utilities.computeDigest(
Utilities.DigestAlgorithm.MD5, parts.join('|'), Utilities.Charset.UTF_8);
return Utilities.base64Encode(bytes);
}
function stamp_(tz) {
return Utilities.formatDate(new Date(), tz, 'MM/dd HH:mm');
}
// スクリプト プロパティ CALENDAR_ID があればそのカレンダー、無ければ自分のカレンダー
function getCalendar_() {
const id = PropertiesService.getScriptProperties().getProperty('CALENDAR_ID');
const cal = id ? CalendarApp.getCalendarById(id) : CalendarApp.getDefaultCalendar();
if (!cal) throw new Error('カレンダーが見つかりません: ' + id);
return cal;
}
実行する関数は syncSheetToCalendar です。名前の末尾に _ を付けた関数は、syncSheetToCalendar から呼ばれる補助用の関数です。
スタブで分岐を確かめた結果
上のひな形3行に「終了時刻が開始より前の行(勉強会 18:00〜17:00)」を1行足し、3回続けて動かしました。
== 1回目
5行目 終了時刻を開始時刻より後にしてください
{"新規":3,"更新":0,"削除":0,"変更なし":0,"エラー":1}
2行目 定例ミーティング | ID=ev001@google.com | 新規済 10/05 21:23
3行目 備品予約システム 仕様確認 | ID=ev002@google.com | 新規済 10/05 21:23
4行目 棚卸し | ID=ev003@google.com | 新規済 10/05 21:23
5行目 勉強会 | ID=(空) | エラー: 終了時刻を開始時刻より後にしてください
カレンダー: 定例ミーティング[10:00], 備品予約システム 仕様確認[14:00], 棚卸し[終日]
== 2回目(何も変えずに実行)
5行目 終了時刻を開始時刻より後にしてください
{"新規":0,"更新":0,"削除":0,"変更なし":3,"エラー":1}
…(省略:シートとカレンダーは1回目と同じ)…
== 3回目(変更・削除・修正のあと)
{"新規":2,"更新":1,"削除":1,"変更なし":0,"エラー":0}
2行目 定例ミーティング | ID=ev001@google.com | 更新済 10/05 21:23
3行目 備品予約システム 仕様確認 | ID=(空) | 削除済 10/05 21:23
4行目 棚卸し | ID=ev004@google.com | 新規済 10/05 21:23
5行目 勉強会 | ID=ev005@google.com | 新規済 10/05 21:23
カレンダー: 定例ミーティング[13:00], 棚卸し[終日], 勉強会[18:00]
3回目の前に行った操作と結果の対応は次のとおりです。イベントIDの ev001@google.com はスタブが振った仮の値で、実際のIDはもっと長い文字列になります。
| 行 | 3回目の前にしたこと | 結果 |
|---|---|---|
| 2 | 開始・終了を 13:00〜14:00 に書き換えた | 同じIDのまま日時が変わった(更新) |
| 3 | H列の削除にチェックを付けた | 予定が消え、IDが空になった(削除) |
| 4 | カレンダー側の予定を先に消した | 見つからないので登録し直し、IDが変わった(新規) |
| 5 | 終了時刻を 19:00 に直した | エラーが解消して登録された(新規) |
2回目の実行で新規が0件になっている点が、二重登録を防げていることの確認です。1行のエラーがほかの行を止めていないことも、1回目のログから分かります。
このコードが守っている5つのこと
- IDを必ず書き戻す:書き戻しを finally に置いているので、途中で例外が出ても、そこまでに作った予定のIDはシートに残ります。IDが残っていれば、次の実行で同じ行をもう一度登録することはありません。
- 1行ごとに try...catch:入力の誤りは J列にメッセージとして残し、次の行へ進みます。直して再実行すれば、その行だけが処理されます。
- 変わっていない行は触らない:同期キーが同じ行はカレンダーへ書き込まないので、1日の作成数の枠と実行時間を使いません。
- 5分で区切る:1回の実行時間の上限に届く前に自分で止まり、続きは次の実行に任せます。
- 書き込みのたびに少し待つ:
Utilities.sleep(300)で間隔を空け、短時間に呼び出しが集中しないようにしています。
ゲスト(G列)は新規登録のときだけ反映しています。登録後にゲストを増やしたい場合は、event.addGuest(メールアドレス) を更新の処理に足してください。
定期的に実行する
手で実行するほか、時間主導型のトリガーを付けると自動で反映できます。エディタ左側の「トリガー」→「トリガーを追加」から syncSheetToCalendar を選ぶか、次の関数を1回だけ実行します。
// 1時間おきに syncSheetToCalendar を実行するトリガーを作る(1回だけ実行する)
function createSyncTrigger() {
ScriptApp.newTrigger('syncSheetToCalendar').timeBased().everyHours(1).create();
}
トリガーは、作成した人のアカウントで動きます。
日付と時刻、タイムゾーンで迷いやすい点
シートの日付と時刻を1つの日時にまとめる
スプレッドシートの日付や時刻のセルは、getValues で読むと Date オブジェクトになります。日付と時刻が別の列にあるときは、次の順で1つにまとめると迷いません。
| 手順 | 使うもの | 理由 |
|---|---|---|
| 1. 日付セルから年・月・日を取り出す | Utilities.formatDate(日付, ss.getSpreadsheetTimeZone(), 'yyyy/M/d') |
シートに表示されているとおりの年月日が取れる |
| 2. 時刻は表示どおりの文字で読む | range.getDisplayValues() |
「10:00」をそのまま時と分に分けられる |
| 3. 5つの数から日時を作る | new Date(年, 月 - 1, 日, 時, 分) |
スクリプトのタイムゾーンでの日時になる |
上の toDates_ がこの手順をそのまま書いたものです。
スクリプトのタイムゾーンをそろえる
new Date(2026, 9, 20, 10, 0) は、スクリプトのタイムゾーンでの「10時」を表します。公式リファレンスでも、タイムゾーンを指定しない日時はスクリプトのタイムゾーンで解釈され、カレンダーのタイムゾーンとは異なる場合があると説明されています。
同じ1行をタイムゾーンだけ変えて Node.js 22 で実行すると、指している瞬間が変わることが分かります。
const d = new Date(2026, 9, 20, 10, 0);
console.log(process.env.TZ, '->', d.toISOString());
Asia/Tokyo -> 2026-10-20T01:00:00.000Z
America/New_York -> 2026-10-20T14:00:00.000Z
東京の10時のつもりで書いた予定が、スクリプトの設定がニューヨークだと13時間後の予定として登録されます。確認する場所は3つです。
| 確認する場所 | 見る場所・調べ方 |
|---|---|
| スクリプト | エディタの「プロジェクトの設定」のタイムゾーン(appsscript.json の timeZone)。コードでは Session.getScriptTimeZone() |
| スプレッドシート | 「ファイル」→「設定」のタイムゾーン。コードでは ss.getSpreadsheetTimeZone() |
| カレンダー | カレンダーの設定のタイムゾーン。コードでは cal.getTimeZone() |
3つを同じ地域(日本なら Asia/Tokyo)にそろえておけば、シートに書いた時刻がそのままカレンダーに表示されます。
予定が意図と違う日時になったときの見直し表
| 起きたこと | 見直すところ |
|---|---|
| 1か月先に登録された | new Date の月を 0 始まりで書いているか(10月は 9) |
| 9時間など一定の時間だけずれる | スクリプトのタイムゾーン |
| 複数日の終日予定が1日短い | 終了日に「最終日の翌日」を渡しているか |
| getEvents で最終日の予定が出てこない | 終了に「最終日の翌日 0時」を渡しているか |
| 「B列を日付の形式で入力してください」と出る | セルが文字列になっていないか(表示形式を日付にする) |
実行時間と1日の作成数の上限
Google の公式ページ「Quotas for Google Services」には、次の上限が載っています(2026年10月5日に閲覧)。
| 項目 | 個人のアカウント | Google Workspace |
|---|---|---|
| カレンダーの予定の作成数 | 5,000件/日 | 10,000件/日 |
| スクリプトの実行時間 | 6分/1回 | 6分/1回 |
| トリガーの合計実行時間 | 90分/日 | 6時間/日 |
| トリガーの数 | 20個/ユーザー・スクリプトごと | 20個/ユーザー・スクリプトごと |
| プロパティの読み書き | 50,000回/日 | 500,000回/日 |
上限はユーザーごとに数えられ、最初のリクエストから24時間後にリセットされます。上限を超えると例外が発生してスクリプトが止まります。
この数字から、一括登録の進め方を決められます。
| 行数の目安 | 進め方 |
|---|---|
| 数十〜数百行 | syncSheetToCalendar を1回実行すれば終わる |
| 1,000行を超える | 5分で区切られるので、終わるまで繰り返し実行する。トリガーを付けておけば自動で続きが進む |
| 1日の作成数の枠に近い | 日を分けて登録する |
Utilities.sleep と try...catch を入れる場所
| 入れる場所 | 書き方 | ねらい |
|---|---|---|
| カレンダーへ書き込んだ直後 | Utilities.sleep(300) |
呼び出しの間隔を空ける。指定はミリ秒 |
| 1行分の処理の外側 | try { ... } catch (e) { 状態列に e.message } |
1行の例外でほかの行を止めない |
| ループ全体の外側 | try { ... } finally { シートへ書き戻す } |
どこで止まってもIDを残す |
| ループの先頭 | if (Date.now() - started > MAX_MS) break; |
実行時間の上限の前に自分で区切る |
sleep で待っている時間も実行時間に含まれます。300ミリ秒を1,000回入れると、それだけで5分になる計算です。待つのは実際に書き込んだ行だけにして、変更なしの行では待たないようにしているのはこのためです。
よくある質問
Q. 同じ予定が二重に登録されてしまいます。どう防げますか?
A. 登録したときに event.getId() の値をシートに書き戻し、次回はIDがある行を新規登録の対象から外します。この記事のコードは書き戻しを finally に置いているので、途中で止まった場合でもIDが残ります。
Q. 自分以外のカレンダーにも登録できますか?
A. 編集の権限をもらっているカレンダーなら、CalendarApp.getCalendarById(カレンダーID) で取り出して同じメソッドを使えます。この記事の getCalendar_ のように、取り出した結果を確かめてから使うと、IDの入力違いにすぐ気づけます。
Q. シートの行を削除したら、カレンダーの予定も消えますか? A. 行そのものを消すとイベントIDも一緒に無くなるため、予定はカレンダーに残ります。予定も消したいときは、先に H列のチェックを付けて実行し、状態が「削除済」になってから行を消してください。
Q. イベントIDはカレンダーの画面のURLに出る値と同じですか?
A. getId() が返すのは iCalUID と呼ばれる形式のIDです。getEventById には、getId で取った値をそのまま渡してください。
まとめ
- 登録は createEvent(タイトル, 開始, 終了)。説明・場所・ゲストは4つ目の options で渡す
- 終日は createAllDayEvent、繰り返しは createEventSeries。複数日の終日予定は終了日に「最終日の翌日」を渡す
- 取得は getEvents(開始, 終了) と getEventsForDay(日付)。search でキーワードを絞れる
- 変更・削除は getEventById で取り出してから setTime・setTitle・deleteEvent。見つからないと null が返る
- 一括登録はイベントIDをシートに書き戻すと、新規・更新・削除を判定でき、二重登録を防げる
- 日時はスクリプトのタイムゾーンで解釈される。スクリプト・シート・カレンダーの3つをそろえる
- 予定の作成は個人のアカウントで1日5,000件、1回の実行は6分まで。5分で区切り、書き込んだ行だけ sleep で間を空ける
参考・出典
- Google Apps Script リファレンス「Class CalendarApp」「Class Calendar」「Class CalendarEvent」「Class CalendarEventSeries」「Class EventRecurrence」(developers.google.com/apps-script/reference/calendar)
- Google Apps Script「Quotas for Google Services」(developers.google.com/apps-script/guides/services/quotas)
- Google Apps Script リファレンス「Class Utilities」「Class Properties」、ガイド「Installable Triggers」「Properties Service」「Custom Functions in Google Sheets」(日付が Date になる説明)
- Google Apps Script サンプル「Create a sign-up for sessions at a conference」(options の sendInvites と getId の使用例)
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「GASでGoogleカレンダーに予定を登録・取得|スプレッドシート一括登録」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。