Google 日曆: 取得已登錄日曆 getAllCalendars()

 


整理公司或個人行程時,你可能早就不只一個行事曆:團隊共享、外部訂閱、臨時專案… 全都堆在 Google 日曆裡。

與其逐一點開確認,不如一次把名單抓出來。這篇會用 Apps Script 的 CalendarApp.getAllCalendars(),把「我擁有或已訂閱」的行事曆通通列出,順便寫進試算表做索引。你會看到最小可行範例、常見參數、權限授權注意事項,也會示範怎麼標記主要行事曆、分辨是否有寫入權、找出被隱藏但其實很重要的日曆。

最後會整理常見雷點與錯誤排查,幫你少走彎路。讀完你就能把零散的行程資產,變成可搜尋、可維護、可擴充的資訊清單。希望本篇的文章可以幫助到需要的您。


目錄

{tocify} $title={目錄} 


為什麼需要 getAllCalendars()?

在自動化流程裡,我們常常不只操作主要行事曆,還會碰到團隊共用、訂閱的外部行事曆、或專案臨時建立的次要行事曆。

CalendarApp.getAllCalendars() 的用處就在於:一次拿到使用者帳號底下「擁有或已訂閱」的所有行事曆,再依名稱、ID、擁有權、是否隱藏等屬性做後續處理(同步事件、報表彙整、權限稽核、顏色規範等)。

依 Google 官方文件說明,這個方法會回傳一組 Calendar 物件陣列,涵蓋使用者擁有或已訂閱的行事曆。


核心觀念與前置條件

1.    需要的服務與權限(Scopes)

Apps Script 的 Calendar 服務可讀寫使用者行事曆,大多數方法(包含抓取所有行事曆)在第一次執行時會跳出授權,常見授權範圍包含 https://www.googleapis.com/auth/calendar 等。若沒有正確授權,存取將被拒絕。

2.    物件與常用屬性

CalendarApp.getAllCalendars() 回傳的是 Calendar[]。每個 Calendar 物件都能再呼叫屬性/方法,例如:

    getId():取得行事曆 ID(做 API 互動、長期紀錄必備)

    getName():顯示名稱

    isOwnedByMe():判斷是否本人擁有

    isMyPrimaryCalendar():是否為主要行事曆

    isHidden() / isSelected():是否隱藏/是否在介面顯示

上述能力皆可從 CalendarApp/Calendar 類別文件查到對應方法。

3.    與 Calendar REST API 的關係

若你之後要串接純 REST(例如用 UrlFetchApp 打 API),取得「使用者清單上的行事曆」則對應 CalendarList.list 端點;若操作事件清單,會用到 events.list,其中主要行事曆可用字串 "primary" 代表。這些是與 Apps Script 內建服務相輔相成的知識點。


快速上手:最小可行範例

目標:列出目前帳號底下所有行事曆(擁有+訂閱),輸出名稱、ID、是否主要、是否本人擁有、是否隱藏。

function listAllCalendars() {
  const cals = CalendarApp.getAllCalendars(); // 取得所有擁有或已訂閱的行事曆
  const rows = cals.map(cal => ({
    name: cal.getName(),
    id: cal.getId(),
    isPrimary: cal.isMyPrimaryCalendar(),
    isOwned: cal.isOwnedByMe(),
    isHidden: cal.isHidden()
  }));
  Logger.log(JSON.stringify(rows, null, 2));
}


說明重點

getAllCalendars() 會回傳整包行事曆,不保證順序。

之後多半會把 id 存起來(例如放到試算表或 Properties),供其他自動化流程使用。


逐步實作流程

步驟 1:建立 Apps Script 專案

開 Google 試算表(可有可無,但方便後續寫入),選單 擴充功能 → Apps Script。

新增檔案 Calendars.gs,貼上上方範例。

首次執行 listAllCalendars(),依提示完成授權(含 Calendar scope)。授權是 Calendar 服務的必備流程。


步驟 2:寫入試算表(便於檢索與 SEO 報表)

function dumpCalendarsToSheet() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sh = ss.getSheetByName('Calendars') || ss.insertSheet('Calendars');
  sh.clear();
  sh.getRange(1, 1, 1, 5).setValues([['Name', 'ID', 'Primary', 'OwnedByMe', 'Hidden']]);

  const cals = CalendarApp.getAllCalendars();
  const data = cals.map(cal => [
    cal.getName(),
    cal.getId(),
    cal.isMyPrimaryCalendar(),
    cal.isOwnedByMe(),
    cal.isHidden()
  ]);
  if (data.length) sh.getRange(2, 1, data.length, 5).setValues(data);
}


步驟 3:過濾條件範例(只取本人擁有的)

function listOwnedCalendarsOnly() {
  const owned = CalendarApp.getAllCalendars()
    .filter(cal => cal.isOwnedByMe());
  owned.forEach(cal => Logger.log(`${cal.getName()} | ${cal.getId()}`));
}


步驟 4:找出主要行事曆並標記

function markPrimaryCalendar() {
  const cals = CalendarApp.getAllCalendars();
  const primary = cals.find(cal => cal.isMyPrimaryCalendar());
  if (primary) {
    Logger.log(`Primary: ${primary.getName()} (${primary.getId()})`);
  }
}


isMyPrimaryCalendar() 可直接判斷主要行事曆,無須手動比對 ID。


步驟 5:延伸—抓各行事曆「今天」事件數

function countTodayEventsPerCalendar() {
  const start = new Date();
  start.setHours(0,0,0,0);
  const end = new Date();
  end.setHours(23,59,59,999);

  CalendarApp.getAllCalendars().forEach(cal => {
    const events = cal.getEvents(start, end);
    Logger.log(`${cal.getName()}: ${events.length} events today`);
  });
}


常見使用情境

情境 A:建立「行事曆索引表」供其他腳本引用

把 id、擁有權、是否顯示、顏色(可 getColor())、時區(getTimeZone())等欄位整理到試算表,其他腳本只需查這張表就能快速定位要操作的行事曆。

註:顏色、時區等屬性與設定方法皆可在 Calendar/CalendarApp 類別文件中查到。

情境 B:公司/學校網域的「共用資源日曆」管理

定期掃描所有行事曆,過濾出 isOwnedByMe() === false 但 isHidden() === true 的清單,提醒使用者把重要共用行事曆「顯示」出來,以免錯過。此類治理腳本可排程每日跑一次(時間驅動觸發器)。

情境 C:結合 REST API 做跨系統整合

若你需要把行事曆清單拋到其他系統,或要比對使用者 Calendar List(REST 的 CalendarList.list),可在 Apps Script 端以 UrlFetchApp.fetch() 打 API,用 getAllCalendars() 結果當本地基準做比對。


錯誤與例外處理(訊息、原因、解法)

下面彙整實務最常遇到的狀況,包含授權、ID、權限與資料品質。參考後面的「防呆程式碼」一起使用,能大幅降低部署後的維護成本。

1.    授權不足 / 第一次執行失敗

症狀:執行時跳出授權或直接被拒。

原因:缺乏 Calendar 相關 OAuth scope。

解法:在第一次執行時,依提示完成授權;若是新版專案,確認使用者以具有目標行事曆存取權的帳號授權。

2.    拿到空陣列或「似乎抓不到全部」

症狀:getAllCalendars() 回傳陣列異常少。

原因:帳號本身就只有主要行事曆;或你以錯誤帳號授權;或在前端環境誤用(例如期望在 HTML 端直接拿到完整物件)。

解法:先在單純的伺服端函式中呼叫並 Logger.log 檢查;確認目前授權帳號有訂閱其他行事曆;前端請用 google.script.run 呼叫伺服端函式回傳序列化後的資料(例如轉成物件陣列再回傳),避免直接把 Calendar 物件傳回造成不可序列化的空值。社群也曾討論過「陣列出現 null」的情況,多與傳遞方式或序列化有關。

3.    不是本人擁有的行事曆無法寫入

症狀:對某些行事曆 createEvent() 失敗或被拒。

原因:你只是訂閱者,沒有寫入權限。

解法:使用 isOwnedByMe() 檢查;或請擁有者授予「可變更事件」權限。

4.    主要行事曆判斷錯誤

症狀:用名稱或 ID 比對主要行事曆時失誤。

解法:直接用 isMyPrimaryCalendar() 判斷,不要猜名稱。

5.    ID 與 REST API 使用不一致

症狀:拿著 Apps Script 取得的 ID 去打 REST events.get、events.list 失敗。

解法:確認使用的端點正確,清單請看 CalendarList.list,主要行事曆可用 "primary"。

6.    隱藏行事曆導致以為「不見了」

症狀:在 Google Calendar 介面看不到,但 getAllCalendars() 有回傳。

解法:用 isHidden() 檢查;需要顯示可呼叫 setSelected(true) 或在 UI 勾選顯示。


防呆與最佳實務

1.    統一輸出「安全資料結構」

不要直接把 Calendar 物件丟給前端或寫入表格;先轉成純物件(POJO)後再輸出,避免序列化問題。

function getCalendarsAsObjects() {
  return CalendarApp.getAllCalendars().map(c => ({
    name: c.getName(),
    id: c.getId(),
    primary: c.isMyPrimaryCalendar(),
    owned: c.isOwnedByMe(),
    hidden: c.isHidden(),
    timeZone: c.getTimeZone()
  }));
}


2.    緩存與更新策略

行事曆清單不會天天變,但也不是完全固定。建議:

        首次全量抓取 → 寫入試算表

        之後以時間驅動觸發器(每日或每週)更新

        新增或刪除時以 ID 作為唯一鍵

3.    權限檢查在前

對欲寫入的行事曆,先以 isOwnedByMe() 或嘗試建立最小事件(或用 try/catch)確認權限,再執行批次操作,降低中途失敗。

4.    與 REST API 對齊

如需跨系統整合(例如後端微服務或報表平台):

        用 getAllCalendars() 建內部索引

        外部系統用 CalendarList.list 做一致性比對

        事件層則用 events.list/events.get 查詢與比對。

5.    時區與跨日處理

抓事件時,請用起訖時間界定當日範圍(見上例)。跨時區專案要以行事曆的 getTimeZone() 或專案時區統一處理。


進階範例:建立共用資料表,並標註可操作的目標行事曆

function buildCalendarIndex() {
  const ss = SpreadsheetApp.getActive();
  const sh = ss.getSheetByName('CalendarIndex') || ss.insertSheet('CalendarIndex');
  sh.clear();
  sh.appendRow(['Active', 'Name', 'ID', 'Owned', 'Primary', 'Hidden', 'TimeZone']);

  CalendarApp.getAllCalendars()
    .sort((a, b) => a.getName().localeCompare(b.getName()))
    .forEach(c => {
      // 預設把「本人擁有」且「非隱藏」的行事曆標記為可操作
      const active = c.isOwnedByMe() && !c.isHidden();
      sh.appendRow([
        active,
        c.getName(),
        c.getId(),
        c.isOwnedByMe(),
        c.isMyPrimaryCalendar(),
        c.isHidden(),
        c.getTimeZone()
      ]);
    });
}


有了這張索引表,其他腳本(例如大量建立事件、月份匯總報表、假期同步)都能快速引用,降低硬編碼的風險。


常見問題與雷點

1.    以名稱當唯一鍵

雷點:同名行事曆在不同人或不同來源很常見。

正解:一律以 getId() 當唯一識別。

2.    把 Calendar 物件直接傳到前端

雷點:HTML 前端常看見 null 或空資料。

正解:轉成 JSON 友善的純物件後再回傳(見上方防呆)。

3.    用猜測方式判斷主要行事曆

雷點:用名稱或排序判斷主要行事曆很容易失準。

正解:呼叫 isMyPrimaryCalendar()。

4.    期望訂閱行事曆可任意寫入

雷點:訂閱不等於擁有寫入權。

正解:isOwnedByMe() 先判斷;必要時調整分享權限。

5.    忽略隱藏狀態

雷點:UI 看不到不代表不存在,腳本仍會抓到。

正解:用 isHidden()/setSelected(true) 管理顯示。

6.    REST/Apps Script 混用時搞混端點與 ID

雷點:拿 Apps Script 的 ID 去打錯 REST 端點以致失敗。

正解:清單端點用 CalendarList.list,主要行事曆可用 "primary"。


問題集

Q1:getAllCalendars() 會包含我只讀的行事曆嗎?

會。它回傳「擁有或已訂閱」的所有行事曆;是否能寫入要看權限,可用 isOwnedByMe() 快速區分。

Q2:怎麼可靠地找出主要行事曆?

直接用 isMyPrimaryCalendar(),不要用名稱或排序猜。

Q3:我要打純 REST API,也能拿到清單嗎?

可以,用 CalendarList.list。主要行事曆在事件端點可用 "primary" 當 calendarId。

Q4:第一次跑就報權限錯誤?

Calendar 服務多數方法需要授權,按提示授權正確的帳號即可。


完整示範:整包抓取 → 清洗 → 寫表 → 輸出報表

function fullCalendarInventory() {
  const ss = SpreadsheetApp.getActive();
  const sh = ss.getSheetByName('CalInventory') || ss.insertSheet('CalInventory');
  sh.clear();

  const headers = [
    'Name','ID','Primary','Owned','Hidden','TimeZone',
    'TodayEvents'
  ];
  sh.getRange(1,1,1,headers.length).setValues([headers]);

  const start = new Date(); start.setHours(0,0,0,0);
  const end = new Date();   end.setHours(23,59,59,999);

  const data = CalendarApp.getAllCalendars().map(c => {
    const events = c.getEvents(start, end);
    return [
      c.getName(),
      c.getId(),
      c.isMyPrimaryCalendar(),
      c.isOwnedByMe(),
      c.isHidden(),
      c.getTimeZone(),
      events.length
    ];
  });

  if (data.length) sh.getRange(2,1,data.length,headers.length).setValues(data);
  Logger.log(`Indexed ${data.length} calendars.`);
}


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