Google AppScript: Automated OOO Calendar Sync
Instructions and script written by Al. Reviewed, edited, and tested by Christine.
Overview
This script automatically mirrors your personal Out of Office (OOO) events to a shared team calendar so you don't have to manually create duplicate entries.
Key Features
- Auto-Update: If you rename your OOO or change the duration on your personal calendar, the shared entry updates automatically.
- Auto-Cleanup: If you delete your OOO or move the start time, the old shared entry is removed.
- Safe Sync: It only touches events it created using your unique tag.
Limitations & Behavior
To ensure the script runs reliably, please keep the following in mind:
- One-Way Sync only: Changes made directly on the Shared Team Calendar will NOT sync back to your personal calendar. Always edit your OOO on your personal calendar.
- The "Move" Behavior: The script uses your Start Time as the unique anchor. If you drag an event to a different start time, the script will delete the old shared event and create a new one.
- Initial Sync: The script only looks 3 months into the future.
- Event Type: This script specifically looks for the "Out of Office" event type in Google Calendar. Standard "Busy" events are not synced.
Setup Instructions
1. Create the Script
- Go to
script.google.com. - Click New Project and name it "OOO Sync Tool".
- Delete any existing code in the editor and paste the script provided below.
2. Enable the Calendar API (Required)
- On the left sidebar, click the + icon next to Services.
- Find and select Google Calendar API.
- Click Add.
3. Personalize the Script
Edit the variables at the top of the script:
MY_UNIQUE_TAG: Replace 'YOUR_UNIQUE_TAG_HERE' with a unique nickname (e.g., 'sync_jsmith').USER_NAME: Replace 'YOUR_NAME_HERE' with your name (e.g., 'Christine').SHARED_CALENDAR_ID: Replace 'YOUR_SHARED_CALENDAR_ID' with your shared calendar ID.
4. Run & Authorize
- Click Run at the top.
- Review and Allow permissions.
- Check the Executions tab on the left to confirm the sync was successful.
5. Set the Hourly Automator
- Click the Triggers (Clock icon) on the left sidebar -> + Add Trigger.
- Function:
syncOOO| Source:Time-driven| Type:Hour timer| Interval:Every hour.
The Script
(Remember to test it!)
// --- CONFIGURATION
const SHARED_CALENDAR_ID = 'YOUR_SHARED_CALENDAR_ID';
const MY_UNIQUE_TAG = 'YOUR_UNIQUE_TAG_HERE';
const USER_NAME = 'YOUR_NAME_HERE';
function syncOOO() {
const primaryCalId = 'primary';
const now = new Date();
const futureLimit = new Date();
futureLimit.setMonth(now.getMonth() + 3);
console.info("Starting sync for period: " + now.toLocaleDateString() + " to " + futureLimit.toLocaleDateString());
// 1. Fetch OOO entries from your personal calendar
const oooEntries = Calendar.Events.list(primaryCalId, {
timeMin: now.toISOString(),
timeMax: futureLimit.toISOString(),
eventTypes: ['outOfOffice'],
singleEvents: true
}).items || [];
const sharedCal = CalendarApp.getCalendarById(SHARED_CALENDAR_ID);
if (!sharedCal) {
console.error("Could not find Shared Calendar. Check the SHARED_CALENDAR_ID.");
return;
}
const sharedEvents = sharedCal.getEvents(now, futureLimit);
const mySyncedEvents = sharedEvents.filter(e => e.getTag('sync_source') === MY_UNIQUE_TAG);
// 2. UPDATE OR DELETE EXISTING SYNCED EVENTS
mySyncedEvents.forEach(syncedEvent => {
const matchingOOO = oooEntries.find(ooo => {
const oooStart = new Date(ooo.start.dateTime || ooo.start.date).getTime();
return oooStart === syncedEvent.getStartTime().getTime();
});
if (!matchingOOO) {
console.warn("DELETING: " + syncedEvent.getTitle() + " (Event removed or start time changed on personal cal)");
syncedEvent.deleteEvent();
} else {
const newTitle = USER_NAME + " " + matchingOOO.summary;
const newEnd = new Date(matchingOOO.end.dateTime || matchingOOO.end.date);
let changed = false;
if (syncedEvent.getTitle() !== newTitle) {
console.info("UPDATING TITLE: " + syncedEvent.getTitle() + " -> " + newTitle);
syncedEvent.setTitle(newTitle);
changed = true;
}
if (syncedEvent.getEndTime().getTime() !== newEnd.getTime()) {
console.info("UPDATING TIME: Duration changed for " + newTitle);
syncedEvent.setTime(syncedEvent.getStartTime(), newEnd);
changed = true;
}
if (!changed) {
console.log("NO CHANGE: " + newTitle + " is already up to date.");
}
}
});
// 3. CREATE NEW SYNCED EVENTS
oooEntries.forEach(ooo => {
const start = new Date(ooo.start.dateTime || ooo.start.date);
const alreadyExists = mySyncedEvents.some(e => e.getStartTime().getTime() === start.getTime());
if (!alreadyExists) {
const title = USER_NAME + " " + ooo.summary;
const end = new Date(ooo.end.dateTime || ooo.end.date);
const newEvent = sharedCal.createEvent(title, start, end);
newEvent.setTag('sync_source', MY_UNIQUE_TAG);
console.info("CREATED: New event synced -> " + title);
}
});
console.info("Sync process complete.");
}