You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Apps Script Calendar API直接获取Out of Office事件并统计休假天数(含单日/多日事件处理)

解决Google Calendar API捕获单日Out of Office事件及多日事件统计问题

我来帮你搞定这个问题,你的代码主要卡在两个关键点:单日全天的Out of Office事件没被API正确识别,以及多日事件的跨月天数统计不准确。下面是具体的解决方案和修正后的完整代码:

核心问题分析

  1. 单日事件丢失的原因:Google Calendar API对全天事件和定时事件的时间字段存储不同——全天的Out of Office事件用start.date和end.date(格式为YYYY-MM-DD),而你的原代码只处理了dateTime字段,直接漏掉了这类单日休假。
  2. 多日事件统计误差:如果休假事件跨月,直接用整个事件的总天数会把非当月的部分也算进去,必须计算事件与当月时间范围的交集天数,再筛选其中的工作日。

修正后的完整代码

const calculateMonthlyLeaveDays = () => {
  const WEEK_DAYS = [1, 2, 3, 4, 5]; // 周一到周五对应getDay()的返回值

  // 计算两个日期之间的工作日数量(包含起止日)
  const countWeekDaysInRange = (startDate, endDate) => {
    let count = 0;
    const current = new Date(startDate);
    while (current <= endDate) {
      if (WEEK_DAYS.includes(current.getDay())) {
        count++;
      }
      current.setDate(current.getDate() + 1);
    }
    return count;
  };

  // 解析Calendar API返回的事件时间(兼容全天/定时事件)
  const parseEventDate = (dateObj) => {
    if (dateObj.date) {
      // 全天事件:date字段是YYYY-MM-DD,转成当月第一天0点
      return new Date(dateObj.date);
    } else {
      // 定时事件:直接用dateTime转成Date对象
      return new Date(dateObj.dateTime);
    }
  };

  // 获取当月起止时间
  const now = new Date();
  const currentYear = now.getFullYear();
  const currentMonth = now.getMonth();
  const monthStart = new Date(currentYear, currentMonth, 1);
  const monthEnd = new Date(currentYear, currentMonth + 1, 0, 23, 59, 59); // 当月最后一天的23:59:59

  // 1. 获取当月所有Out of Office事件(包含全天和定时)
  const oooEvents = Calendar.Events.list('primary', {
    timeMin: monthStart.toISOString(),
    timeMax: monthEnd.toISOString(),
    singleEvents: true, // 展开重复事件
    eventTypes: 'outOfOffice' // 直接筛选Out of Office类型
  }).items.map(event => {
    return {
      start: parseEventDate(event.start),
      end: parseEventDate(event.end)
    };
  });

  // 2. 计算每个Out of Office事件在当月内的工作日天数
  const totalOoODays = oooEvents.reduce((total, event) => {
    // 计算事件与当月的时间交集
    const eventStart = event.start;
    const eventEnd = event.end;
    // 交集的开始是事件开始和当月开始的较大值
    const overlapStart = new Date(Math.max(eventStart.getTime(), monthStart.getTime()));
    // 交集的结束是事件结束和当月结束的较小值(注意全天事件的end是次日,所以要减一天)
    const overlapEnd = new Date(Math.min(eventEnd.getTime() - 86400000, monthEnd.getTime()));
    
    if (overlapStart > overlapEnd) return total; // 无交集,跳过
    return total + countWeekDaysInRange(overlapStart, overlapEnd);
  }, 0);

  // 3. 获取当月法定节假日(法国)的工作日数量
  const holidayCalendar = CalendarApp.getCalendarsByName('Holidays in France')[0];
  const totalHolidays = holidayCalendar.getEvents(monthStart, monthEnd)
    .filter(event => WEEK_DAYS.includes(event.getStartTime().getDay()))
    .length;

  // 4. 计算当月工作日总数
  const totalWorkDays = countWeekDaysInRange(monthStart, monthEnd);

  // 生成邮件内容
  const message = `
${totalWorkDays} working days from ${monthStart.toLocaleDateString()} to ${monthEnd.toLocaleDateString()}
- ${totalOoODays} out of office days
- ${totalHolidays} bank holidays
Total leave days: ${totalOoODays + totalHolidays}
`;

  // 发送邮件(替换成你的邮箱)
  MailApp.sendEmail('your.email@provider.com', 'Monthly Leave Summary', message);
  Logger.log(message);
};

关键优化点说明

  • 兼容全天/定时事件:新增parseEventDate函数,自动识别事件的时间字段类型,不会漏掉单日全天的Out of Office。
  • 精确计算当月有效天数:通过计算事件与当月的时间交集,避免跨月事件的天数统计错误,同时只统计工作日。
  • 移除#dayoff依赖:直接通过API的eventTypes: 'outOfOffice'参数筛选休假事件,无需手动加标签。
  • 展开重复事件:调用API时添加singleEvents: true,确保重复的Out of Office事件也能被正确统计。

分享给同事的步骤

  1. 让同事打开Google Apps Script编辑器,新建项目。
  2. 将上述代码粘贴进去,替换邮件地址为他们自己的邮箱。
  3. 启用高级Calendar API:点击编辑器菜单「资源」→「高级Google服务」,找到「Calendar API」并开启。
  4. 设置定时触发器:点击编辑器菜单「编辑」→「当前项目的触发器」,添加一个每月触发的时间驱动触发器,选择每月最后一天执行。

内容的提问来源于stack exchange,提问作者Michel Hua

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 13:34:07