Google 日曆: 透過名字取得日曆 getCalendarsByName()

 


如果你的工作流程常跟 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 || {});
}


張貼留言 (0)
較新的 較舊