Skip to content

Instantly share code, notes, and snippets.

@voltechs
Last active January 4, 2021 18:10
Show Gist options
  • Save voltechs/8fc6cdba2a6e19a8425ec40a07b901b6 to your computer and use it in GitHub Desktop.
Save voltechs/8fc6cdba2a6e19a8425ec40a07b901b6 to your computer and use it in GitHub Desktop.
Block time off from your personal calendar on your work calendar.

Calendar Sync

Copyright: Dale Stevens 2018

About:

The script below will pull events from the calendar of your choice, and add them to the primary calendar of the account you employ this script under. There are a couple "knobs" (variables) at the top of the script to assist in tweeking to your liking.

Setup:

  • Go to your Google Drive under your desired account (I recommend your "work" account).
  • Create a new Google Apps Script under "more". (you may need to "enable" this service by going to "connect more apps")
  • Paste the code into script files in the project.
  • Setup triggers to run sync_lock. (Click the "clock" icon)
    • Setup a timed trigger (every 15-30 minutes or so)
    • Setup an event trigger (on update from your personal calendar)
  • Run.
  • You may be prompted to authorize and sign-in to your account. Do this for your primary calendar (the account you're on)

Todo:

function getPrimaryCalendar() {
return CalendarApp.getDefaultCalendar();
}
function getSecondaryCalendar() {
debug(JSON.stringify(PropertiesService.getUserProperties()));
debug(JSON.stringify(PropertiesService.getScriptProperties()));
return CalendarApp.getCalendarById(calendarID());
}
function calendarID()
{
return calendarId; //userProperties.getProperty('SyncCalendarID');
}
function tagId() {
return calendarID(); //userProperties.getProperty('TagID') || userProperties.getProperty('SyncCalendarID')
}
function getTaggedId(event) {
if (props = event.extendedProperties) {
if (props.shared) {
return props.shared[tagId()];
}
}
return null;
}
function getCalendarEvents(calendar, start, end) {
var response = Calendar.Events.list(calendar.getId(), {
timeMin: start.toISOString(),
timeMax: end.toISOString(),
singleEvents: true
});
return response.items;
}
function clearEvents() {
var primaryCalendar = getPrimaryCalendar();
for each (var event in getCalendarEvents(primaryCalendar, start_time, end_time)) {
if ((tagged = getTaggedId(event)) != null) {
Calendar.Events.remove(primaryCalendar.getId(), event.id);
}
}
}
// Experimental, not needed
// https://developers.google.com/apps-script/guides/services/advanced
// https://developers.google.com/apps-script/guides/services/advanced#enabling_advanced_services
function getPrimaryCalendarChangedEvents(start, end) {
var syncToken = userProperties.getProperty('syncToken');
var request_options = {}
if (syncToken && typeof syncToken != 'undefined') {
request_options.syncToken = syncToken;
} else {
// Sync events up to thirty days in the past.
request_options.timeMin = start.toISOString();
}
try {
Logger.log("Requesting with: "+JSON.stringify(request_options));
response = Calendar.Events.list(getPrimaryCalendar().getId(), request_options);
Logger.log("Setting: "+JSON.stringify(response));
userProperties.setProperty('syncToken', response.nextSyncToken);
if (response.items && response.items.length > 0)
{
return response.items;
} else {
return [];
}
} catch (e) {
// Check to see if the sync token was invalidated by the server;
// if so, perform a full sync instead.
userProperties.deleteProperty('syncToken');
Logger.log("Doing full sync: "+e.message+syncToken);
return getPrimaryCalendarChangedEvents(start, end);
}
}
function sync2() {
var events = getPrimaryCalendarChangedEvents(start_time, end_time);
Logger.log("HOW MANY: "+events.length);
}
var calendarId="your.email@some.email.provider.com"; // CHANGE - id of the secondary calendar to pull events from
var days_in_advance = 14; // how many days in advance to monitor and block off time
var days_prior = 7;
var weekdays_only = true;
var color = CalendarApp.EventColor.PALE_RED;
// Set to false, or use 24-hour hours
// Currently need to adjust for daylight savings
var working_hours = {
begin: 6, // 7
end: 19 // 20
}
// Only show confirmed events
var only_confirmed = true;
/* Advanced Settings */
var eventPrefix="Blocked"; // update this to the text you'd like to appear in the new events created in primary calendar
var default_very_private = false;
var sync_lock_seconds = 60;
var logging = true;
var warning = true;
var debuging = false;
function main() {
sync_lock();
}
function copyEventSummary(event) {
if (event.visibility == 'private' || event.visibility == undefined && default_very_private) {
return eventPrefix;//+" (Private)";
} else {
return eventPrefix+" ("+event.summary+")";
}
}
function copyEventDescription(event) {
if (event.visibility == "public" && default_very_private) {
return "";
} else {
return event.description;
}
}
function copyEvent(event) {
var data = {
summary: copyEventSummary(event),
// location: 'The Deli',
description: copyEventDescription(event),
start: {
dateTime: event.start.dateTime,//.toISOString()
date: event.start.date
},
end: {
dateTime: event.end.dateTime,//.toISOString()
date: event.end.date
},
// Red background. Use Calendar.Colors.get() for the full list.
colorId: color,
reminders: {
useDefault: false,
overrides: []
},
extendedProperties: {
shared: {}
},
transparency: event.transparency || "opaque", // Free/Busy — defaults to "opaque" (busy)
visibility: 'private'
};
data.extendedProperties.shared[calendarId] = event.id
debug(JSON.stringify(data));
return data;
}
function createEvent(sEvent, calendar) {
var eventData = copyEvent(sEvent);
return Calendar.Events.insert(eventData, calendar.getId());
}
function updateEvent(pEvent, sEvent) {
var updates = copyEvent(sEvent);
retry(3, function() {
Calendar.Events.patch(updates, primaryCalendar.getId(), pEvent.id);
});
}
/* Globals you really shouldn't touch... */
var start_time=new Date();
var end_time=new Date();
start_time.setDate(start_time.getDate()-days_prior);
end_time.setDate(end_time.getDate()+days_in_advance);
var secondaryCalendar = getSecondaryCalendar();
var primaryCalendar = getPrimaryCalendar();
var userProperties = PropertiesService.getUserProperties();
function is_on_weekday(event) {
// According to google docs, event.start.dateTime
// should be a datetime object. Oh well.
day = new Date(event.start.dateTime).getDay();
return (day > 0 && day < 6);
}
function is_in_working_hours(event) {
// TODO: Replace routine after fetching working hours from calendar
// https://stackoverflow.com/questions/51875481/api-to-get-the-working-hours-data-from-google-calendar-settings
if (working_hours) {
start = new Date(event.start.dateTime).getHours();
return (start >= working_hours.begin && start <= working_hours.end);
} else {
return true;
}
}
function check_confirmed(event) {
if (only_confirmed && event.attendees) {
log(event.attendees.length);
for each (var attendee in event.attendees) {
if (attendee.email == calendarId) {
return attendee.responseStatus == 'accepted' || attendee.responseStatus == 'tentative';
}
}
return false;
} else {
return true;
}
}
function should_create_or_update(event) {
if (weekdays_only && is_on_weekday(event) && is_in_working_hours(event) && check_confirmed(event)) {
return true;
} else {
return false;
}
}
function sync_lock() {
lock(sync_lock_seconds, sync);
}
function sync() {
var secondaryEvents = getCalendarEvents(secondaryCalendar, start_time, end_time);
var primaryEvents = getCalendarEvents(primaryCalendar, start_time, end_time); // all primary calendar events
var primaryEventsFiltered = {}; // to contain primary calendar events that were previously created from secondary calendar
var primaryEventsUpdated = []; // to contain primary calendar events that were updated from secondary calendar
var primaryEventsCreated = []; // to contain primary calendar events that were created from secondary calendar
debug('Number of primaryEvents: ' + primaryEvents.length);
debug('Number of secondaryEvents: ' + secondaryEvents.length);
// create filtered list of existing primary calendar events that were previously created from the secondary calendar
for each (var pEvent in primaryEvents)
{
if ((tid = getTaggedId(pEvent)) != null) {
primaryEventsFiltered[tid] = pEvent;
}
}
// process all events in secondary calendar
for each (var sEvent in secondaryEvents)
{
var pEvent = null;
var sId = sEvent.id;
var eventData = copyEvent(sEvent);
// Skip adding non-blocking (free, versus busy) all-day events
if (sEvent.end.date != null && sEvent.transparency == 'transparent')
{
debug("Nonblocking All Day!: " + sEvent.summary + " => " + sEvent.id + " @ " + sEvent.end.date);
continue;
}
if (should_create_or_update(sEvent)) {
// if the secondary event has already been blocked in the primary calendar, update it
if ((pEvent = primaryEventsFiltered[sId]) != null)
{
delete primaryEventsFiltered[sId];
debug("Updating: " + sEvent.summary + " => " + sEvent.id + " @ " + sEvent.start.dateTime);
updateEvent(pEvent, sEvent);
} else {
debug("Creating: " + sEvent.summary + " => " + sEvent.id + " @ " + sEvent.start.dateTime);
pEvent = createEvent(sEvent, primaryCalendar);
}
}
}
log("Processed Events: " + secondaryEvents.length);
// if a primary event previously created no longer exists in the secondary calendar, delete it
for each (var pEvent in primaryEventsFiltered)
{
debug("Deleting: " + pEvent.summary + " => " + pEvent.id + " @ " + pEvent.start.dateTime);
Calendar.Events.remove(primaryCalendar.getId(), pEvent.id);
}
log("Deleted Events: " + primaryEventsFiltered.length);
}
function log(msg)
{
if (logging)
{
Logger.log(msg);
}
}
function debug(msg)
{
if (debuging)
{
Logger.log(msg);
}
}
function warn(msg)
{
if (warning)
{
Logger.log(msg);
}
}
function retry(max, func) {
for (var n=0; n<=max; n++) {
try {
return func();
} catch(e) {
Utilities.sleep((Math.pow(2,n)*2000) + (Math.round(Math.random() * 2000)));
}
}
}
function lock(timeout, func) {
try {
var lock = LockService.getScriptLock();
if (lock.tryLock(timeout*1000)) {
try {
func();
} finally {
lock.releaseLock();
}
} else {
warn('Could not obtain lock after ' + timeout + ' seconds.');
}
} catch (e) {
warn('Could not obtain lock service.');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment