code icon Code

Get Free/Busy Status

Query free/busy information for one or more Google Calendars

Source Code

/**
 * Query free/busy information for one or more Google Calendars
 * @param {string} timeMin - Start datetime in RFC3339 format (e.g., '2025-12-15T09:00:00-08:00')
 * @param {string} timeMax - End datetime in RFC3339 format (e.g., '2025-12-15T17:00:00-08:00')
 * @param {string} calendarToken - Google Calendar API token
 * @param {Array<string>} calendars - Array of calendar IDs to check (default: ['primary'])
 * @returns {Promise<Object>} Free/busy information with busySlots, allFree flag, etc.
 */
async function getFreeBusy(timeMin, timeMax, calendarToken, calendars = ['primary']) {
  if (!timeMin || !timeMax) {
    throw new Error("Time range (timeMin and timeMax) is required");
  }

  const body = {
    timeMin: timeMin,
    timeMax: timeMax,
    items: calendars.map(id => ({ id }))
  };

  const res = await fetch(
    "https://www.googleapis.com/calendar/v3/freeBusy",
    {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${calendarToken}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify(body)
    }
  );

  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Google Calendar API failed (${res.status}): ${errorText}`);
  }

  const data = await res.json();

  // Process and simplify the response
  const calendarStatus = {};

  for (const calId of calendars) {
    const calData = data.calendars[calId];
    if (calData) {
      calendarStatus[calId] = {
        busySlots: calData.busy || [],
        busyCount: (calData.busy || []).length,
        errors: calData.errors || []
      };
    }
  }

  // Determine if entire range is free for all calendars
  const allFree = Object.values(calendarStatus).every(cal => cal.busyCount === 0);

  return {
    timeMin,
    timeMax,
    calendars: calendarStatus,
    allFree,
    queriedAt: new Date().toISOString()
  };
}

export { getFreeBusy };