ProTips: Google calendar one-way sync (from google to application)

In this blog post, I will walk you through my experience of google calendar one-way sync (from google to my application) and will tell you some key points which I understood about google calendar recurring events and normal events.

Google documentation for this is very nice but you need to experiment a lot to understand this so thought of writing it down.

Recently, I was working on one rails backend API only app which has iOS and angular as frontend. In this project one of the feature was syncing user’s google/salesforce/exchange calendar and send push notification as and when meeting is created or updated and that meeting can be single or recurring. Of course, the  user has given permissions to do so 🙂

The obvious question – why not do it in iOS app?

Yes, but we cannot because the user can login using the web app too. And the data on these two need to be in sync. More over its not a plain sync but an intelligent one. We have a SMART calendar that needs some processing.

The first we picked up was Google calendar sync. The requirement was to store meetings of six months. But this window changes every day as we need +3 months from today and -3 months before today.
NOTE: In our case it was only one-way sync. Google to our database.

In any calendar there are two types of events, and we had to sync both these types.
1. Single
2. Recurring. Recurring are a bit complex. If don’t already understand, perhaps it would be good to read documentation.

Also, syncing means:
1. always getting a delta after the last sync (for performance obviously)
2. get created/updated and deleted events.

My initial thought was that syncing single event would be easy but a bit difficult to deal with recurring ones.

I went trough the API Documentation. Google API accepts parameters which are timeMax, timeMin.

We already had the Google auth integrated in our app so it was just a matter of calling the API through the rest client and thought my job is done. So here was my initial api call –

     url = "https://www.googleapis.com/calendar/v3/calendars/primary/events?singleEvents=true&alwaysIncludeEmail=true&timeMax=#{CGI.escape(time_max.to_s)}&timeMin=#{CGI.escape(time_min.to_s)}"

singleEvents true was used to get each instance of recurring events (See documentation).

When I called this API with RestClient I got expected results for created and updated events so I thought now my job is done as I don’t have to deal with recurring events (as I was getting each instance separately). So I wrote code to create/ update meeting in my database based on unique user id and event id. It was very simple but when our client asked for notification of deleted events we faced issue. Why? because when we read documentation again Google calendar gives you deleted events if you pass showDeleted parameter to api but not always meaning, If showDeleted and singleEvents both are True, only single instances of deleted events (but not the underlying recurring events) are returned. And we wanted all occurrences of deleted events.

Then we again read documentation and came across syncToken parameter. When we pass this parameter we get nextSyncToken which can be stored in database and next time while querying if we pass this parameter we get events which are created/ updated/ deleted after last sync time. This was exactly we wanted. Earlier I was fetching 6 month’s event each time and comparing with database for existence and updating based on time i.e. I was wasting API calls + server processing.

Then we decided to change our approach and did following

  • If nextSyncToken is not present in database then fetch 6 months meetings and store nextSyncToken obtained from last call to database
    url = "https://www.googleapis.com/calendar/v3/calendars/primary/events?alwaysIncludeEmail=true&timeMax=#{CGI.escape(time_max.to_s)}&timeMin=#{CGI.escape(time_min.to_s)}"
  • If nextSyncToken is present in database then only fetch updated events and store it in database
    url = "https://www.googleapis.com/calendar/v3/calendars/primary/events?alwaysIncludeEmail=true&syncToken=#{self.calendar_sync_token}"

but if you notice we have not passed singleEvents option to API. Why? Because when I was sending singleEvent true parameter I was getting each instance of recurring meeting. The API returns max 250 events in a single call. So, if there are 2000 occurrences of any recurring event you will have to paginate through all of them (using nextPageToken) to get the nextSyncToken on the last page. It is a no brainer that processing time increased.

Need a smart way to handle it – After some research and reading I figured out, not to pass singleEvents parameter and if we got recurring_event_id back in response fetch the recurring events separately using this API to maintain proper sync.

So here are some key points which I understood about google one-way calendar sync

  • If you don’t want to deal with recurring events your job is very simple just call API and skip recurring events by parsing response.

  • If you have to deal with recurring event but don’t want to deal with deletion of event just pass singleEvent parameter to true. This will increase some calls and server processing but will make your life easy 🙂

  • BUT, When you want to deal with deletion of recurring event don’t pass singleEvent to true. Google will give you recurring meeting as single event with following data in response. Using this you can figure out whether that is recurring event or not.

{ 
  ...
 "recurrence": [
  "RRULE:FREQ=WEEKLY;BYDAY=TU"
  ],
  "id": "f8j5134grukmqufapvn0q0p2bq0"
  ...
}

Now you can also parse “recurrence” tag and then calculate & set recurring schedule in your database. But, there are multiple formats “recurrence” and I did not want to deal with them. Hence, I separately captured recurring events by passing recurring event id to this API and made my life simple.

How to handle modifications to recurring events (a.k.a. “exceptions”)

CASE-1: Modification to Single Event
A] When you update only single event from recurring event google will create a new event from updated event and you will get that in next sync call. The new event will have parent recurring event id and you can use this to do any processing on your server.

{ 
  ...
  "id": "pmu0u4psc1ebq2vun468vpn9ms_201611123103000Z",
  "recurringEventId": "pmu0u4psc1ebq2vun468asdn9ms",
  ...
}

B] When you delete single event from recurring event you gets following response

 {
  "kind": "calendar#event",
  "etag": ""296070510840"",
  "id": "pmu0u4psc1ebq2vun468vpns_20161201T103000Z",
  "status": "cancelled",
  "recurringEventId": "pmu0u4psc1ebq2vun468vps",
  "originalStartTime": {
    "dateTime": "2016-12-01T16:00:00+05:30",
    "timeZone": "Asia/Calcutta"
  }
}

CASE-2: Modification to Multiple Events
A] When you update multiple by using “Change all following meetings”. For e.g. Let’s say you have 1 recurring event with 10 occurrences you update 6th meeting and all it’s following events now in this case google creates 1 more recurring events. It keeps the 1st 5 meetings in first recurring event and will create new recurring event with updated 5 (6 to 10). The sync API will return BOTH. And hence forth for syncing you can treat these as two separate recurring events.

B] When you delete following recurring events from particular event in response you get recurring event id and you can again resync.

I wasted a couple of days to figure all this out. Hopefully this will save someone’s time.

Lesson Learnt: Always read documentation carefully and think of all future cases and design accordingly though the requirements may not have asked for some feature.

One thought on “ProTips: Google calendar one-way sync (from google to application)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s