appscript.dev
Automation Intermediate Calendar Gmail

Detect and report double-booked time

Flag overlapping events on Northwind calendars before they cause conflicts.

Published Jul 16, 2025

Double-booked time is the small calendar problem that quietly burns the week at Northwind — a client review stacked on a producer one-to-one, a workshop overlapping the all-hands, nobody notices until two meeting invites ping at the same minute. By then someone has to bail, apologise, and reschedule.

This script walks the next seven days of the default calendar, finds every pair of events whose times overlap, and emails you a list. Run it each morning and the conflicts surface while there is still time to move one — not five minutes before the clash.

What you’ll need

  • Access to the calendar you want to check. The script uses CalendarApp.getDefaultCalendar(); swap in getCalendarById if you want to scan a shared team calendar instead.
  • Gmail access for the alert email — sent to the script owner via Session.getActiveUser().getEmail().
  • Nothing else — no spreadsheet, no API keys.

The script

// How many days ahead to scan for overlaps. One week is usually enough notice
// to move one of the conflicting meetings.
const LOOKAHEAD_DAYS = 7;

// Milliseconds in a day — used to build the end of the scan window.
const ONE_DAY_MS = 24 * 60 * 60 * 1000;

/**
 * Scans the next seven days of the default calendar, finds every pair of
 * events that overlap, and emails the script owner a list of the conflicts.
 */
function reportDoubleBookings() {
  const start = new Date();
  const end = new Date(start.getTime() + LOOKAHEAD_DAYS * ONE_DAY_MS);

  // 1. Fetch every event in the window and sort by start time. Sorting is
  //    what makes the linear sweep below correct — without it, an event
  //    out of order would hide a real conflict.
  const events = CalendarApp.getDefaultCalendar()
    .getEvents(start, end)
    .sort((a, b) => a.getStartTime() - b.getStartTime());

  // 2. Walk neighbouring pairs. If event[i] ends after event[i+1] starts,
  //    they overlap. This is O(n) and catches every adjacent conflict; for
  //    rare three-way pile-ups, see "Watch out for".
  const conflicts = [];
  for (let i = 0; i < events.length - 1; i++) {
    if (events[i].getEndTime() > events[i + 1].getStartTime()) {
      const a = events[i].getTitle();
      const b = events[i + 1].getTitle();
      const when = Utilities.formatDate(
        events[i + 1].getStartTime(),
        Session.getScriptTimeZone(),
        'EEE d MMM HH:mm'
      );
      conflicts.push(`${when} — "${a}" overlaps "${b}"`);
    }
  }

  // 3. Send the email only if there is something to say.
  if (!conflicts.length) {
    Logger.log('No conflicts in the next ' + LOOKAHEAD_DAYS + ' days.');
    return;
  }

  GmailApp.sendEmail(
    Session.getActiveUser().getEmail(),
    `${conflicts.length} calendar conflict(s) this week`,
    conflicts.join('\n')
  );
  Logger.log('Reported ' + conflicts.length + ' conflict(s).');
}

How it works

  1. reportDoubleBookings builds a window from now to seven days ahead, the default lookahead defined in LOOKAHEAD_DAYS.
  2. It fetches every event in the window from the default calendar and sorts them by start time. Sorted order is what makes the next step work.
  3. It walks the list in pairs. If the end time of one event is later than the start time of the next, they overlap — push a one-line description onto the conflicts list.
  4. If no conflicts turned up, it logs that fact and returns without sending an empty email.
  5. Otherwise it emails the script owner one line per conflict, with the time formatted in the script’s timezone so the message reads naturally.

Example run

Say the calendar has these events this week:

  • Mon 10:00–11:00 — Production brief
  • Mon 10:30–11:30 — Client call: Fabrikam
  • Wed 14:00–15:00 — Workshop prep
  • Thu 09:00–10:00 — All-hands

After a run, the email looks like this:

Subject: 1 calendar conflict(s) this week

Mon 1 Sep 10:30 — “Production brief” overlaps “Client call: Fabrikam”

The Wednesday and Thursday events do not overlap each other and so are not listed. Clean weeks produce no email at all — the script logs “No conflicts” and exits.

Trigger it

Run this once each morning so conflicts land before the day starts:

  1. In the Apps Script editor, open Triggers (the clock icon).
  2. Add a trigger for reportDoubleBookings, time-driven, day timer, 6am–7am.
  3. Save and approve the calendar and Gmail authorisation prompts.

Watch out for

  • The pairwise sweep only compares neighbours. If three events all overlap, it will spot two of the three pairings — usually enough to flag the problem, but not a complete conflict graph. For exhaustive detection, nest a second loop and compare every event against every later one.
  • All-day events count as events. If you keep “Out of office” or “Holiday” markers in the same calendar, they will appear as overlaps with normal meetings. Filter them out by title or by isAllDayEvent() if that gets noisy.
  • Tentative (“maybe”) events still show up here. Add a check on getMyStatus() and skip anything that is not OWNER or YES if you only want firm bookings.
  • Recurring events are fine — getEvents already expands them into individual occurrences in the window.

Related