Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
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")
// 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 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:
// 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"
// Then choose [Allow]:
// 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):
// 10. Click [+ Add Trigger], choose "exec", "Minutes timer", and an interval you want.
// Example:
// 11. [Save] and watch for your calendar to sync!
// *************
// You must fill in these
// *************
// Your calendar ID comes from [Settings] > [Calendar settings]:
// secondary calendar is your "personal" calendar or one you'll copy events from
// primary calendar is your "work" calendar where secondary events will be copied over as "Busy" slots
// *******************
// 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 = "Busy";
// Override your usual primary calendar event color for copied Busy events.
// From
// If you don't want to override, comment out the place this constant is used.
const BUSY_EVENT_COLOR_ID = CalendarApp.EventColor.GRAY;
// ignore secondary events that end before this time (in 24 hr time)
// ignore secondary events that start after this time (in 24 hr time)
// ignore secondary events over weekends
// *******************
// Below here is code you can look through and tweak if you want, but most of the customization
// should be above.
// *******************
// source:
// blog:
// original:
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];
// 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;
// There's probably no carry updates as long as the only thing you're syncing is "Busy". If you wanted to
// copy over more info, this would be the place to re-copy updates
// pEvent.setDescription(secondaryTitle + '\n\n' + secondaryDesc);
// etc...
if (canSkip) continue;
if (shouldIgnore(evi)) {
// 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,
start: {
dateTime: evi.getStartTime().toISOString(),
end: {
dateTime: evi.getEndTime().toISOString(),
reminders: {
useDefault: false,
const newEvent = Calendar.Events.insert(event, PRIMARY_CALENDAR_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");
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;
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