One of our clients wanted to put a simple calendar of upcoming events on his website. I briefly considered writing a database driven application to do this, before discarding the idea as a little too complicated and costly. Instead, I recommended that he use Google Calendar for entering all of the information – we were already using Google Apps to maintain his e-mail, so it seemed a logical enough step. The plan was that I would then use Google Calendar’s RSS feed to display these events on his website’s homepage. However, we soon hit a snag, which was that the Google Calendar RSS feed lists events in the order in which they are published (that is, are added to the calendar) rather than the order in which they are going to occur. For what we wanted, this was no good.
So, I turned instead to the iCalendar formatted feed, which is usually used for subscribing to a public calendar. This contained all the information I needed, although not in a format I was used to dealing with. I could, of course, have decided to change the code to process the iCalendar feed directly. However, an approach based on the XML of the RSS feed seemed to be much simpler, and fitted with the way the rest of the site was put together. So, I decided to write a few regular expressions to convert the iCalendar format to nice, clean XML.
Technical note and disclaimer: The website was running on a Windows 2003 Server box, so the following is written as a JScript-based scheduled script. I have no doubt that this could all be done in a much more elegant way – one day I’ll get started on that. In the meantime, I hope this is helpful to others.
Before running the regular expressions I modified the file containing the iCalendar data so that each field was on a single line:
// Open the text file
var fso = new ActiveXObject("Scripting.FileSystemObject");
var fsofile = fso.OpenTextFile("d:\\basic.ics",1);
// Set some string variables
var fsotext = "";
var last_line = "";
try {
// While there is still something in the file to read
while (true) {
// Read the next line of the file in
var this_line = fsofile.ReadLine();
// If it doesn't start with a space ...
if (this_line.substring(0,1)!=" ") {
// ... assume that this is the last line of data for this field
last_line += "\n";
}
// otherwise, just add to the variable this_line
else {
this_line = this_line.substring(1,this_line.length);
}
// Add last_line to the fsotext variable that contains the processed text
fsotext += last_line;
last_line = this_line;
}
}
catch(err) {}
// Tidy up with the last bit of data
fsotext += last_line + "\n";
// And close the text file
fsofile.close()
Then I loaded this file into a test variable called fsotext and ran the following regular expressions on it:
// Strip out extraneous \'s fsotext = fsotext.replace(/\\/gi,""); // Convert all BEGIN:X statements to fsotext = fsotext.replace(/BEGIN:(.*)/gi,""); // Convert all END:X statements to fsotext = fsotext.replace(/END:(.*)/gi,""); // Convert all Name:Value pairs to Value fsotext = fsotext.replace(/(.*?)\:(.*)/gi,"$2"); // Convert any & references to & // (this is just to prevent some problems when we convert them back) fsotext = fsotext.replace(/&/gi,"&"); // Remove any additional flags on data // (which we are not using for the purposes of this feed) fsotext = fsotext.replace(/;[^>]*>/gi,">"); // Crude way of handling the RRULE FREQ=YEARLY flag fsotext = fsotext.replace(/>FREQ=YEARLY>/gi," />"); // Some character encoding issues fsotext = fsotext.replace(/œ/gi,"£"); // Convert all & references to XML & fsotext = fsotext.replace(/&/gi,"&"); // Replace all carriage returns with spaces fsotext = fsotext.replace(/\\n/gi," ");
The final step, before converting the XML into a snippet of HTML, was to handle events spanning one or more days, and remove any events that had already happened:
var calendarxml = WScript.CreateObject("Microsoft.XMLDOM");
calendarxml.loadXML(fsotext);
var date_nodes = calendarxml.selectNodes("//DTSTART");
for(var i=0;i<date_nodes.length;i++) {
var n = date_nodes[i];
// If no time details are set, default them to 00:00:00
if (n.text.indexOf("T")==-1) n.text += "T000000";
// Handle the year, month and day
n.text = n.text.replace(/^(....)(..)(..)/,"$1-$2-$3");
// Handle the hours, minutes and seconds
n.text = n.text.replace(/T(..)(..)(..)/,"T$1:$2:$3");
n.text += ".000-00:00";
}
var date_nodes = calendarxml.selectNodes("//DTEND");
for(var i=0;i<date_nodes.length;i++) {
var n = date_nodes[i];
var is_dayrange = false;
if (n.text.indexOf("T")==-1) {
// If no time details are set, default them to 00:00:00
n.text += "T000000";
// and mark the record as being one that spans one or more days
is_dayrange = true;
}
// Handle the year, month and day
n.text = n.text.replace(/^(....)(..)(..)/,"$1-$2-$3");
// Handle the hours, minutes and seconds
n.text = n.text.replace(/T(..)(..)(..)/,"T$1:$2:$3");
// Get the date and time
var dtm = new Date(n.text.replace(/T/," ").replace(/\.000\-00\:00/,"").replace(/\-/gi,"/"));
if (is_dayrange) {
// and set the end date to the previous day (rather than 00:00:00 on the next day)
dtm.setDate(dtm.getDate()-1);
n.text = dateString(dtm,"t");
}
else {
n.text += ".000-00:00";
}
// Remove any events that have already happened
if (dtm<new Date()) {
var p = n.parentNode;
var el = p.parentNode;
el.removeChild(p);
}
}
Then it was just a case of deciding how to display the information and creating an XSL stylesheet to do the job.
I’m now using this method on a couple of sites, and so far it works well. The client gets to use the full Google Calendar interface to maintain their events diary, and – now that I’ve set up a couple of scheduled scripts that run every few hours – I get to sit back and watch the websites update automatically.
Tags: calendar, convert, events, feed, google, icalendar, jscript, rss, xml
9 December 2007 at 6:32 am |
thats really nice…. like that !
24 December 2008 at 8:09 am |
would you be interested in revisiting this project?
i have a xml driven calendar built in Flash and would like to have the data come from Google’s ICAL output.
please contact me directly via email if you’re interested!