James Cridland

Google Calendar on your website

Google Calendar is marvellous. But you might want to use it on your own website, to show something handy like your own calendar for, say, speaking engagements or something. Sure beats editing HTML. Well, here's how I did it, since a friend called Rick wanted to know and was really impressed at my coding skills couldn't be arsed to write his own.

Configure your Google Calendar
- Find the Google Calendar you want to publish in the 'Calendars' tab (lower left of the Calendar homepage).
- Click the down-arrow next to it, and choose 'share this calendar'.
- Click 'Share all information on this calendar with everyone'.
- Then click 'calendar details' and scroll down to 'calendar address'.
- Copy the Calendar ID which is displayed here.

Now, for your website.
- Get Simplepie, an excellent RSS reader which works well with Google Calendar, unlike Magpie. Get it working on your website: the instructions are pretty simple. Ensure you're using v1.0 - older versions won't work.
- Then, use this magic code:

<?php

//v0.93: Added "make email addresses clickable". Thank you, Bjorn!
//v0.92: Fixed an issue with 'a section of dates' in amendable code. Thank you Kevin!
//v0.91: Nice error message if there are no events to display, requested by Tomas. Thanks!
//v0.90: Feature: clickable links in descriptions (start them http://). Thank you, Adam!
//       Feature: display end times, requested by Lucy. Thanks!
//       Feature: group by date, requested by Lucy. Thanks!
//       Note: I now use PHP5 - while this code should continue working in PHP4, beware!
//       http://james.cridland.net/code


/////////
//Configuration
//

// The 'Calendar ID' code (as shown in your 'calendar settings' page)
if (!isset($gmail)) {$gmail "cridland.net_9nfm5orp01h05bnhtd0j0og5g0@group.calendar.google.com"; }

// Date format you want your details to appear
$dateformat="j F Y"// 10 March 2009 - see http://www.php.net/date for details
$timeformat="g.ia"// 12.15am

//Time offset - if times are appearing too early or too late on your website, change this.
$offset="now"// you can use "+1 hour" here for example

// How you want each thing to display.
// By default, this contains all the bits you can grab. You can put ###DATE### in here too if
// you want to, and disable the 'group by date' below.
$event_display="<P><B>###TITLE###</b> - from ###FROM### until ###UNTIL### (<a href='###LINK###'>add this</a>)<BR>###WHERE### (<a href='###MAPLINK###'>map</a>)<br>###DESCRIPTION###</p>";

// What happens if there's nothing to display
$event_error="<P>There are no events to display.</p>";

// The separate date header is here
$event_dateheader="<P><B>###DATE###</b></P>";
$GroupByDate=true;
// Change the above to 'false' if you don't want to group this by dates,
// but remember to add ###DATE### in the event_display if you do.

// ...and how many you want to display (leave at 999 for everything)
$items_to_show=999;

//Where your simplepie.inc is (mine's in the root for some reason)
require_once($_SERVER['DOCUMENT_ROOT'].'/simplepie.inc');

// Cache location for your XML file
$cache_location=$_SERVER['DOCUMENT_ROOT'].'../cache';

// Change this to 'true' to see lots of fancy debug code
$debug_mode=false;

//
//End of configuration block
/////////

if ($debug_mode) {error_reporting (E_ALL); echo "<P>Debug mode is on.</p>";}

// Make sure that correct version of SimplePie is loaded
if (SIMPLEPIE_VERSION<1) { echo "<P><B>Fatal error</b><BR>You need to be running SimplePie v1.0 or above for this to work.</p>"; die; }

// Form the XML address.
$calendar_xml_address "http://www.google.com/calendar/feeds/".$gmail."/public/full?singleevents=true&max-results=2500&futureevents=true&orderby=starttime";

// If you only want a section of dates - today only, for example, then try the following, which sets a maximum date to return. I've set this for a day from now.
// $calendar_xml_address = "http://www.google.com/calendar/feeds/".$gmail."/public/full?singleevents=true&start-min=".date("c")."&start-max=".date("c",strtotime("+1 day"));

// Set the offset correctly
$offset=(strtotime("now")-strtotime($offset));
if (
$debug_mode) {echo "Offset is ".$offset;}

// Let's create a new SimplePie object
$feed = new SimplePie();

// Set the cache location
$feed->set_cache_location($cache_location);
 
if (
$debug_mode) {
$feed->enable_cache(false);
echo 
"<P>We're going to go and grab <a href='$calendar_xml_address'>this feed</a>.<P>";}

// This is the feed we'll use
$feed->set_feed_url($calendar_xml_address);
 
// Let's turn this off because we're just going to re-sort anyways, and there's no reason to waste CPU doing it twice.
$feed->enable_order_by_date(false);
 
// Initialize the feed so that we can use it.
$feed->init();
 
// Make sure the content is being served out to the browser properly.
$feed->handle_content_type();
 
// We'll use this for re-sorting the items based on the new date.
$temp = array();
 
foreach (
$feed->get_items() as $item) {
 
    
// We want to grab the Google-namespaced <gd:when> tag.
    
$when $item->get_item_tags('http://schemas.google.com/g/2005''when');
 
    
// Now, let's grab the Google-namespaced <gd:where> tag.
    
$gd_where $item->get_item_tags('http://schemas.google.com/g/2005''where');
    
$location $gd_where[0]['attribs']['']['valueString'];
    
//and the status tag too, come to that
    
$gd_status $item->get_item_tags('http://schemas.google.com/g/2005''eventStatus');
    
$status substr$gd_status[0]['attribs']['']['value'], -8);
 
    
$when $item->get_item_tags('http://schemas.google.com/g/2005''when');
    
$startdate $when[0]['attribs']['']['startTime']; 
    
$enddate $when[0]['attribs']['']['endTime']; 
    
$unixstartdate SimplePie_Misc::parse_date($startdate);
        
$unixenddate SimplePie_Misc::parse_date($enddate);
    
$where $item->get_item_tags('http://schemas.google.com/g/2005''where'); 
    
$location $where[0]['attribs']['']['valueString']; 

    
// If there's actually a title here (private events don't have titles) and it's not cancelled...
if (strlen(trim($item->get_title()))>&& $status != "canceled" && strlen(trim($startdate)) > 0) {
        
$temp[] = array('startdate'=>$unixstartdate'enddate'=>$unixenddate'where'=>$location'title'=>$item->get_title(), 'description'=>$item->get_description(), 'link'=>$item->get_link());
        if (
$debug) { echo "Added ".$item->get_title();}
    } 

}

//Sort this 
sort($temp);

if (
$debug) {print_r($temp);}

$items_shown=0
$old_date="";
// Loop through the (now sorted) array, and display what we wanted.
foreach ($temp as $item) {
    
// These are the dates we'll display
    
$gCalDate gmdate($dateformat$item['startdate']-$offset);
    
$gCalStartTime gmdate($timeformat$item['startdate']-$offset);
    
$gCalEndTime gmdate($timeformat$item['enddate']-$offset);

    
//Make any URLs used in the description also clickable: thanks Adam
    
$item['description'] = eregi_replace('(((f|ht){1}tp://)[-a-zA-Z0-9@:%_\+.~#?,&//=]+)','<a href="\\1">\\1</a>'$item['description']);
       
// Make email addresses clickable: thanks, Bjorn
       
$item['description'] =
eregi_replace('([_.0-9a-z-]+@([0-9a-z][0-9a-z-]+\.)+[a-z]{2,3})','<a
href="mailto:\\1">\\1</a>'
$item['description']);

    
// Now, let's run it through some str_replaces, and store it with the date for easy sorting later
    
$temp_event=$event_display;
    
$temp_dateheader=$event_dateheader;
    
$temp_event=str_replace("###TITLE###",$item['title'],$temp_event);
    
$temp_event=str_replace("###DESCRIPTION###",$item['description'],$temp_event);
    
$temp_event=str_replace("###DATE###",$gCalDate,$temp_event);
    
$temp_dateheader=str_replace("###DATE###",$gCalDate,$temp_dateheader);
    
$temp_event=str_replace("###FROM###",$gCalStartTime,$temp_event);
    
$temp_event=str_replace("###UNTIL###",$gCalEndTime,$temp_event);
    
$temp_event=str_replace("###WHERE###",$item['where'],$temp_event);
    
$temp_event=str_replace("###LINK###",$item['link'],$temp_event);
    
$temp_event=str_replace("###MAPLINK###","http://maps.google.com/?q=".urlencode($item['where']),$temp_event);
    
// Accept and translate HTML
    
$temp_event=str_replace("&lt;","<",$temp_event);
    
$temp_event=str_replace("&gt;",">",$temp_event);
    
$temp_event=str_replace("&quot;","\"",$temp_event);

    if ((
$items_to_show>AND $items_shown<$items_to_show)) {
                if (
$GroupByDate) {if ($gCalDate!=$old_date) { echo $temp_dateheader$old_date=$gCalDate;}}
        echo 
$temp_event;
        
$items_shown++;
    }
}

//Nothing displayed. Oh dear.
if ($items_shown==0) { echo $event_error; }

if (
$debug_mode) { echo "<PRE>";
echo 
wordwrap(highlight_string(file_get_contents($calendar_xml_address),true),80);
echo 
"</pre>"; }


?>

Download this code

Other bits of code based on this
Antii came up with something that looks like this; the nifty code is here.
If you're looking for this for your Drupal website, you want the Drupal GCal Events plugin. Thanks, Jeff!

FAQ

I just changed something in my Calendar but this script hasn't picked it up
It won't. Not instantly.
But it will within the hour. (Simplepie's default cache value is one hour: which keeps Google happy, as well as your site visitors).