Import iCal (ics) with fullcalendar?

There is support for iCalendar as an event source as of V 5.5.0.

You can view the docs here: https://fullcalendar.io/docs/icalendar

Example:

import dayGridPlugin from '@fullcalendar/daygrid'
import iCalendarPlugin from '@fullcalendar/icalendar'

var calendar = new Calendar(calendarEl, {
  plugins: [dayGridPlugin, iCalendarPlugin],
  events: {
    url: 'https://mywebsite/icalendar-feed.ics',
    format: 'ics'
  }
})

calendar.render()

what you would need is to write your own extension to fullcalendar (similar to the gcal.js which is provided with fullcalendar) something you could call ical.js

You should know that writting an complete ical parser can be quite draining so you may want to consider sticking with google calendar for your back-end unless you have a compeling reason.

Should you go down the road of developping your own extension for fullcalendar you may want to have a look at an existing jquery ical parser (here - disclaimer: I've never tried this plugin)


I manage to do it. Not as hard as I thought. I use ical.js as ics parser. After parsing, I get a json object which contains all the information in the ics. And then traverse it and construct event object according to the definition of FullCalendar Event object.

Update with FullCalendar v4 and reccuring events

As v4 changed the initialization code and the code bellow didn't count with recurring events, here's one that does and works with the v4 version:

$.get(calendarUrl).then(function (data) {
     // parse the ics data
     var jcalData = ICAL.parse(data.trim());
     var comp = new ICAL.Component(jcalData);
     var eventComps = comp.getAllSubcomponents("vevent");
     // map them to FullCalendar events
     var events = $.map(eventComps, function (item) {

        if (item.getFirstPropertyValue("class") == "PRIVATE") {
            return null;
        }
        else {
            var toreturn = {
                "title": item.getFirstPropertyValue("summary"),
                "location": item.getFirstPropertyValue("location"),
            };
            var rrule=item.getFirstPropertyValue("rrule");
            if(rrule!= null){ //event recurs
                toreturn.rrule={};
                if(rrule.freq) toreturn.rrule.freq=rrule.freq;
                if(rrule.parts.BYDAY) toreturn.rrule.byweekday=rrule.parts.BYDAY;
                if(rrule.until) toreturn.rrule.until=rrule.until.toString();
                if(rrule.until) toreturn.rrule.until=rrule.until.toString();
                if(rrule.interval) toreturn.rrule.interval=rrule.interval;
                var dtstart=item.getFirstPropertyValue("dtstart").toString();
                var dtend=item.getFirstPropertyValue("dtend").toString();
                toreturn.rrule.dtstart=dtstart;
                //count duration ms
                var startdate=new Date(dtstart);
                var enddate=new Date(dtend);
                toreturn.duration = enddate - startdate;
            }else{
                toreturn.start=item.getFirstPropertyValue("dtstart").toString();
                toreturn.end=item.getFirstPropertyValue("dtend").toString();
            }
            return toreturn;
        }
     });
     var calendarEl = document.getElementById('calendar');
     var calendar = new FullCalendar.Calendar(calendarEl, {

              plugins: [ 'interaction','dayGrid','rrule' ],
              defaultView: 'dayGridWeek',
              displayEventEnd: true,
              header: {
                  left: 'prev,next',
                  center: 'title',
                  right: 'dayGridDay,dayGridWeek,dayGridMonth'
              },
              events: events,
              eventRender: function (info) {
                // console.log(info.event);
                // append location
                if (info.event.extendedProps.location != null && info.event.extendedProps.location != "") {
                    info.el.append(info.event.extendedProps.location );
                }
              }
     });
     calendar.render();
});

Original answer's code (v3 and lower):

$.get(calendarUrl).then(function (data) {
// parse the ics data
var jcalData = ICAL.parse(data.trim());
var comp = new ICAL.Component(jcalData);
var eventComps = comp.getAllSubcomponents("vevent");
// console.log(JSON.stringify(eventComps));
// map them to FullCalendar events
var events = $.map(eventComps, function (item) {
    if (item.getFirstPropertyValue("class") == "PRIVATE") {
        return null;
    }
    else {
        return {
            "title": item.getFirstPropertyValue("summary") + ";",
            "start": item.getFirstPropertyValue("dtstart").toJSDate(),
            "end": item.getFirstPropertyValue("dtend").toJSDate(),
            "location": item.getFirstPropertyValue("location")
        };
    }
});

// refresh the control
calendarCtrl.fullCalendar('destroy');
calendarCtrl.fullCalendar({
    events: events,
    timeFormat: "H:mm",
    displayEventEnd: true,
    eventRender: function (event, element) {
        // console.log(element);
        // append location
        if (event.location != null && event.location != "") {
            element.append("<span>" + event.location + "</span>");
        }
    },
    header: {
        left: 'title',
        center: '',
        right: 'today,month,basicWeek,listDay prev,next'
    }
});
});