Skip to content

Instantly share code, notes, and snippets.

@vaurora
Forked from ojwoodford/CalendarSync.js
Last active November 11, 2024 21:13
Show Gist options
  • Save vaurora/880b6edbe7d06fad76db062481170ab0 to your computer and use it in GitHub Desktop.
Save vaurora/880b6edbe7d06fad76db062481170ab0 to your computer and use it in GitHub Desktop.
Sync events from a personal Google calendar to a work Google calendar
// Sync one or more personal calendars to a work calendar - and you can sync the work calendar back to
// your personal calendars too! Put this into a Google Apps Script and set up an hourly trigger.
//
// IMPORTANT: To run the script, you must enable the "Calendar API" under the "Services" menu on the left
// of the Google Apps Script IDE.
//
// Author: Oliver J. Woodford https://gist.github.com/ojwoodford
// Small tweaks: Valerie Aurora
function SyncMyCal() {
var options = {
'targetEventTitle': "Busy (sync'd event)", // What event title do you want ported events to have
'useSourceTitle' : true, // If you want sync'd events to have the same title as the source
'daysahead': 30, // How many days ahead do you want to sync events over
'ignorealldayevents': true, // Do you want to ignore all day events in your "from" calendars
'ignorethesedays': [0,6], // Are there days of the week you don't want to sync? 0 = Sunday
'maxhoursbetweenruns': 1, // How many hours between scheduled runs of the calendar sync (you need to set these up)
'onlybusyevents': true, // Only copy events marked 'busy'
'busyness': 1, // Should events be marked 'busy' by default (0: free, 1: busy, 2: match source event)
'visibility': 0, // Should the events be visible by default (0: private, 1: public, 2: match source event)
'verbose': true, // Do you want to log output (useful for debugging)
}
// The first array below contains the ID strings of the "from" calendar (your personal calendars), and
// The second argument is the ID string of the "to" calendar (your work calendar)
CalendarSync(["XXXXXXXXX", "YYYYYYYYYY"], "ZZZZZZZZ", options)
// Want to sync the calendar the other way? Swap the order. It won't sync events created by this script.
// Add one CalendarSync() call per work calendar that you want to sync to a personal calendar.
CalendarSync(["ZZZZZZZZ",], "XXXXXXXXX", options)
}
// CalendarSync - sync from one or more google calendars to another calendar, with:
// - a generic event name (original name in the description)
// - visibility set to private
// - no reminders set
// Useful if you want to automatically block out personal commitments in your work calendar
// Note, you must have previously shared the "from" calendars with the "to" calendar,
// otherwise this script won't have access
function CalendarSync(fromcals, tocal, options) {
var today=new Date();
var enddate=new Date();
enddate.setDate(today.getDate()+options.daysahead); // how many days in advance to monitor and block off time
var lastupdate=new Date();
lastupdate.setHours(today.getHours()-options.maxhoursbetweenruns); // how long ago was this script last run (at a maximum)
// Calendar to copy events to
var targetEvents=CalendarApp.getCalendarById(tocal).getEvents(today,enddate).filter(e => e.getTag("CalSyncKey") != null); // all target calendar events created by this script
var targetEventIds=targetEvents.map(e => e.getTag("CalSyncKey")) // Original event IDs
// Process source calendars
if (typeof fromcals === 'string' || fromcals instanceof String) {
ProcessSourceCalendar(tocal, fromcals, targetEventIds, today, enddate, lastupdate, options)
} else {
for (const cal of fromcals) {
ProcessSourceCalendar(tocal, cal, targetEventIds, today, enddate, lastupdate, options)
}
}
// If a target event previously created no longer exists in the source calendar, delete it
for (var tev in targetEvents)
{
if (targetEventIds[tev] === "")
continue;
if (options.verbose) { Logger.log('EVENT DELETED: ' + targetEvents[tev].getStartTime() + ' ' + targetEvents[tev].getDescription()); }
targetEvents[tev].deleteEvent();
}
}
function OwnerNotAttending(event, fromcal) {
var guests = event.attendees;
if (typeof guests == 'undefined')
return false;
for (var g of guests)
if (g.email == fromcal)
if (g.responseStatus != "accepted")
return true;
else
return false;
}
function ProcessSourceCalendar(tocal, fromcal, targetEventIds, today, enddate, lastupdate, options) {
// Get all the events in source calendar in the relevant time period
var events;
try {
events = Calendar.Events.list(fromcal, {
timeMin: today.toISOString(),
timeMax: enddate.toISOString(),
singleEvents: true
});
}
catch (err) {
Logger.log('Error calling Calendar.Events.list for calendar ' + fromcal + ': ' + err);
// Failure. Ensure that the calendar ID is correct, that your account has access, and that you've enabled the Calendar API under "Services"
return;
}
if (!events.items || events.items.length === 0) {
return;
}
// Process each event
for (const event of events.items)
{
if (options.ignorealldayevents && event.start.date)
continue; // Do nothing if the event is an all-day event. This script only syncs hour-based events
const startTime = event.start.getDateTime();
const endTime = event.end.getDateTime();
if (!PeriodOverlapsDays(startTime, endTime, options.ignorethesedays)) // Skip events outside of work days
continue;
var available = event.getTransparency();
if (options.onlybusyevents && available) // Skip events marked available
continue;
if (event.extendedProperties && event.extendedProperties.shared && event.extendedProperties.shared.hasOwnProperty("CalSyncKey"))
continue; // Don't copy events created by this script
if (OwnerNotAttending(event, fromcal)) // Skip events the user owns but is not attending
continue;
// Check if the source event has already been blocked in the target calendar
var id = event.getId();
var ind = targetEventIds.indexOf(id);
if (ind != -1) {
// Check if anything changed since the last script run
var lastupdated = new Date(event.getUpdated())
if (lastupdated < lastupdate) {
targetEventIds[ind] = ""
if (options.verbose) { Logger.log('EVENT UNCHANGED: ' + startTime + ' ' + event.getSummary()); }
continue;
}
}
// Create a new event
available = options.busyness == 0 ? true : (options.busyness == 1 ? false : available)
var visible = options.visibility <= 1 ? options.visibility : event.getVisibility();
var title = options.useSourceTitle ? event.getSummary() : options.targetEventTitle;
var newevent = {
"summary": title,
"description": event.getDescription(),
"start": {"dateTime": startTime},
"end": {"dateTime": endTime},
"transparency": available ? "transparent" : "opaque",
"visibility": visible ? "public" : "private",
"extendedProperties": {"shared": {"CalSyncKey": id}}
};
// call method to insert/create new event in provided calandar
Calendar.Events.insert(newevent, tocal);
if (options.verbose) { Logger.log('EVENT CREATED: ' + startTime + ' ' + event.getSummary()); }
}
}
function PeriodOverlapsDays(startDate_, endDate_, ignorethesedays) {
if (ignorethesedays.length == 0)
return true;
var startDate = new Date(startDate_)
var endDate = new Date(endDate_)
var date = startDate.getDate()
var day = startDate.getDay()
while (startDate < endDate) {
if (!ignorethesedays.includes(day))
return true;
date++;
startDate.setDate(date);
day = (day + 1) % 7;
}
return false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment