Serverless iCal Parsing

Today's post isn't necessarily too interesting code-wise, but it touches upon some greater, more broad, serverless topics that I'd like to bring up. A few weeks ago I discovered an interesting GitHub repository: https://github.com/gadael/icsdb.

This repository contains iCal files (think plain text calendar data) for non-working days for all 50 US states and various European countries. This could be useful in a number of ways, if, of course, you can parse the iCal data. I thought it might be interesting to build a simple service that would take a URL pointing to iCal data and return the information in JSON form.

There are multiple different ways of parsing iCal, but I felt ical.js by Mozilla was good enough. The ical.js library is pretty complex, letting you work with iCal data like a component, calling different methods to find data, but it can also just return a simple parsed version of the text. Here is the code I came up with:

const rp = require('request-promise');
const ical = require('ical.js');

function flattenEvent(e) {
    let event = {};
    for(let i=0;i<e[1].length;i++) {
        let prop = e[1][i];
        event[prop[0]] = prop[3];
        //console.log('e',prop);
    } 
    return event;
}

exports.main = (args) => {

    return new Promise((resolve, reject) => {
        rp(args.url).then((txt) => {
            try {
                let parsed = ical.parse(txt);
                let events = parsed[2];

                let result = [];
                events.forEach(e => result.push(flattenEvent(e)));
                resolve({events:result});
            } catch(e) {
                console.log(e);
                reject(e.message);
            }
        })
        .catch((e) => {
            reject(e);  
        });
    });
}

Basically - suck down the remote URL and parse using the library. As I said, there is a nice "object based" API that the library provides, but I found I could work with the initial data a bit easier. You can see where I just grab the third item in the array to get the actual events. I then "flatten" the data using flattenEvent. If your curious, the data is in a form called jCal, which has a specification: https://tools.ietf.org/html/draft-ietf-jcardcal-jcal-10. My flattenEvent function makes some assumptions that may not necessarily always work out well, but so far it's done ok.

I tested it by asking for Louisiana holidays:

wsk action invoke ical/get --param url 
https://raw.githubusercontent.com/gadael/icsdb/master/build/en-US/us-louisiana-nonworkingdays.ics
-r

And here are the top five results.


{
    "class": "PUBLIC",
    "created": "2014-01-09T00:47:56Z",
    "description": "",
    "dtend": "1970-01-02",
    "dtstamp": "2017-06-23T13:53:48Z",
    "dtstart": "1970-01-01",
    "last-modified": "2017-06-23T13:53:48Z",
    "rrule": {
        "freq": "YEARLY"
    },
    "sequence": 0,
    "status": "CONFIRMED",
    "summary": "New Year's Day",
    "transp": "TRANSPARENT",
    "uid": "b901ca08-d924-43c3-9166-1d215c9453d6"
},
{
    "class": "PUBLIC",
    "created": "2014-01-09T00:47:56Z",
    "description": "",
    "dtend": "1983-01-02",
    "dtstamp": "2017-06-23T13:53:48Z",
    "dtstart": "1983-01-01",
    "last-modified": "2017-06-23T13:53:48Z",
    "rrule": {
        "byday": "3MO",
        "freq": "YEARLY"
    },
    "sequence": 0,
    "status": "CONFIRMED",
    "summary": "Marthin Luther King day/Robert E. Lee day",
    "transp": "TRANSPARENT",
    "uid": "0ae8128a-e360-492c-b2bd-52ed0d6d06fd"
},
{
    "categories": "-New Mexico",
    "class": "PUBLIC",
    "created": "2014-01-09T00:47:56Z",
    "description": "",
    "dtend": "1970-02-01",
    "dtstamp": "2017-06-23T13:53:48Z",
    "dtstart": "1970-02-01",
    "last-modified": "2017-06-23T13:53:48Z",
    "rrule": {
        "byday": "3MO",
        "freq": "YEARLY"
    },
    "sequence": 0,
    "status": "CONFIRMED",
    "summary": "Presidents Day",
    "transp": "TRANSPARENT",
    "uid": "17425d41-9ed3-4088-adad-4693d1bd44c9"
},
{
    "categories": "Louisiana",
    "class": "PUBLIC",
    "created": "2014-01-09T00:47:56Z",
    "description": "",
    "dtend": "1970-04-02",
    "dtstamp": "2017-06-23T13:53:48Z",
    "dtstart": "1970-04-01",
    "last-modified": "2017-06-23T13:53:48Z",
    "rdate": "1970-02-10",
    "sequence": 0,
    "status": "CONFIRMED",
    "summary": "Mardi gras",
    "transp": "TRANSPARENT",
    "uid": "7ac45e93-a684-4061-a0c7-948a89e358b0"
},
{
    "categories": "Connecticut",
    "class": "PUBLIC",
    "created": "2014-01-09T00:47:56Z",
    "description": "",
    "dtend": "1970-04-09",
    "dtstamp": "2017-06-23T13:53:48Z",
    "dtstart": "1970-04-08",
    "last-modified": "2017-06-23T13:53:48Z",
    "rdate": "1970-03-26",
    "sequence": 0,
    "status": "CONFIRMED",
    "summary": "Good Friday",
    "transp": "TRANSPARENT",
    "uid": "3c46243f-00f8-418f-94cf-4eda72ae7cb2"
}

So... that's some data. Cool. What would I do next? When I first started thinking about this data, my first thought is that it could be a useful API for web apps. I'm a web developer so everything looks like a web-related source for me. Of course, why would I need serverless for that? I could run the iCal library in the browser - the only issue I'd run into is CORS - maybe. And I could always copy the iCal files to the same server as the app. But that's really limited thinking.

By creating this as a serverless action, I've opened it up to any event source, web or not. How about an example of something completely non-web based?

Imagine a support service where every day, my process gets a list of employees responsible for IT work in case of emergencies. I could imagine a process by which after getting a list of employees, I could check each one's home state against a list of holidays for that state and determine if that person is off today. It's the exact same code, but I'm simply using it in a different context.

And this is something important to keep in mind here. Serverless isn't just about building NodeJS apps simpler. Instead I'm creating a resource that can be used in multiple situations, both direct (web app making a request) and indirect (a process run on a timed schedule).

This is something I've talked about before, but I feel like it's something that's possibly not quite as evident.

p.s. You can find the source code for this demo here: https://github.com/cfjedimaster/Serverless-Examples/blob/master/ical/get.js

Like This?

If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can also subscribe to the email feed to get notified of new posts.

See Also