如果你的工作流程常跟 Google 日曆打交道,getCalendarsByName() 是那種看起來小,但能幫你省下很多時間的工具。
想像一下:同事在表單只填「行銷排程」,腳本就能自己找到對的日曆、建立事件、加描述與地點——不用每次都去找 ID。現實世界當然沒那麼單純:同名日曆可能有好幾個、權限不對就抓不到、有人改了名稱你隔天就爆錯。
這篇會帶你把流程拆小塊:先以名稱取得候選、用擁有權與隱藏狀態過濾、最終存下 ID 做長期操作;同場加映錯誤處理模板、常見錯誤與排查步驟、以及幾個實用的「穩定化」技巧(像是 Properties 快取與候選清單寫回試算表),讓專案更耐用。希望本篇文章能幫助到需要的您。
目錄
{tocify} $title={目錄}
為什麼用 getCalendarsByName()?
在 Google Apps Script(GAS)裡,你會常常需要「用名字」把某個日曆抓出來,例如公司共用行事曆「行銷排程」、社團的「活動總表」、或公開的「Taiwan Holidays」。這時候就輪到 CalendarApp.getCalendarsByName(name) 出場了。
官方文件重點如下:
功能:取得使用者擁有或已訂閱、且名稱相符的所有日曆(回傳陣列)。
名稱不分大小寫。
回傳型別:Calendar[]。
小提醒:Calendar 服務能存取你擁有或訂閱的日曆(包含額外日曆)。
什麼情境適合用?
你知道日曆名稱(例如「US Holidays」或「行銷排程」),但懶得翻設定找 ID。
要讓非技術同事在設定表單只填名稱,也能讓腳本找到對的日曆。
快速把多個同名日曆抓出來,再用其他條件(是否本人擁有、是否隱藏)過濾。可搭配 isOwnedByMe()、isHidden() 等屬性。
若你的使用情境需要唯一識別,建議最終還是轉成用 Calendar ID(不會重名),以免日後名稱被改動就掛點。ID 可在「設定與共用 → 整合行事曆」找到。
快速上手
1) 基本範例:印出找到幾個同名日曆
function demoListByName() {
const name = 'US Holidays';
const calendars = CalendarApp.getCalendarsByName(name);
Logger.log('找到 %s 個同名日曆', calendars.length);
}
此段與官方示例一致的用法:用名稱抓回陣列、計數。
2) 僅取第一個(常見但有風險)
function getFirstCalendarByName(name) {
const list = CalendarApp.getCalendarsByName(name);
return list.length ? list[0] : null;
}
風險:若有多個同名日曆,你可能抓到不是你想要的那個(例如你訂閱的公開日曆 vs 你自己擁有的團隊日曆)。
3) 比較保險:過濾「我擁有的」且「沒被隱藏」
function pickOwnedVisibleCalendar(name) {
const cals = CalendarApp.getCalendarsByName(name);
const candidates = cals.filter(c => c.isOwnedByMe() && !c.isHidden());
return candidates[0] || null;
}
說明:isOwnedByMe() 與 isHidden() 都是 Calendar 物件的方法,可幫你縮小範圍。
4) 一次取到 ID,之後固定用 ID 操作(最佳實務)
function resolveCalendarIdByName(name) {
const cal = pickOwnedVisibleCalendar(name);
if (!cal) throw new Error(`找不到符合條件的日曆:「${name}」`);
const id = cal.getId(); // 之後請都用這個 ID
return id;
}
ID 才是長久且唯一的識別;名稱可被人手動更改,風險高。ID 的取得與用途請參考官方/說明頁。
實作範例:從「表單設定」到「建立事件」
場景
你有一個 Google 試算表「設定」Sheet,A2 儲存格填了要操作的日曆名稱(例如「行銷排程」)。
你要把當週要發佈的內容,寫成事件丟進這個日曆。
程式碼(精簡版)
function createWeeklyMarketingEvent() {
const ss = SpreadsheetApp.getActive();
const sheet = ss.getSheetByName('設定');
const calName = sheet.getRange('A2').getValue().toString().trim();
// 1) 以名稱找日曆 -> 過濾「我擁有、未隱藏」-> 取 ID
const id = resolveCalendarIdByName(calName); // 前面章節已定義
// 2) 用 ID 取得 Calendar 物件(之後都用 ID,避免名稱變動影響)
const cal = CalendarApp.getCalendarById(id);
// 3) 建立一個下週一上午 10:00 的 1 小時事件
const now = new Date();
const nextMonday = getNextMondayAtHour(now, 10);
const end = new Date(nextMonday.getTime() + 60 * 60 * 1000);
cal.createEvent('【社群發佈】品牌月主題文', nextMonday, end, {
description: '同步 FB/IG/Threads,素材連結見 Notion',
location: 'Remote'
});
}
function getNextMondayAtHour(from, hour) {
const d = new Date(from);
const day = d.getDay(); // 0 日 1 一 ... 6 六
const add = (8 - day) % 7 || 7; // 下週一
d.setDate(d.getDate() + add);
d.setHours(hour, 0, 0, 0);
return d;
}
進階:多個同名日曆怎麼辦?
做法 A:讓使用者明確選擇(互動式選單)
function chooseCalendarByName(name) {
const cals = CalendarApp.getCalendarsByName(name);
if (!cals.length) throw new Error(`找不到名為「${name}」的日曆`);
// 以擁有權在前、訂閱在後,排出候選清單
const sorted = cals.sort((a, b) => Number(b.isOwnedByMe()) - Number(a.isOwnedByMe()));
// 組出簡單對話訊息(在 Apps Script 執行記錄看)
sorted.forEach((c, i) => {
Logger.log('#%s %s | owned=%s | hidden=%s | id=%s',
i + 1, c.getName(), c.isOwnedByMe(), c.isHidden(), c.getId());
});
// 真正專案可把這份清單寫回試算表,讓同事挑選 index
return sorted[0]; // 先回傳第一個(擁有權優先)
}
做法 B:建立一次性對應表(名稱 → ID),之後只用 ID
把上面列出的候選清單與其 getId() 回寫到試算表,讓管理者選定正確那一個;之後的腳本就改用 getCalendarById(selectedId) 操作。這樣就不怕名稱被改動。
常見錯誤與雷點
1. 以為會做「部分比對」(模糊 / 萬用字元)
症狀:傳入 getCalendarsByName('holiday') 卻完全抓不到「US Holidays」。
真相:getCalendarsByName() 是全名比對,不支援萬用字元或模糊搜尋。要模糊搜尋就得自己拿所有日曆迭代比對(或改用 ID)。
2. 找不到日曆,其實是「你沒有存取權」
說明:此方法只會回傳你擁有或訂閱的日曆。如果是同事的私人日曆、或你沒被分享,當然找不到。請確認分享權限是否開給你(或至少能看忙碌/空閒)。
3. 名稱變更導致腳本掛掉
現象:昨天還能找到,今天回傳空陣列。
原因:有人把日曆名稱改了。
解法:把名稱解析成 ID 之後固定用 getCalendarById(id);或在設定頁維護「名稱→ID」對照,避免名稱漂移。
4. 取得了事件 ID 卻又抓不到事件
場景:先用某個方法列出事件、儲存其 ID,之後用 getEventById() 讀不回來。
可能原因:事件 ID 與來源日曆/系列事件型態有關,或抓取方法不同步。遇到這類議題,建議統一以同一個日曆物件產生與讀取,並留意重複事件/系列事件的差異。
5. 事件被加到了「錯的日曆」
狀況:你本來想加到「行銷排程」,結果跑到「我的行事曆」。
排查:檢查你呼叫 createEvent() 的到底是哪個 Calendar 物件;以名稱找完後,是否確實轉成 ID、再由 getCalendarById(id) 取得正確對象。若混用 getDefaultCalendar() 就很容易誤投。
6. 訂閱的公開日曆與你擁有的同名
建議:先過濾 isOwnedByMe(),必要時把候選列給使用者決策,再把選定的那顆轉成 ID 存檔。
7. 安全與垃圾邀請
提醒:Google 日曆偶爾會出現釣魚邀請亂入的新聞與提醒。對外部來源的邀請要有基本警覺,企業或團隊應設定合宜的分享策略。
實務配方:把「名稱」導到「ID」,再快取起來
快取至 Properties(避免每次都掃)
function getCalendarIdFromConfig_(name) {
const props = PropertiesService.getScriptProperties();
const key = `CAL_ID__${name.toLowerCase()}`;
let id = props.getProperty(key);
if (id) return id;
const cal = pickOwnedVisibleCalendar(name); // 前文函式
if (!cal) throw new Error(`無法解析日曆:「${name}」`);
id = cal.getId();
props.setProperty(key, id);
return id;
}
function getCalendarByFriendlyName(name) {
const id = getCalendarIdFromConfig_(name);
return CalendarApp.getCalendarById(id);
}
效果:第一次解析名稱會比較慢;之後都直接用 ID,速度與穩定度都更好。
問題集
Q1:我想模糊搜尋,找出名字包含「Marketing」的所有日曆可以嗎?
A:getCalendarsByName() 不支援模糊或萬用字元。你可以先用「已知名稱」找,或自己維護一份名稱→ID 的白名單;要做模糊,就取得所有候選再用字串包含判斷(但 Apps Script 沒有「列出所有可用日曆」的直接 API,通常會改用 ID 策略)。
Q2:公開日曆跟我自己的日曆同名怎麼辦?
A:先用 isOwnedByMe() 過濾,或把候選清單列給使用者選,再固定用 ID。
Q3:找不到同事的日曆?
A:請對方到「設定與共用」把日曆分享給你(至少忙碌/空閒),或直接把 Calendar ID 給你用 getCalendarById()。
Q4:公司政策要控管誰能看到細節?
A:在日曆的「存取權限」關閉公開,必要時僅分享給特定人員或群組,並調整可見層級。
最佳實務
1. 名稱只當入口,不當主鍵:用名稱找到日曆後,立刻存下 ID,之後都用 getCalendarById(id)。
2. 多名同曆要決策:先 isOwnedByMe()、!isHidden() 過濾,再讓管理者選定並固化為 ID。
3. 權限先到位:抓不到往往是沒被分享。開啟適當的「設定與共用」。
4. 避免誤投:少用 getDefaultCalendar() 混著寫,統一鎖定同一顆目標日曆物件。
5. 安全意識:防釣魚邀請、控制公開可見性。
完整錯誤處理模板
function getCalendarOrThrowByName(name) {
if (!name || !String(name).trim()) {
throw new Error('日曆名稱不可為空');
}
const list = CalendarApp.getCalendarsByName(String(name).trim());
if (!list.length) {
throw new Error(`找不到日曆:「${name}」。請確認是否有訂閱/被分享,或名稱是否完全相符。`);
}
// 優先選「擁有且未隱藏」,否則退而求其次
const ownedVisible = list.filter(c => c.isOwnedByMe() && !c.isHidden());
const chosen = ownedVisible[0] || list[0];
return chosen;
}
function safeCreateEventByName(name, title, start, end, options) {
const cal = getCalendarOrThrowByName(name);
// 建議:轉成 ID,再重新取得(穩定)
const stable = CalendarApp.getCalendarById(cal.getId());
return stable.createEvent(title, start, end, options || {});
}
