-
-
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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ******************* | |
// 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