Google 日曆:用 Apps Script 玩轉 google 日曆

 


如果你常在「會議太多、訊息太雜、提醒老是延後」之間打轉,先別怪意志力。

多半是流程沒自動化。Google Apps Script 裡的 Google Calendar 服務,讓你用幾行程式把日曆變得聰明:固定會議自動建立、寄邀請不用手點、加上描述與地點只要一個參數;表單送出就排活動、試算表一鍵批量生事件;需要暫時佔用或不佔用時段、調整可見與顏色,也有現成方法。

本文章盡可能說明內容,再逐步介紹 CalendarApp、Calendar、CalendarEvent、循環規則與提醒的寫法,附上實測程式碼與排雷清單。希望本文章能幫助到需要的您。


目錄

{tocify} $title={目錄} 


為什麼要用 GAS 操作 Google 日曆?


        你已經把生活和工作塞進 Google 日曆了:會議、提醒、休假、回報節奏、甚至工作地點。再加上表單、試算表、文件、Gmail——每天會重複做的其實就幾件事:新增事件、寄出邀請、改時間、改標題、改顏色、加提醒、拉報表。

        Google Apps Script 的 Calendar 服務(CalendarApp 等),讓你用少量 JavaScript 就能把這些瑣事自動化:定時掃日曆、批量建立會議、從試算表匯入行程、遇到關鍵字自動寄邀請、為整個部門產生週會、或是一鍵轉出「本月會議統計」。這些都可以靠內建的 Calendar 服務完成,無須額外伺服器或憑證。官方文件也明確說明,CalendarApp 能讀寫你的日曆、建立事件、設定重複規則與來賓等日常需求。


什麼是 Google Calendar 服務?(與 Advanced 版本的差異)

        在 Apps Script 裡,內建 Calendar 服務提供一組高階、好記的類別與方法(CalendarApp, Calendar, CalendarEvent, CalendarEventSeries…),用來操作你擁有或訂閱的日曆、單一事件與循環事件,還包含顏色、可見性、透明度、來賓狀態等列舉型別。

        另有一個Advanced Calendar Service(進階服務),它讓你在 Apps Script 內直接呼叫「Google Calendar API」的 REST 端點。大部分日常自動化用內建服務比較快,但如果你需要 API 才有的細節(例如某些欄位或更細的控制),就啟用 Advanced 版本(編輯器左側 Services 加上 Google Calendar API)。官方也指出:進階服務功能更廣,但一般情境內建服務更好上手。


一句話分工:

Calendar 服務(內建):快速、語意清楚、涵蓋 90% 日常自動化。

Advanced Calendar:要打 API 的特殊情境,再上。


可用的 Class 與列舉(Enums)一覽

官方參考頁把可用的類別與列舉整理得很清楚,以下是你在寫腳本時最常遇到的:

主要類別

    CalendarApp:

        進入點。抓預設日曆、依名稱/ID 取日曆、直接快速建立事件/循環事件/整天事件、建立新日曆等。

    Calendar:

        某一個具體日曆。可在這個日曆上查詢事件、建立事件、設定日曆名稱/時區/顏色等。

    CalendarEvent:

        單次事件。可讀寫標題、時間、地點、描述、顏色、可見性、來賓、提醒等。

    CalendarEventSeries:

        循環事件(週會、每月結算…),能設定/更新循環規則(EventRecurrence + RecurrenceRule)。

    EventGuest:來賓資訊(信箱、名稱、回覆狀態)。

    EventRecurrence / RecurrenceRule:

        建立「每週三」「每月最後一個工作天」等規則。

常用列舉(設定值更直覺)

    EventType:

        事件類型(一般、生日、Focus Time、Out of Office、工作地點…)。注意:內建服務能讀到事件類型,但直接「設定成 OOO」等特定型別可能需要進階服務配合;內建可設定的還有透明度/可見性等。

    Visibility:事件可見性(預設、公開、私人)。

    EventTransparency:是否占用時間(OPAQUE/TRANSPARENT)。

    EventColor / Color:事件或日曆顏色常數。


權限與授權(Scopes)

第一次執行會跳出授權視窗。Calendar 服務會依你呼叫的方法請求對應的日曆存取權,例如讀寫事件。官方文件也註明多數方法需要特定授權範圍才能讀取或修改日曆資料——這是正常行為。


操作範例

拿到日曆

// 預設日曆
const cal = CalendarApp.getDefaultCalendar();

// 或:用名稱找(可能有多個)
const [teamCal] = CalendarApp.getCalendarsByName('團隊例會');

// 或:已知日曆ID(設定 > 整合日曆 可以看到)
const byId = CalendarApp.getCalendarById('your_calendar_id@group.calendar.google.com');


建立單次事件(含地點、來賓、寄出邀請)

const start = new Date('2025-10-08T09:30:00+09:00');
const end   = new Date('2025-10-08T10:30:00+09:00');

const event = cal.createEvent(
  '產品週會',
  start,
  end,
  {
    location: '線上 Google Meet',
    description: '本週議程:Roadmap、Bug Review、數據觀察',
    guests: 'a@example.com,b@example.com',
    sendInvites: true // 建立時寄出邀請
  }
);


建立循環事件(每週三 9:30)

const recurrence = CalendarApp.newRecurrence()
  .addWeeklyRule()
  .onlyOnWeekday(CalendarApp.Weekday.WEDNESDAY);

const series = cal.createEventSeries(
  '工程例會',
  new Date('2025-10-08T09:30:00+09:00'),
  new Date('2025-10-08T10:30:00+09:00'),
  recurrence,
  { guests: 'devlead@example.com,qa@example.com', sendInvites: true }
);


整天與跨日活動(年度活動、出差)

cal.createAllDayEvent('產品黑客松', new Date('2025-11-15'));

// 跨日整天活動
cal.createAllDayEvent('年度會議', new Date('2025-12-10'), new Date('2025-12-12'), {
  description: '全公司年會與工作坊'
});


查詢事件(區間、條件)

// 抓一週內所有事件
const weekStart = new Date('2025-10-06T00:00:00+09:00');
const weekEnd   = new Date('2025-10-13T00:00:00+09:00');
const events = cal.getEvents(weekStart, weekEnd);

// 只抓「可見」且含指定關鍵字的事件(options 可篩選)
const filtered = cal.getEvents(weekStart, weekEnd, { search: '例會', visibility: CalendarApp.Visibility.DEFAULT });


修改事件(標題、時間、可見性、透明度、顏色、來賓)

event.setTitle('產品週會(含上市檢討)')
     .setLocation('會議室 A / 線上')
     .setTime(new Date('2025-10-08T10:00:00+09:00'), new Date('2025-10-08T11:00:00+09:00'))
     .setVisibility(CalendarApp.Visibility.DEFAULT)
     .setTransparency(CalendarApp.EventTransparency.OPAQUE);

event.addGuest('pm@example.com');     // 加來賓
event.removeGuest('b@example.com');   // 移除來賓
event.addPopupReminder(10);           // 10 分鐘前跳窗提醒


操作每個 Class 的「實作步驟」與常用方法

CalendarApp:入口動作

常見步驟 :

    1.    取得日曆:預設日曆、用名稱找、用 ID 找

    2.    直接建事件/整天事件/循環事件

    3.    產生循環規則:CalendarApp.newRecurrence()

// 取得日曆
const calDefault = CalendarApp.getDefaultCalendar();
const [teamCal]  = CalendarApp.getCalendarsByName('團隊例會');
const byId       = CalendarApp.getCalendarById('xxx@group.calendar.google.com');

// 建立單次事件(含來賓、寄出邀請)
calDefault.createEvent('產品週會',
  new Date('2025-10-08T09:30:00+09:00'),
  new Date('2025-10-08T10:30:00+09:00'),
  { location:'Meet', guests:'a@x.com,b@x.com', sendInvites:true });

// 建立循環事件:每週三 09:30
const rule = CalendarApp.newRecurrence().addWeeklyRule();
const series = calDefault.createEventSeries('工程例會',
  new Date('2025-10-08T09:30:00+09:00'),
  new Date('2025-10-08T10:30:00+09:00'),
  rule,
  { guests:'dev@x.com', sendInvites:true });




Calendar:在「某一個日曆」上行動

常見步驟 :

    1.    查詢區間事件:getEvents(start,end,options)

    2.    建立整天/跨日整天:createAllDayEvent*

    3.    抓時區或基本資訊

const cal = CalendarApp.getDefaultCalendar();

// 抓本週含關鍵字「例會」的事件
const start = new Date('2025-10-06T00:00:00+09:00');
const end   = new Date('2025-10-13T00:00:00+09:00');
const events = cal.getEvents(start, end, { search: '例會' });

// 整天與跨日整天活動
cal.createAllDayEvent('黑客松', new Date('2025-11-15'));
cal.createAllDayEvent('年會', new Date('2025-12-10'), new Date('2025-12-12'), {
  description: '全公司年會與工作坊'
});




CalendarEvent:單次事件的讀寫

常見步驟 : 

    1.    讀寫基本欄位:標題、時間、地點、描述

    2.    控制可見性與透明度(是否占用時段)

    3.    管理來賓與提醒

    4.    取得 iCalUID 以避免重複

    5.    需要時用 setTag(key, value) 存外部系統關聯

function tuneEvent(e) {
  e.setTitle('產品週會(含上市檢討)')
   .setLocation('會議室 A / Meet')
   .setTime(new Date('2025-10-08T10:00:00+09:00'), new Date('2025-10-08T11:00:00+09:00'))
   .setVisibility(CalendarApp.Visibility.DEFAULT)
   .setTransparency(CalendarApp.EventTransparency.OPAQUE)
   .addPopupReminder(10);

  e.addGuest('pm@x.com');        // 加來賓
  e.removeGuest('temp@x.com');   // 移除來賓

  const id = e.getId();          // iCalUID,可用於去重/對應
  e.setTag('project', 'Q4Launch'); // 自訂標籤:存外部鍵
}




CalendarEventSeries:循環事件的系列操作

常見步驟 :

    1.    建立後可加提醒、設描述/地點

    2.    若要變更「循環規則」,重建規則(EventRecurrence/RecurrenceRule)

series.setLocation('Meet').addEmailReminder(30);




EventRecurrence / RecurrenceRule:規則怎麼寫

常見步驟 :

    1.    建規則:每週/每月/每年、次數上限 times(n)、截至日 until(date)

    2.    指定星期幾 / 月份幾號 / 排除特定日

// 每月最後一天,連續 6 次
const r1 = CalendarApp.newRecurrence().addMonthlyRule().times(6);

// 每週三,直到年底;排除 12/25
const r2 = CalendarApp.newRecurrence()
  .addWeeklyRule()
  .onlyOnWeekday(CalendarApp.Weekday.WEDNESDAY)
  .until(new Date('2025-12-31'))
  .addDateExclusion(new Date('2025-12-25'));




EventGuest:來賓細節

常見步驟 :

    1.    用 CalendarEvent.getGuestList(includeOwner) 取回陣列

    2.    對每位 EventGuest 讀 email、名稱、狀態

const guests = event.getGuestList(true);
guests.forEach(g => {
  const email = g.getEmail();
  const name  = g.getName();
  const stat  = g.getGuestStatus(); // 出席狀態
});



常見錯誤與排雷清單

時區錯亂 : 

    症狀:建立時間偏移一小時或一天。

    做法:請務必以 ISO 字串+時區 或使用 Session.getScriptTimeZone() 統一轉換;必要時也可讀取日曆本身時區(Calendar.getTimeZone())來對齊。

重複建立事件 :

    症狀:定時腳本每跑一次就複製一次同名會議。

    做法:把事件的 getId()(iCalUID)寫回你的資料庫/試算表,或用 setTag 存一個外部鍵,下次先查再決定新增或更新。

來賓沒有收到邀請 :

    症狀:事件建立了,但沒信。

    做法:建立時在 options 帶 sendInvites: true;若後續才加來賓,內建服務會寄信,但若用進階 API update/patch 請留意對應參數。

循環規則怪異(每月第 N 週、排除特定日):

    做法:用 EventRecurrence + RecurrenceRule 的 addWeeklyRule/addMonthlyRule/addDateExclusion 等方法,直覺地組出你要的規則,並善用 until() 或 setTimeZone()。

OOO / Focus Time 需求 :

    做法:內建可讀取 EventType,也能用 Visibility/Transparency 描述大多數情境;若要精準指定型別,考慮 Advanced 服務。


實作注意點

授權範圍:

    第一次跑會跳授權。大多數讀寫都需要 Calendar 權限;看到要求 scope 是正常的。

時區一致:

    腳本預設時區、日曆時區、字串時間(最好用含時區的 ISO 格式)要對齊,避免一小時或一天的偏移。可讀 Calendar.getTimeZone() 佐證。

單次 vs 循環:

    CalendarEvent(單次)能 setTime;CalendarEventSeries 是「系列」,常見錯誤是在 Series 物件上呼叫 setTime(會噴錯)。

    要改規則請用 EventRecurrence/RecurrenceRule 重建。

提醒上限與可用範圍:

    單一事件的提醒次數與時間範圍有限制(5 分鐘~4 週前,且最多 5 個提醒,超過會丟錯)。

來賓通知:

    建立時帶 sendInvites:true 會寄邀請;後續用 API 更新要注意相關參數(進階服務 update/patch 的通知行為不同)。

查詢關鍵字的限制:    

    getEvents(..., {search:'A B'}) 不是完整的布林搜尋;複雜條件建議自己過濾。

讀取循環詳細規則:

    想從單次事件回推整個規則細節,可能需要 Advanced Calendar Service(REST Events.get 的 recurrence 欄位)。


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