Skip to content

Instantly share code, notes, and snippets.

@ewerybody
Last active July 29, 2026 14:29
Show Gist options
  • Select an option

  • Save ewerybody/73e8f11f8073e44e79ae6b333edb6c1a to your computer and use it in GitHub Desktop.

Select an option

Save ewerybody/73e8f11f8073e44e79ae6b333edb6c1a to your computer and use it in GitHub Desktop.
selectiveCalendarSync - Google App Script
function selectiveCalendarSync() {
const startTime = Date.now();
// Number of days to include in the sync:
const NUM_DAYS = 14;
// Lower case exclude trigger strings to keep matching events from being synced:
const EXCLUDE = ["focus time", "home", "bob ooo", "alice pto"];
// Universal name for synced events? Make it empty to use original event name:
const NEW_NAME = "";
// Also sync whole day events without start and end time?:
const FULL_DAY_EVENTS = false;
// Log more or less?
const VERBOSE = true;
const now = new Date();
const end = new Date();
end.setDate(now.getDate() + NUM_DAYS);
// Fill with `Calendar ID` from "Integrate calendar" Settings section of your TARGET calendar:
const targetCal = getCalendar(
"1234567890...abcdefghijklmno@group.calendar.google.com",
"target",
);
if (!targetCal) {
return;
}
// Fill with `Calendar ID` from "Integrate calendar" Settings section of your SOURCE calendar:
// Or keep empty to use your DEFAULT calendar!
const sourceCal = getCalendar("", "source");
if (!sourceCal) {
return;
}
const targetEvents = targetCal.getEvents(now, end);
const targetMap = {};
targetEvents.forEach((event) => {
targetMap[makeEventKey(event)] = event;
});
const sourceEvents = sourceCal.getEvents(now, end);
const keysToSync = new Set();
if (VERBOSE) Logger.log(`Running selectiveCalendarSync over ${sourceEvents.length} source events from:\n ${now} to\n ${end} ...`);
const myMailAddress = Session.getActiveUser().getEmail();
sourceEvents.forEach((event) => {
if (skipEvent(event, EXCLUDE, FULL_DAY_EVENTS, VERBOSE, myMailAddress)) {
return;
}
const key = makeEventKey(event);
keysToSync.add(key);
const title = event.getTitle();
const newTitle = NEW_NAME || title;
const date = getEventDateStr(event);
if (!targetMap[key]) {
if (event.isAllDayEvent()) {
targetCal.createAllDayEvent(newTitle, event.getAllDayStartDate());
} else {
targetCal.createEvent(
newTitle,
event.getStartTime(),
event.getEndTime(),
);
}
if (VERBOSE) Logger.log(`${date} Synced event: "${title}"`);
} else {
if (VERBOSE) Logger.log(`${date} Already synced: "${title}"`);
}
});
targetEvents.forEach((event) => {
const key = makeEventKey(event);
if (!keysToSync.has(key)) {
event.deleteEvent();
if (VERBOSE) {
Logger.log(`${date} Removed stale event: ${event.getTitle()}`);
}
}
});
const durationSec = ((Date.now() - startTime) / 1000).toFixed(2);
Logger.log(`Sync finished in ${durationSec} seconds.`);
}
// Create simple 'key' to identify events from start and end time.
// Excluding title is not safe to overbooking with identical times but
// safe for our renaming and good enough for now.
function makeEventKey(event) {
return `${event.getStartTime().getTime()}_${event.getEndTime().getTime()}`;
}
// all skipping rules in one place with optional logging
function skipEvent(event, excludeKeywords, fullDays, verbose, myMailAddress) {
const title = event.getTitle();
const date = getEventDateStr(event);
if (!fullDays && event.isAllDayEvent()) {
if (verbose) Logger.log(`${date} Skipping: "${title}" is full day!`);
return true;
}
// Skip based on event status. There is OWNER, YES, NO, INVITED, MAYBE
const status = event.getMyStatus();
if (status === CalendarApp.GuestStatus.OWNER) {
const selfGuest = event.getGuestByEmail(myMailAddress);
if (selfGuest && selfGuest.getGuestStatus() === CalendarApp.GuestStatus.NO) {
if (verbose) Logger.log(`${date} Skipping: "${title}" status OWNER, but declined!`);
return true;
}
} else if (status !== CalendarApp.GuestStatus.YES) {
if (verbose) Logger.log(`${date} Skipping: "${title}" status: ${status}`);
return true;
}
const titleLower = title.toLowerCase();
const matchedKeyword = excludeKeywords.find((kw) => titleLower.includes(kw));
if (matchedKeyword) {
if (verbose) Logger.log(`${date} Skipping: "${title}" matching keyword ${matchedKeyword}`);
return true;
}
return false;
}
function getCalendar(calendarId, name) {
const calObject = calendarId
? CalendarApp.getCalendarById(calendarId)
: CalendarApp.getDefaultCalendar();
if (!calObject) {
Logger.log(`ERROR: Could not find ${name} calendar by ID "${calendarId}"`);
}
return calObject;
}
function getEventDateStr(event) {
const date = event.isAllDayEvent() ? event.getAllDayStartDate() : event.getStartTime();
return Utilities.formatDate(date, Session.getScriptTimeZone(), "MMM d");
}
@ewerybody

Copy link
Copy Markdown
Author
  • fixed OWN events that are declined.
  • added ${date} to logging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment