要用 Apps Script 跟 Google 日曆打交道,第一步永遠是「認出對象」。getId() 就是你辨識日曆與事件的通行證。
很多人以為拿到的東西都叫 ID,但日曆有日曆的 ID,事件又有 event.id 與 iCalUID,用途完全不同。搞不清楚,刪不掉事件、抓不到重複行程,是常見後果。這篇會先把名詞講清楚,再用圖解和程式碼示範:怎麼取得預設日曆與共用日曆的 ID、如何從事件身上拿到 iCalUID,以及在需要呼叫 Calendar API 時,怎麼把 iCalUID 對回 event.id。
最後附上常見錯誤與排雷表,幫你避開權限、時區、重複事件等坑,寫出穩定可維護的自動化腳本。希望本篇文章能幫助到需要的您。
目錄
{tocify} $title={目錄}
為什麼你需要懂 getId()
大部分與 Google 日曆整合的需求,最後都會回到兩個「識別碼」問題:
我到底要操作哪一個日曆?(primary、共用、資源日曆)
我到底要操作哪一個事件?(單一事件、重複事件中的某一次)
getId() 就是把這兩件事釐清的關鍵。更重要的是:日曆的 ID 與事件的 ID 完全是兩種動物,還牽涉到 iCalUID 這個常被混淆的值。先把名詞對齊,之後不會一直撞牆。
Calendar(日曆)物件的 getId():回傳該日曆的唯一 ID。預設日曆的 ID 通常就是使用者的信箱(例如 you@example.com)。
CalendarEvent(事件)物件的 getId():在 Apps Script 裡,這個 getId() 回傳的是事件的 iCalUID(不是 Google Calendar API 用的 event.id)。
Google Calendar REST API 裡,event.id 與 iCalUID 是兩個不同欄位:event.id 用在 events.get;若你只有 iCalUID,要用 events.list?iCalUID=... 去查。
重點提示:重複事件(Recurring)會有「多個 event.id」對應到「同一個 iCalUID」。這是官方文件明寫的差異,開發時務必先決定你要用哪一個識別方向。
基礎觀念:日曆 ID 與事件識別的生態系
1. 日曆(Calendar)的 ID 是什麼
預設(Primary)日曆:ID 通常等於使用者的 Email。
其他日曆(共用、群組、資源日曆):ID 多為一串字尾含 @group.calendar.google.com 的字串。你可以在日曆的 UI「設定」→「整合行事曆」裡看到它(本文著重 Apps Script 用法;UI 位置僅作補充)。
2. 事件(Event)的兩種識別:event.id vs iCalUID
event.id:Google Calendar API 內部使用的事件 ID,每一個實例(尤其重複事件的每次發生)都有不同的 event.id。查單筆事件用 events.get(calId, eventId)。
iCalUID:跨行事曆系統(RFC5545)的事件識別,同一組重複事件的所有實例共享同一個 iCalUID。若你只有 iCalUID,需要用 events.list?iCalUID=... 搜尋。
為什麼 Apps Script 的 CalendarEvent.getId() 回來是 iCalUID?這是 Apps Script 的設計。若你接著打 REST API 的 events.get,你會發現它要的是 event.id,不是 iCalUID,因此會「對不起來」。
最實用的 Apps Script 操作清單
下方程式碼皆為 Google Apps Script(JavaScript)語法,可直接在編輯器執行。
範例 A:取得「目前使用者」的預設日曆 ID
function getMyPrimaryCalendarId() {
const cal = CalendarApp.getDefaultCalendar();
const calendarId = cal.getId(); // 多半等於你的 Gmail 位址
Logger.log(calendarId);
}
說明:預設日曆的 ID 一般就是你的 Email。這點已由 Apps Script Calendar 類別文件說明。
範例 B:用已知 ID 取得日曆,並再讀回它的 ID(驗證)
function openCalendarByIdAndEcho() {
const targetId = 'your-team-calendar@group.calendar.google.com';
const cal = CalendarApp.getCalendarById(targetId);
if (!cal) {
throw new Error('找不到該日曆,請確認是否有存取權或 ID 是否正確。');
}
Logger.log(cal.getId()); // 應該等於 targetId
}
說明:getCalendarById() 可開啟使用者有權限的日曆;getId() 會回傳同一個 ID 以供你比對。
範例 C:取得某一天所有事件的 iCalUID(Apps Script 的 getId())
function listEventIcalUIDsForDay() {
const cal = CalendarApp.getDefaultCalendar();
const today = new Date();
const events = cal.getEventsForDay(today);
events.forEach(e => {
const iCalUID = e.getId(); // 注意:這是 iCalUID,不是 REST API 的 event.id
Logger.log(iCalUID);
});
}
說明:Apps Script 的 CalendarEvent.getId() = iCalUID。若你接下來要對 REST API 叫 events.get,會失敗,因為 events.get 期望的是 event.id。
範例 D:我只有 iCalUID,如何用 REST API 找到事件?
/**
* 需先在「服務」啟用「進階的 Google 服務」→ Calendar API v3,
* 並在專案的 Google Cloud Console 開啟 Calendar API。
*/
function findEventByICalUID_viaAdvancedService(iCalUID, calendarId) {
// 用 iCalUID 搜尋(events.list),而不是用 events.get
const res = Calendar.Events.list(calendarId, { iCalUID, maxResults: 10, singleEvents: true });
if (!res.items || res.items.length === 0) {
Logger.log('找不到對應事件');
return null;
}
// 這裡拿到的 items[*].id 就是 REST API 的 event.id
Logger.log(JSON.stringify(res.items.map(it => ({ id: it.id, summary: it.summary }))));
return res.items;
}
說明:官方文件明確指出:用 Google Calendar ID 取事件用 events.get;用 iCalUID 搜尋則要 events.list?iCalUID=...。
常見錯誤與雷點
1. 把 iCalUID 當作 event.id 用
現象:拿 Apps Script e.getId()(其實是 iCalUID)去叫 REST API 的 events.get(calId, eventId),結果 404。
解法:改走 events.list?iCalUID=...,或在能取得 event.id 的情境下改用 event.id。官方文件已清楚註明兩者不同且使用時機不同。
2. 重複事件辨識不清
現象:同一組重複事件有多個 event.id,導致你以為資料「重複」。
解法:若你的業務邏輯是「同一系列就是同一件事」,請用 iCalUID 做聚合;這正是 iCalUID 的設計目的。
3. 日曆 ID 用錯
現象:共用/群組/資源日曆用 Email 當 ID,呼叫就噴 null。
解法:去該日曆的設定頁找真正的日曆 ID;或用 getCalendarById() 驗證。官方定義:預設日曆才「常為 Email 位址」。
4. 權限不足或服務沒開
現象:Advanced Service 沒啟用,或 Cloud Console 沒開 Calendar API,Calendar.Events.list 直接報錯。
解法:到 Apps Script 專案的「服務」啟用 Calendar API v3,並在對應的 Cloud 專案開啟 API 後重新授權。
5. 事件由外部 iCal 匯入造成的識別差異
情境:從外部 iCal 匯入的事件,iCalUID 與你在本地新建事件的規則不同,導致你看到「同樣 API、不同型態 ID」。
解法:在流程設計上以「來源歸一化」為前提,將關鍵邏輯都落在 iCalUID 聚合與 event.id 精準定位上(社群也有人分享過遇到這種情況)。
6. 時區差異導致事件篩選錯位
現象:用 getEventsForDay() 之類 API 時,因 Script 專案時區與日曆時區不同,漏抓或多抓事件。
解法:統一專案時區;跨時區時改用明確的起訖時間篩選。
7. 以為 getId() 永遠可直接拿來刪改事件
提醒:Apps Script 的 CalendarEvent.getId() 是 iCalUID,不能直接拿去 events.delete(calId, id)。你要先把 iCalUID 換回 event.id(透過 events.list?iCalUID=... 查到目標後再操作)。
工作流程設計建議
策略 1:用日曆 ID 做「資料分區」
先以日曆 ID 建立你的資料邊界(個人、團隊、資源)。Primary 多半等於 Email,但不要硬寫死。以設定檔或資料表保存 ID。
策略 2:讀取事件時同時保存 iCalUID 與 event.id
如果你只用 Apps Script 內建物件處理,保留 iCalUID 即可。
如果你會混用 REST API,請同時保存 event.id,以便後續 CRUD(刪改)直達。官方語意差異已說明。
策略 3:重複事件報表以 iCalUID 聚合
例如「某系列會議的出席率」,請以 iCalUID 群組,避免單次實例把你資料灌爆。
進階範例:把 iCalUID 轉成 event.id,再進行精準操作
情境:我抓到的是 iCalUID,但我要刪除/更新單一實例
/**
* 以 iCalUID 查找該系列下的單一/多個實例,回傳 REST 事件 ID 清單
*/
function resolveEventIdsFromICalUID(iCalUID, calendarId) {
const res = Calendar.Events.list(calendarId, {
iCalUID,
singleEvents: true, // 展開重複事件成各個實例
maxResults: 2500,
orderBy: 'startTime'
});
return (res.items || []).map(it => it.id); // REST event.id
}
/**
* 更新該系列的第一個未來實例(示範:修改標題)
*/
function updateFirstUpcomingInstanceTitle(iCalUID, calendarId, newTitle) {
const ids = resolveEventIdsFromICalUID(iCalUID, calendarId);
if (ids.length === 0) return Logger.log('找不到事件實例');
const now = new Date();
for (const eventId of ids) {
const ev = Calendar.Events.get(calendarId, eventId); // 這裡用的是 REST event.id
const start = new Date(ev.start.dateTime || ev.start.date);
if (start >= now) {
ev.summary = newTitle;
Calendar.Events.patch(ev, calendarId, eventId);
Logger.log(`已更新:${eventId}`);
break;
}
}
}
重點:events.get/events.patch 都是吃 event.id;用 iCalUID 必須先 events.list 找回對應的 event.id。
除錯清單
1. 我拿到的是 Email,能當日曆 ID 嗎?
若是「預設日曆」通常可以;若是共用/群組/資源日曆,多半不行,請回設定頁找真正的 ID。
2. CalendarEvent.getId() 出來的值很像不是我想的那個?
你看到的是 iCalUID。要打 REST events.get 得先換成 event.id。
3. 重複事件抓到好多筆?
那是多個 event.id 的實例。要聚合請用 iCalUID。
問題集
Q1:有沒有辦法用 Apps Script 直接拿到 REST 的 event.id?
Apps Script 原生 CalendarEvent 不直接給 REST 的 event.id,而是 iCalUID。若你需 event.id,可透過「進階服務」用 events.list?iCalUID=... 查回。
Q2:我要跨系統(像 Outlook、Apple Calendar)同步,該用哪個識別最好?
跨系統同步最穩的是 iCalUID,它設計就是跨行事曆系統唯一識別。同一系列重複事件共用同一 iCalUID,適合做對帳與去重。
Q3:為什麼我在某些事件上看到的 iCalUID 規則跟我新建的事件不同?
外部匯入來源可能影響 iCalUID 的樣貌,社群也有人遇到這點。設計流程時請把 iCalUID 與 event.id 的對應當成一個可重建步驟,而不是假設格式固定。
Q4:如果只有 iCalUID 能不能刪事件?
可以,但要先把 iCalUID 找回該實例的 event.id(用 events.list),再呼叫 events.delete。
