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

如何调试Apps Script导入日历事件时的BadRequest错误?

问题

基于谷歌开发者平台的「填充团队休假日历」示例脚本开发的Apps Script,原本正常运行数月,近期未做任何修改,调用Calendar.Events.import(event, TEAM_CALENDAR_ID)时突然报错:

GoogleJsonResponseException: API call to calendar.events.import failed with error: Bad Request

调试仅返回400 Bad Request,无额外细节。导入的event变量来自Calendar.Events.list(...)的返回结果,是API生成的JS事件对象。

最小复现示例

let TEAM_CALENDAR_ID = '<calendar id goes here>';

function testImport() {
  const now = new Date();
  let user = Session.getActiveUser();
  
  // Fetch next 10 events
  let events = Calendar.Events.list(user.getEmail(), {
    timeMin: now.toISOString(),
    singleEvents: true,
    orderBy: 'startTime',
    maxResults: 10
  });

  if (events.items.length === 0) {
    console.error('No events found');
    return;
  }

  // Use next upcoming event for this user
  let event = events.items[0];

  // Set event fields
  event.organizer = { id: TEAM_CALENDAR_ID };
  event.attendees = [];

  try {
    Calendar.Events.import(event, TEAM_CALENDAR_ID);
  } catch (e) {
    console.error('Error attempting to import event: %s.', e.toString());
  }
}

完整脚本

// Set the ID of the team calendar to add events to. You can find the calendar's
// ID on the settings page.
let TEAM_CALENDAR_ID = '<calendar ID here>';
// Set the email address of the Google Group that contains everyone in the team.
// Ensure the group has less than 500 members to avoid timeouts.
let GROUP_EMAIL = '<group email address here>';
let MONTHS_IN_ADVANCE = 3;
 
/**
 * Sets up the script to run automatically every hour.
 */
function setup() {
  let triggers = ScriptApp.getProjectTriggers();
  if (triggers.length > 0) {
    throw new Error('Triggers are already setup.');
  }
  ScriptApp.newTrigger('sync').timeBased().everyHours(1).create();
  // Runs the first sync immediately.
  sync();
}
 
/**
 * Looks through the group members' public calendars and adds any
 * 'vacation' or 'out of office' events to the team calendar.
 */
function sync() {
  // Defines the calendar event date range to search.
  let today = new Date();
  let maxDate = new Date();
  maxDate.setMonth(maxDate.getMonth() + MONTHS_IN_ADVANCE);
 
  // Determines the time the the script was last run.
  let lastRun = PropertiesService.getScriptProperties().getProperty('lastRun');
  lastRun = lastRun ? new Date(lastRun) : null;
 
  // Gets the list of users in the Google Group.
  let users = GroupsApp.getGroupByEmail(GROUP_EMAIL).getUsers();
 
  // For each user, finds Out Of Office events, and import
  // each to the team calendar.
  let count = 0;
  users.forEach(function(user) {
    let events = findEvents(user, today, maxDate, lastRun);
    events.forEach(function(event) {
      importEvent(user, event);
      count++;
    });
  });
 
  PropertiesService.getScriptProperties().setProperty('lastRun', today);
  console.log('Updated ' + count + ' events');
}
 
/**
 * In a given user's calendar, looks for Out Of Office events within the 
 * specified date range and returns any such events found.
 * @param {Session.User} user The user to retrieve events for.
 * @param {Date} start The starting date of the range to examine.
 * @param {Date} end The ending date of the range to examine.
 * @param {Date} optSince A date indicating the last time this script was run.
 * @return {Calendar.Event[]} An array of calendar events.
 */
function findEvents(user, start, end, optSince) {
  let params = {
    timeMin: formatDateAsRFC3339(start),
    timeMax: formatDateAsRFC3339(end),
    showDeleted: true,
  };
  if (optSince) {
    // This prevents the script from examining events that have not been
    // modified since the specified date (that is, the last time the
    // script was run).
    params.updatedMin = formatDateAsRFC3339(optSince);
  }
  let pageToken = null;
  let events = [];
  do {
    params.pageToken = pageToken;
    let response;
    try {
      response = Calendar.Events.list(user.getEmail(), params);
    } catch (e) {
      console.error('Error retriving events for %s: %s; skipping',
          user, e.toString());
      continue;
    }
    events = events.concat(response.items.filter(function(item) {
      return shoudImportEvent(user, item);
    }));
    pageToken = response.nextPageToken;
  } while (pageToken);
  return events;
}
 
/**
 * Determines if the given event should be imported into the shared team
 * calendar.
 * @param {Session.User} user The user that is attending the event.
 * @param {Calendar.Event} event The event being considered.
 * @return {boolean} True if the event should be imported.
 */
function shoudImportEvent(user, event) {
  
  // Skip events that are not Out Of Office
  if (event.eventType != "outOfOffice") {
    return false;
  }
  
  // If the user is the creator of the event, always imports it.
  if (!event.organizer || event.organizer.email == user.getEmail()) {
    return true;
  }
  
  // Only imports events the user has accepted.
  if (!event.attendees) {
    return false;
  }
  let matching = event.attendees.filter(function(attendee) {
    return attendee.self;
  });
  
  return matching.length > 0 && matching[0].responseStatus == 'accepted';
}
 
/**
 * Imports the given event from the user's calendar into the shared team
 * calendar.
 * @param {string} username The team member that is attending the event.
 * @param {Calendar.Event} event The event to import.
 */
function importEvent(user, event) {
  let username = user.getEmail().split('@')[0];
  username = username.charAt(0).toUpperCase() + username.slice(1);
  event.summary = '[' + username + '] ' + event.summary;
  event.organizer = {
    id: TEAM_CALENDAR_ID,
  };
  event.attendees = [];
  let action = event.status == "confirmed" ? "Importing" : "Removing";
  console.log('%s: %s on %s', action, event.summary, event.start.getDateTime());
  try {
    Calendar.Events.import(event, TEAM_CALENDAR_ID);
  } catch (e) {
    console.error('Error attempting to import event: %s. Skipping.',
        e.toString());
  }
}
 
/**
 * Returns an RFC3339 formated date String corresponding to the given
 * Date object.
 * @param {Date} date a Date.
 * @return {string} a formatted date string.
 */
function formatDateAsRFC3339(date) {
  return Utilities.formatDate(date, 'UTC', 'yyyy-MM-dd\'T\'HH:mm:ssZ');
}
调试与解决方法

1. 捕获完整错误详情

默认的e.toString()仅返回基础错误信息,需获取e.details查看API返回的具体错误原因。修改importEvent中的catch块:

catch (e) {
  if (e.details) {
    console.error('Full error details:', JSON.stringify(e.details, null, 2));
  }
  console.error('Error attempting to import event: %s. Skipping.', e.toString());
}

此操作会打印API返回的具体字段错误(如必填字段缺失、格式错误等)。

2. 清理冗余字段

Calendar.Events.list返回的事件对象包含大量只读字段(如id、etag、created、updated等),这些字段会在调用import时引发冲突。需创建新的事件对象,仅保留必要可写字段:

function importEvent(user, event) {
  let username = user.getEmail().split('@')[0];
  username = username.charAt(0).toUpperCase() + username.slice(1);
  
  // 仅保留必要字段,避免只读字段干扰
  let cleanEvent = {
    summary: '[' + username + '] ' + event.summary,
    description: event.description,
    start: event.start,
    end: event.end,
    eventType: event.eventType,
    status: event.status,
    organizer: { id: TEAM_CALENDAR_ID },
    attendees: []
  };

  let action = event.status == "confirmed" ? "Importing" : "Removing";
  console.log('%s: %s on %s', action, cleanEvent.summary, cleanEvent.start.getDateTime());
  try {
    Calendar.Events.import(cleanEvent, TEAM_CALENDAR_ID);
  } catch (e) {
    if (e.details) {
      console.error('Full error details:', JSON.stringify(e.details, null, 2));
    }
    console.error('Error attempting to import event: %s. Skipping.', e.toString());
  }
}

只读字段会被API判定为无效输入,这是引发400错误的最常见原因。

3. 检查权限与日历设置

  • 确认脚本使用的账号拥有共享日历的编辑权限,若为团队日历,检查共享设置是否允许该账号修改。
  • 检查共享日历是否被设为只读,或存在特殊访问限制。

4. 验证日期时间格式

尽管事件对象来自API,仍可能存在时区问题。打印event.start和event.end的内容,确认格式符合RFC3339规范且时区信息正确。

5. 测试单个事件

在最小复现脚本中,手动指定简单事件对象进行测试,排除原事件对象的问题:

function testSimpleImport() {
  let testEvent = {
    summary: 'Test OOO Event',
    start: { dateTime: new Date().toISOString() },
    end: { dateTime: new Date(Date.now() + 3600000).toISOString() }, // 1小时后
    eventType: 'outOfOffice',
    status: 'confirmed',
    organizer: { id: TEAM_CALENDAR_ID },
    attendees: []
  };
  try {
    Calendar.Events.import(testEvent, TEAM_CALENDAR_ID);
    console.log('Test event imported successfully');
  } catch (e) {
    if (e.details) {
      console.error('Full error details:', JSON.stringify(e.details, null, 2));
    }
    console.error('Error:', e.toString());
  }
}

若测试成功,说明问题出在原事件对象的冗余字段或特定属性上。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 07:07:32