Skip to content

Instantly share code, notes, and snippets.

@sigmaris
Forked from AndrewSouthpaw/calendarCopy.js
Last active August 2, 2023 12:58
Show Gist options
  • Save sigmaris/af73e1e5401ead499398f10fdc0d70e5 to your computer and use it in GitHub Desktop.
Save sigmaris/af73e1e5401ead499398f10fdc0d70e5 to your computer and use it in GitHub Desktop.
Google Apps script to copy personal events and block those times in your work calendar.
// *******************
// This setup will allow you to synchronize personal events from one calendar (the "secondary calendar") e.g. your Opsgenie
// schedule, to another calendar, e.g. work (the "primary calendar").
//
// 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 are in your Google calendar list, and the primary calendar can be edited
// by the Google account you're currently under (or switch accounts to the owner of the primary calendar)
// To get your Opsgenie schedule into Google Calendar, export your Opsgenie webcal URL: https://support.atlassian.com/opsgenie/docs/view-your-on-call-schedule/#Export-with-manual-URL
// and subscribe to the URL in your Google Calendar: https://calendar.google.com/calendar/u/0/r/settings/addbyurl
// 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. Select "exec" as the function to run. Click [Run] and make sure things sync up correctly.
// You could change DAYS_LOOKAHEAD to a smaller number if you want to test 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", "Hours/Minutes timer", and an interval you want.
// Example: https://i.imgur.com/TMXoAIA.jpg
// 11. [Save] and watch for your calendar to sync!
// *************
// You must fill in these
// *************
// Your calendar ID comes from [Settings] > [Calendar settings]: https://i.imgur.com/sY87aoI.jpg
// The secondary calendar is your Opsgenie 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 = 14;
// What title do your secondary events have in your primary calendar
const BUSY_EVENT_TITLE = "Ops Rotation";
// 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.RED;
// ignore secondary events that end before this time (in 24 hr time)
const IGNORE_SECONDARY_EVENTS_BEFORE = 9
// ignore secondary events that start after this time (in 24 hr time)
const IGNORE_SECONDARY_EVENTS_AFTER = 17
// ignore secondary events over weekends
const IGNORE_SECONDARY_EVENTS_ON_WEEKENDS = false
// *******************
// Below here is code you can look through and tweak if you want, but most of the customization
// should be above.
// *******************
// source: https://gist.github.com/ttrahan/a88febc0538315b05346f4e3b35997f2
// blog: https://chromatichq.com/blog/syncing-your-personal-work-calendars
// original:
// https://medium.com/@willroman/auto-block-time-on-your-work-google-calendar-for-your-personal-events-2a752ae91dab
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);
const secondaryEvents = secondaryCal.getEvents(today, enddate);
const primaryCal = CalendarApp.getCalendarById(PRIMARY_CALENDAR_ID);
const primaryEvents = primaryCal.getEvents(today, enddate); // all primary calendar events
const primaryEventTitle = BUSY_EVENT_TITLE;
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() === primaryEventTitle) { primaryEventsFiltered.push(pEvent); }
}
// process all events in secondary calendar
for (let sev in secondaryEvents) {
let canSkip = false;
evi = secondaryEvents[sev];
let primaryDescription = evi.getTitle() + '\n\n' + evi.getDescription();
// 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();
if (isSameStart && isSameEnd) {
canSkip = true;
// If you wanted to copy over more info, this would be the place to re-copy updates
pEvent.setDescription(primaryDescription);
// etc...
primaryEventsUpdated.push(pEvent.getId());
}
}
if (canSkip) continue;
if (shouldIgnore(evi)) {
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 notified about meaningless "Busy" events.
const event = {
summary: primaryEventTitle,
description: primaryDescription,
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);
}
// You can update the conditions where you do not copy secondary events over as "Busy"
function shouldIgnore(event) {
// Do nothing if the event is an all-day or multi-day event. This script only syncs hour-based events
if (event.isAllDayEvent()) {
return true;
}
// skip events that end by 9 AM
if (event.getEndTime().getHours() <= IGNORE_SECONDARY_EVENTS_BEFORE) {
return true;
}
// skip events that start after 5pm
if (event.getStartTime().getHours() >= IGNORE_SECONDARY_EVENTS_AFTER) {
return true;
}
if (IGNORE_SECONDARY_EVENTS_ON_WEEKENDS) {
const date = event.getStartTime();
const dayNum = date.getDay();
if (dayNum === 0 || dayNum === 6) {
return true;
}
}
return false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment