Auto-reschedule low-priority conflicts
Move flexible Northwind events around fixed ones — focus blocks bend, client calls don't.
Publié le 16 oct. 2025
A Northwind designer’s week is a mix of two kinds of event: fixed commitments like client calls and reviews, and flexible work like focus blocks and admin time. When a new client call lands on top of a focus block, the focus block should give way — but only a person noticing the clash and dragging it does that. Left alone, the calendar shows a double-booking and the focus block quietly never happens.
This script enforces the rule automatically. Flexible events are marked with a
[flex] prefix in their title. The script scans the week, finds any overlap
between a flexible event and another event, and slides the flexible one to just
after the thing it collided with. Fixed events never move.
What you’ll need
- The Google Calendar you want managed — the script uses your default calendar.
- A naming convention: every flexible, movable event has a title starting with
[flex], for example[flex] Focus blockor[flex] Admin. - Nothing else. Anything without the
[flex]prefix is treated as fixed and left alone.
The script
// Title prefix that marks an event as flexible (movable).
const FLEX_PREFIX = '[flex]';
// How far ahead to scan for conflicts, in days.
const SCAN_DAYS = 7;
// Gap to leave after a fixed event before a moved flexible one, in minutes.
const BUFFER_MINUTES = 5;
/**
* Scans the next week, finds overlaps between a flexible event and
* its neighbour, and shifts the flexible event clear of the conflict.
*/
function rescheduleFlexible() {
const start = new Date();
const end = new Date(start.getTime() + SCAN_DAYS * 86400000);
// Pull the week's events, sorted earliest-start first.
const events = CalendarApp.getDefaultCalendar()
.getEvents(start, end)
.sort((a, b) => a.getStartTime() - b.getStartTime());
if (events.length < 2) {
Logger.log('Fewer than two events — no conflicts possible.');
return;
}
// Compare each event with the one immediately after it.
for (let i = 0; i < events.length - 1; i++) {
const a = events[i];
const b = events[i + 1];
// No overlap: a ends before (or as) b starts — nothing to do.
if (a.getEndTime() <= b.getStartTime()) continue;
// They overlap. Move whichever one is flexible; if both are flexible
// move the later one, if neither is, leave the clash for a human.
if (a.getTitle().startsWith(FLEX_PREFIX)) {
shiftAfter(a, b);
} else if (b.getTitle().startsWith(FLEX_PREFIX)) {
shiftAfter(b, a);
}
}
}
/**
* Moves the flexible event to start just after the fixed event ends,
* keeping the flexible event's original duration.
*/
function shiftAfter(flex, fixed) {
const duration = flex.getEndTime() - flex.getStartTime();
const newStart = new Date(
fixed.getEndTime().getTime() + BUFFER_MINUTES * 60000
);
flex.setTime(newStart, new Date(newStart.getTime() + duration));
}
How it works
rescheduleFlexibleworks out a window — now toSCAN_DAYSahead — and reads every event in it from the default calendar.- It sorts the events by start time, so each event sits next to its nearest neighbour.
- If there are fewer than two events there can be no conflict, so it stops.
- It walks the list comparing each event
awith the next eventb. Ifaends on or beforebstarts there is no overlap and it moves on. - When two events overlap it checks the
[flex]prefix: ifais flexible it movesa; otherwise ifbis flexible it movesb. If neither is flexible the clash is between two fixed events and is left for a person to resolve. shiftAftermeasures the flexible event’s duration, sets its new start toBUFFER_MINUTESafter the fixed event ends, and applies a matching new end — so the event keeps its length and just slides later.
Example run
The calendar before a run, on one day:
| Event | Time |
|---|---|
[flex] Focus block | 10:00 – 12:00 |
| Client call: Acme | 11:00 – 11:30 |
The focus block and the client call overlap. After rescheduleFlexible:
| Event | Time |
|---|---|
| Client call: Acme | 11:00 – 11:30 |
[flex] Focus block | 11:35 – 13:35 |
The client call stays put; the focus block keeps its two-hour length and moves to start five minutes after the call ends.
Trigger it
Run this on a daily schedule so the week stays clean as new events land:
- In the Apps Script editor open Triggers (the clock icon).
- Add a trigger for
rescheduleFlexible, Time-driven, Day timer, set for early morning before the working day starts. - Approve the authorisation prompt. Flexible events now get nudged clear of conflicts every morning.
Watch out for
- The script only compares each event with its immediate neighbour. Moving a flexible event can push it onto a later event — run the trigger again, or loop until no overlaps remain, for dense calendars.
- A moved flexible event keeps its duration but not its preferred time of day. A morning focus block can land in the afternoon if the day is busy.
- Only events with the exact
[flex]prefix are movable. A typo in the title means the event is treated as fixed and never moved. - Two overlapping fixed events are left untouched by design — the script will not choose between two real commitments.
- The script edits your live calendar with no undo. Test it on a quiet week, or on a secondary calendar, before trusting it with your main one.
- All-day events have no real start and end time in the usual sense and can produce odd comparisons. Exclude them if your calendar uses them.
À voir aussi
Schedule personal habits and routines
Block recurring habits on Awadesh's calendar — gym, walks, deep-work mornings.
Mis à jour le 5 nov. 2025
Sync birthdays and anniversaries to Calendar
Populate recurring personal dates from a Sheet — the Northwind team rituals calendar.
Mis à jour le 1 nov. 2025
Generate recurring events with custom exceptions
Handle complex recurrence rules in code — every Tuesday except UK bank holidays.
Mis à jour le 28 oct. 2025
Build a contract-renewal calendar
Track Northwind's recurring-revenue renewal dates as calendar events for proactive sales.
Mis à jour le 8 oct. 2025
Apply attendee rules by event type
Auto-invite the right Northwind people from a Sheet — design reviews get design, client calls get account leads.
Mis à jour le 4 oct. 2025