'How to sync a calendar using Google Application Script?

I want to sync two Google Calendars automatically whenever there is an update to one of them. Note that these two calendars are NOT identical. I thought setting up a trigger triggered by calendar would be enough. But Google documentation says

These triggers do not tell you which event changed or how it changed. Instead, they indicate that your code needs to do an incremental sync operation to pick up recent changes to the calendar.

so I found an implementation of full and incremental sync from Google

I used functions logSyncedEvents() and getRelativeDate() from above link without any change. Then I created two functions

function fullSync(){
  var calendarId = 'myId'
  
  logSyncedEvents(calendarId, true)

}

function incrementalSync(){
  var calendarId = 'myID'
 
  logSyncedEvents(calendarId, false)

}

/**
 * Retrieve and log events from the given calendar that have been modified
 * since the last sync. If the sync token is missing or invalid, log all
 * events from up to a month ago (a full sync).
 *
 * @param {string} calendarId The ID of the calender to retrieve events from.
 * @param {boolean} fullSync If true, throw out any existing sync token and
 *        perform a full sync; if false, use the existing sync token if possible.
 */
function logSyncedEvents(calendarId, fullSync) {
  var properties = PropertiesService.getUserProperties();
  var options = {
    maxResults: 100
  };
  var syncToken = properties.getProperty('syncToken');
  if (syncToken && !fullSync) {
    options.syncToken = syncToken;
  } else {
    // Sync events up to thirty days in the past.
    options.timeMin = getRelativeDate(-30, 0).toISOString();
  }

  // Retrieve events one page at a time.
  var events;
  var pageToken;
  do {
    try {
      options.pageToken = pageToken;
      events = Calendar.Events.list(calendarId, options);
    } catch (e) {
      // Check to see if the sync token was invalidated by the server;
      // if so, perform a full sync instead.
      if (e.message === 'Sync token is no longer valid, a full sync is required.') {
        properties.deleteProperty('syncToken');
        logSyncedEvents(calendarId, true);
        return;
      } else {
        throw new Error(e.message);
      }
    }

    if (events.items && events.items.length > 0) {
      for (var i = 0; i < events.items.length; i++) {
        var event = events.items[i];
        if (event.status === 'cancelled') {
          console.log('Event id %s was cancelled.', event.id);
        } else if (event.start.date) {
          // All-day event.
          var start = new Date(event.start.date);
          console.log('%s (%s)', event.summary, start.toLocaleDateString());
        } else {
          // Events that don't last all day; they have defined start times.
          var start = new Date(event.start.dateTime);
          console.log('%s (%s)', event.summary, start.toLocaleString());
        }
      }
    } else {
      console.log('No events found.');
    }

    pageToken = events.nextPageToken;
  } while (pageToken);

  properties.setProperty('syncToken', events.nextSyncToken);
}

then I ran fullSync() manually and it found all events. Then I created new event for today and ran incrementalSync() and was expecting the new event to be picked up by the script. But I got No events found.

Could someone help me to make incremental sync work?



Solution 1:[1]

I have an old manual script that can check secondary calendar events created and sync it to the primary.

I managed to add a trigger to it that any changes being performed to the secondary calendar would call the function that creates the event over the primary.

Feel free to test it and if this can give you an insight on how to gather the information of the secondary calendar and update the primary.

Notes:

  1. It is designed to only work with hour based events
  2. It also skips weekends, you can edit it or remove it
  3. The script creates the event over the primary calendar but with a default name (variable), you might have to edit it or implement a way to gather the same event name.
function callback() {

  var id="[email protected]"; // id of the secondary calendar to pull events from

  var today=new Date();
  var enddate=new Date();
  enddate.setDate(today.getDate()+7); // how many days in advance to monitor and block off time
  
  var secondaryCal=CalendarApp.getCalendarById(id);
  var secondaryEvents=secondaryCal.getEvents(today,enddate);
  
  var primaryCal=CalendarApp.getDefaultCalendar();
  var primaryEvents=primaryCal.getEvents(today,enddate); // all primary calendar events
  
  var primaryEventTitle="Testing the creation of the primary calendar"; // update this to the text you'd like to appear in the new events created in primary calendar
  
  var stat=1;
  var evi, existingEvent; 
  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
  var primaryEventsDeleted = []; // to contain primary calendar events previously created that have been deleted from secondary calendar

  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 (pev in primaryEvents)
  {
    var pEvent = primaryEvents[pev];
    if (pEvent.getTitle() == primaryEventTitle)
    { primaryEventsFiltered.push(pEvent); }
  }
  
  // process all events in secondary calendar
  for (sev in secondaryEvents)
  {
    stat=1;
    evi=secondaryEvents[sev];
    
    // if the secondary event has already been blocked in the primary calendar, update it
    for (existingEvent in primaryEventsFiltered)
      {
        var pEvent = primaryEventsFiltered[existingEvent];
        var secondaryTitle = evi.getTitle();
        var secondaryDesc = evi.getDescription();
        if ((pEvent.getStartTime().getTime()==evi.getStartTime().getTime()) && (pEvent.getEndTime().getTime()==evi.getEndTime().getTime()))
        {
          stat=0;
          pEvent.setTitle(primaryEventTitle);
          pEvent.setDescription(secondaryTitle + '\n\n' + secondaryDesc);
          // event.setDescription(evi.getTitle() + '\n\n' + evi.getDescription());
          pEvent.setVisibility(CalendarApp.Visibility.PRIVATE); // set blocked time as private appointments in work calendar
          primaryEventsUpdated.push(pEvent.getId());
          Logger.log('PRIMARY EVENT UPDATED'
                     + '\nprimaryId: ' + pEvent.getId() + ' \nprimaryTitle: ' + pEvent.getTitle() + ' \nprimaryDesc: ' + pEvent.getDescription());
        } 
      }

    if (stat==0) continue;    
    
    var d = evi.getStartTime();
    var n = d.getDay();

    if (evi.isAllDayEvent())
    {
      continue; // Do nothing if the event is an all-day or multi-day event. This script only syncs hour-based events
    }
    else if (n==1 || n==2 || n==3 || n==4 || n==5) // skip weekends. Delete this if you want to include weekends
    // if the secondary event does not exist in the primary calendar, create it
    {
      var newEvent = primaryCal.createEvent(primaryEventTitle,evi.getStartTime(),evi.getEndTime()); // change the Booked text to whatever you would like your merged event titles to be
      // alternative version below that copies the exact secondary event information into the primary calendar event
      // var newEvent = primaryCal.createEvent(evi.getTitle(),evi.getStartTime(),evi.getEndTime(), {location: evi.getLocation(), description: evi.getDescription()});  
      newEvent.setDescription(evi.getTitle() + '\n\n' + evi.getDescription());
      newEvent.setVisibility(CalendarApp.Visibility.PRIVATE); // set blocked time as private appointments in work calendar
      newEvent.removeAllReminders(); // so you don't get double notifications. Delete this if you want to keep the default reminders for your newly created primary calendar events
      primaryEventsCreated.push(newEvent.getId());
      Logger.log('PRIMARY EVENT CREATED'
                 + '\nprimaryId: ' + newEvent.getId() + '\nprimaryTitle: ' + newEvent.getTitle() + '\nprimaryDesc ' + newEvent.getDescription() + '\n');
    }
  }

  // if a primary event previously created no longer exists in the secondary calendar, delete it
  for (pev in primaryEventsFiltered)
  {
    var pevIsUpdatedIndex = primaryEventsUpdated.indexOf(primaryEventsFiltered[pev].getId());
    if (pevIsUpdatedIndex == -1)
    { 
      var 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 updated: ' + primaryEventsUpdated.length);
  Logger.log('Primary events deleted: ' + primaryEventsDeleted.length);
  Logger.log('Primary events created: ' + primaryEventsCreated.length);

}  


function calendarTrigger() {
  var trigger = ScriptApp.newTrigger('callback')
  .forUserCalendar('[email protected]')
  .onEventUpdated()
  .create();
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Rubén