Skip to content

Instantly share code, notes, and snippets.

@maxsam4
Last active March 24, 2023 19:27
Show Gist options
  • Save maxsam4/d9424d33fb582c47df75d2a9a3ed8851 to your computer and use it in GitHub Desktop.
Save maxsam4/d9424d33fb582c47df75d2a9a3ed8851 to your computer and use it in GitHub Desktop.
Sync multiple google calendars
// *******************
// This setup will allow you to synchronize personal events from one calendar (the "secondary calendar")
// to another calendar, e.g. work (the "primary calendar"), but obfuscate the details. Then your coworkers
// know when you're busy but don't get to see the personal details.
//
// Follow these steps:
// 1. Go to https://script.google.com/home and click [+ New project]
// 2. Make sure the two calendars you want to sync can be edited by the Google account you're currently under
// (or switch accounts)
// 3. Click the title and give it a name like "Calendar sync"
// 4. Copy this code and paste it in to the editor
// 5. Update the values below 👇
// 6. Click [+] next to [Services], and choose [Google Calendar API], then [Add]
// Example: https://i.imgur.com/xlrAj39.jpg
// 7. Click [Run] and make sure things sync up correctly. You could change DAYS_LOOKAHEAD to
// a smaller number if you want to dry run with a smaller set of events.
// You will be prompted to [Review permissions], do so and authorize the app to access your
// Google account.
// You'll have to go through an awkward warning about "Google hasn't verified the app"
// https://i.imgur.com/EH5OBqR.jpg
// Then choose [Allow]: https://i.imgur.com/jyz6XEx.jpg
// 8. Once behavior looks good, set the other customization values to what you want.
// 9. Save, and then click on [Triggers] (looks like an alarm clock): https://i.imgur.com/jEa4HA0.jpg
// 10. Click [+ Add Trigger], choose "exec", "From Calendar", "Calendar updated",
// and enter your secondary (personal) calendar email.
// 11. [Save] and watch your calendar sync!
// *************
// You must fill in these
// *************
// Your calendar ID comes from [Settings] > [Calendar settings]: https://i.imgur.com/sY87aoI.jpg
// secondary calendar is your "personal" calendar or one you'll copy events from
const SECONDARY_CALENDAR_ID = "[email protected]";
// primary calendar is your "work" calendar where secondary events will be copied over as "Busy" slots
const PRIMARY_CALENDAR_ID = "[email protected]";
// *******************
// These you can customize if you want
// *******************
// How many days ahead do you want to block off time in your primary calendar
const DAYS_LOOKAHEAD = 28;
// What title tag do your secondary events have in your primary calendar
const BUSY_EVENT_TITLE_TAG = "[HI] ";
// Override your usual primary calendar event color for copied Busy events.
// From https://developers.google.com/apps-script/reference/calendar/event-color.html
// If you don't want to override, comment out the place this constant is used.
const BUSY_EVENT_COLOR_ID = CalendarApp.EventColor.GRAY;
// *******************
// Below here is code you can look through and tweak if you want, but most of the customization
// should be above.
// *******************
function exec() {
const today = new Date();
const enddate = new Date();
enddate.setDate(today.getDate() + DAYS_LOOKAHEAD); // how many days in advance to monitor and block off time
const secondaryCal = CalendarApp.getCalendarById(SECONDARY_CALENDAR_ID);
// Filter events where secondary calendar was invited but declined
const secondaryEvents = secondaryCal.getEvents(today, enddate).filter(
evi => evi.getGuestByEmail(SECONDARY_CALENDAR_ID) == null || evi.getGuestByEmail(SECONDARY_CALENDAR_ID).getGuestStatus() != "NO"
);
const primaryCal = CalendarApp.getCalendarById(PRIMARY_CALENDAR_ID);
const primaryEvents = primaryCal.getEvents(today, enddate); // all primary calendar events
let evi, existingEvent;
const primaryEventsFiltered = []; // primary events that were previously created from secondary
const primaryEventsUpdated = []; // primary events that were updated from secondary calendar
const primaryEventsCreated = []; // primary events that were created from secondary calendar
const primaryEventsDeleted = []; // primary events previously created that have been deleted from secondary
Logger.log("Number of primaryEvents: " + primaryEvents.length);
Logger.log("Number of secondaryEvents: " + secondaryEvents.length);
// create filtered list of existing primary calendar events that were previously created from the secondary calendar
for (let pev in primaryEvents) {
const pEvent = primaryEvents[pev];
if (pEvent.getTitle().startsWith(BUSY_EVENT_TITLE_TAG)) { primaryEventsFiltered.push(pEvent); }
}
// process all events in secondary calendar
for (let sev in secondaryEvents) {
let canSkip = false;
evi = secondaryEvents[sev];
// if the secondary event has already been blocked in the primary calendar, update it
for (existingEvent in primaryEventsFiltered) {
const pEvent = primaryEventsFiltered[existingEvent];
const isSameStart = pEvent.getStartTime().getTime() === evi.getStartTime().getTime();
const isSameEnd = pEvent.getEndTime().getTime() === evi.getEndTime().getTime();
const isSameTitle = pEvent.getTitle() === BUSY_EVENT_TITLE_TAG + evi.getTitle();
if (isSameStart && isSameEnd && isSameTitle) {
canSkip = true;
pEvent.setDescription(evi.getDescription());
primaryEventsUpdated.push(pEvent.getId());
}
}
if (canSkip) continue;
// if the secondary event does not exist in the primary calendar, create it
// we use the Calendar API (instead of the CalendarApp given to us) because it allows us to specify not using
// default reminders, so we aren't double notified about copied events.
const event = {
summary: BUSY_EVENT_TITLE_TAG + evi.getTitle(),
description: evi.getDescription(),
visibility: 'private',
start: {
dateTime: evi.getStartTime().toISOString(),
},
end: {
dateTime: evi.getEndTime().toISOString(),
},
colorId: BUSY_EVENT_COLOR_ID,
reminders: {
useDefault: false,
},
};
const newEvent = Calendar.Events.insert(event, PRIMARY_CALENDAR_ID);
primaryEventsCreated.push(newEvent.id);
Logger.log("PRIMARY EVENT CREATED", newEvent);
}
// if a primary event previously created no longer exists in the secondary calendar, delete it
for (pev in primaryEventsFiltered) {
const pevIsUpdatedIndex = primaryEventsUpdated.indexOf(primaryEventsFiltered[pev].getId());
if (pevIsUpdatedIndex === -1) {
const pevIdToDelete = primaryEventsFiltered[pev].getId();
Logger.log(pevIdToDelete + " deleted");
primaryEventsDeleted.push(pevIdToDelete);
primaryEventsFiltered[pev].deleteEvent();
}
}
Logger.log("Primary events previously created: " + primaryEventsFiltered.length);
Logger.log("Primary events no change: " + primaryEventsUpdated.length);
Logger.log("Primary events deleted: " + primaryEventsDeleted.length);
Logger.log("Primary events created: " + primaryEventsCreated.length);
}
// source: https://gist.github.com/AndrewSouthpaw/5ef5798d46a4ee159e05ef5a477b542d
// Modified to privately copy details and not copy declined events.
// caveat: The workspace admins can see private event details as well.
// If you want complete privacy, you can modify the script to not copy the event details,
// but just create a new "Busy" event. Look at the original source for how to do it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment