appscript.dev
Automation Intermediate Calendar

Add travel-time buffers before offsite events

Insert commute events before any Northwind meeting outside the studio.

Published Aug 1, 2025

Northwind’s calendar shows a client meeting starting at 10:00 across town, and the half-hour it takes to get there is invisible. So the 09:30 slot looks free, someone books it, and now there is no way to make the offsite on time. The commute is real time that nothing on the calendar reflects.

This script blocks that time out. It scans the next week of events, finds the ones with a location outside the studio, asks the Maps service how long the journey takes, and drops a “Travel to…” event in front of each one. The commute becomes a visible, bookable-against block instead of an assumption.

What you’ll need

  • A Google Calendar — the script uses your default calendar.
  • Events with a real address in the location field. Events with no location are treated as in-studio and skipped.
  • Nothing else — the Maps service is built into Apps Script, no API key needed.

The script

// Where journeys are measured from — the studio.
const HOME_LOCATION = 'Northwind Studios, Hoxton Square, London';

// How far ahead to scan, in days.
const SCAN_DAYS = 7;

// Tag used to mark events we've already added a buffer for.
const BUFFER_TAG = 'travel-buffered';

/**
 * Scans the next week of calendar events and inserts a travel-time
 * event before each one that has an external location.
 */
function addTravelBuffers() {
  const now = new Date();
  const until = new Date(now.getTime() + SCAN_DAYS * 86400000);

  const calendar = CalendarApp.getDefaultCalendar();
  const events = calendar.getEvents(now, until);

  if (!events.length) {
    Logger.log('No upcoming events to check.');
    return;
  }

  let added = 0;

  for (const event of events) {
    // 1. Skip events with no location, or ones already buffered.
    const location = event.getLocation();
    if (!location) continue;
    if (event.getTag(BUFFER_TAG) === 'yes') continue;

    // 2. Ask Maps how long it takes to get there by public transport.
    const directions = Maps.newDirectionFinder()
      .setOrigin(HOME_LOCATION)
      .setDestination(location)
      .setMode(Maps.DirectionFinder.Mode.TRANSIT)
      .getDirections();

    // 3. No route found — skip rather than guess.
    if (!directions.routes.length) {
      Logger.log('No route to: ' + location);
      continue;
    }

    // 4. Convert the journey duration (seconds) to milliseconds.
    const travelMs = directions.routes[0].legs[0].duration.value * 1000;

    // 5. Create a travel event ending exactly when the meeting starts.
    const meetingStart = event.getStartTime();
    calendar.createEvent(
      'Travel to ' + location,
      new Date(meetingStart.getTime() - travelMs),
      meetingStart
    );

    // 6. Tag the meeting so a future run never double-buffers it.
    event.setTag(BUFFER_TAG, 'yes');
    added++;
  }

  Logger.log('Added ' + added + ' travel buffer(s).');
}

How it works

  1. addTravelBuffers works out a window from now to seven days ahead (SCAN_DAYS) and reads every event in it from the default calendar.
  2. If there are no events it logs a message and stops.
  3. For each event it reads the location field. Events with no location are treated as in-studio and skipped, as are events already carrying the travel-buffered tag.
  4. It asks the Maps service for a transit route from the studio (HOME_LOCATION) to the event’s location.
  5. If Maps returns no route it logs the location and moves on — better a missing buffer than a made-up one.
  6. It reads the journey duration, which Maps gives in seconds, and converts it to milliseconds.
  7. It creates a “Travel to…” event that ends exactly when the meeting starts and begins one journey-length earlier.
  8. Finally it tags the meeting with travel-buffered, so the next run sees the tag and skips it — no duplicate buffers.

Example run

A meeting on the calendar:

EventLocationStart
Client review — AcmeAcme HQ, Canary WharfTue 10:00

After a run, a new event sits in front of it:

EventStartEnd
Travel to Acme HQ, Canary WharfTue 09:25Tue 10:00
Client review — AcmeTue 10:00Tue 11:00

The 35-minute transit journey is now a visible block. The 09:25–10:00 slot shows as busy, so nobody books over the commute.

Trigger it

Run this on a schedule so new offsite meetings get buffered automatically:

  1. In the Apps Script editor, open Triggers (the clock icon).
  2. Click Add trigger.
  3. Choose the addTravelBuffers function, a Time-driven source, and a Day timer running early each morning.
  4. Save and approve the authorisation prompt.

A daily run catches anything booked the day before, well ahead of time.

Watch out for

  • The script only measures the trip to the meeting. A return-journey buffer would need a second event created after the meeting ends.
  • It assumes you always start from the studio. Back-to-back offsites mean the real origin is the previous location, not HOME_LOCATION — this script does not chain journeys.
  • Transit times shift with timetables and disruption. The buffer reflects the estimate at the moment of the run, not live conditions on the day.
  • Deleting a meeting does not delete its travel event. The buffer is a separate event and will linger on the calendar.
  • The Maps service has a daily directions quota. A week of offsite-heavy days uses one lookup per event — fine normally, but worth knowing on a busy calendar.
  • getTag and setTag store data on the event itself. If an event is recreated or copied, it loses the travel-buffered tag and may be buffered again.

Related