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 ingetCalendarByIdif 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
reportDoubleBookingsbuilds a window from now to seven days ahead, the default lookahead defined inLOOKAHEAD_DAYS.- 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.
- 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.
- If no conflicts turned up, it logs that fact and returns without sending an empty email.
- 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:
- In the Apps Script editor, open Triggers (the clock icon).
- Add a trigger for
reportDoubleBookings, time-driven, day timer, 6am–7am. - 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 notOWNERorYESif you only want firm bookings. - Recurring events are fine —
getEventsalready expands them into individual occurrences in the window.
Related
Send a feedback survey after each event
Email attendees a survey link automatically after Northwind workshops or trainings.
Updated Oct 24, 2025
Build a team-capacity view from calendars
Show how booked the Northwind team is this week — meeting hours per person.
Updated Oct 20, 2025
Flag meetings that could have been emails
Detect short, agendaless, oversized meetings — the smell of bad calendar hygiene.
Updated Oct 12, 2025
Send tiered deadline countdown reminders
Email Northwind teammates at 7, 3, and 1 days out from a Sheet of upcoming deadlines.
Updated Sep 30, 2025
Archive past events to a log sheet
Keep a searchable Northwind meeting history — every event logged with title, attendees, duration.
Updated Sep 26, 2025