1. Abstract

My calendar works like this :

I write events in a particular calendar.

I share this calendar with a special ownCloud user, dedicated for this.

The script will login on the CalDAV API with the credential of the special user, and get the content of the shared calendar.

The script compile the events and compile a nice table, with the content of the calendar.

Please note that the events I write are special: I only use the title field to specify a "color" that will be displayed for the day. The colors are corresponding to my « level of availability » (to show my holidays dates, for instance)

2. Create the calendar and the user

In owncloud, create the second user. I called it public, for instance.

In ownCloud, create the shared calendar on your own account, and give public access to it. Do not tick write permissions.

Create a test event, and log out.

Log in back with the public user, and check that you see the shared calendar and its content.

3. Download the library

I used this ICS parser to parse the CalDAV respponses. Download it on your web hosting to the direcory ics-parser/.

4. Install the script

Here is the script I use:

// Require ICS parser library

// Use it to debug, the previously included parser disables the errors
ini_set('display_errors', '1');

// Simple caching system, feel free to change the delay
if (file_exists('calendar.cache.html')) {
    $last_update = filemtime('calendar.cache.html');
} else {
        $last_update = 0;
if ($last_update + 60*60 < time()) {

// Get events

$headers = array(
        'Content-Type: application/xml; charset=utf-8',
        'Depth: 1',
        'Prefer: return-minimal'

// Setup the calendar URL and the credentials here
$calendar_url = 'https://example.com/remote.php/caldav/calendars/public/public_shared_by_YOU';
$calendar_user = 'public';
$calendar_password = 'public_password';

// Prepare request body
$doc  = new DOMDocument('1.0', 'utf-8');
$doc->formatOutput = true;

$query = $doc->createElement('c:calendar-query');
$query->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:c', 'urn:ietf:params:xml:ns:caldav');
$query->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:d', 'DAV:');

$prop = $doc->createElement('d:prop');

$prop = $doc->createElement('c:filter');
$filter = $doc->createElement('c:comp-filter');
$filter->setAttribute('name', 'VCALENDAR');

$body = $doc->saveXML();

// Debugging purpose
//echo '<pre>' . htmlspecialchars($body) . '</pre>';

// Prepare cURL request
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $calendar_url);
curl_setopt($ch, CURLOPT_USERPWD, $calendar_user . ':' . $calendar_password);
curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);

$response = curl_exec($ch);
if (curl_error($ch)) {
    //echo curl_error($ch);

// Debugging purpose
//echo '<pre>' . htmlspecialchars($response) . '</pre>';

// Get the useful part of the response
$xml = simplexml_load_string($response);
$data = $xml->xpath('//cal:calendar-data');

// Debugging purpose
//echo '<pre>' . htmlspecialchars($data[0]) . '</pre>';

// Parse events
$calendar_events = array();
foreach ($data as $vcalendars) {
    $ical = new ICal();
    $vcalendars = $vcalendars->__tostring();

    $lines = explode("\n", $vcalendars);
    $events = $ical->events();

    foreach ($events as $event) {
        $start = $ical->iCalDateToUnixTimestamp($event['DTSTART']);
        $end = $ical->iCalDateToUnixTimestamp($event['DTEND']);
        $summary = $event['SUMMARY'];
        $calendar_events[] = array(
            'start' => $start,
            'end' => $end,
            'summary' => $summary,

// Function to sort the events by start date
function _sort_events($a, $b) {
    return ($a['start'] > $b['start']);

// Sort the events by start date
// By doing so, you can have overlapping events, the newest event will superseed the previous
usort($calendar_events, '_sort_events');

// Function to convert my "special titles" into hex colors
function color2hex($color) {
    $color = strtolower($color);

    switch($color) {
        case 'green':
        return '#00ff00';

        case 'orange':
        return '#ffaa00';

        case 'red':
        return '#ff0000':

        case 'black':
        return '#000000';

        case 'blue':
        return '#0000ff';

// Bufferize output for caching system

// Get current date
$now = mktime(0,0,0);

// Initiate the color array
// Each day in our calendar table will have a color
// Each color is an hex color, corresponding tu the event title (see the switch-case block upper)
$day_color = array();

// Loop through ALL events (old events included)
foreach ($calendar_events as $event) {

    // Discard passed events
    if ($event['end'] < time()) {

    // Calculate the timestamp of the "start" date of the event
    $current_day = mktime(0,0,0, date('n', $event['start']), date('j', $event['start']), date('Y', $event['start']));
    $first_loop = true;
    // Loop to fill the color array from the event start date to the event end date
    for ($d = $current_day; ($first_loop || $d < $event['end']); $d+=(60*60*24)) {

        // Debugging purpose
        //var_dump(date('r', $current_day) . ' ' . date('r', $d) . ' ' . date('r', $event['start']) . ' ' . date('r', $event['end']));

        // Handle overlapping events: stack color at the beginning of the sub-array
        if (isset($day_color[$d]) && is_array($day_color[$d])) {
            array_unshift($day_color[$d], color2hex($event['summary']));
        } else {
            $day_color[$d][0] = color2hex($event['summary']);

        $first_loop = false;

// Debugging purpose

// Output the final table
/ My week starts on Monday, but you can change it

$week_day = date('N', $now);

echo '<table class="forecast">';
echo '<tr><th></th><th>Mon</th><th>Tue</th><th>Wed</th><th>Thu</th><th>Fri</th><th>Sat</th><tH>Sun</th></tr>';

// Loop through the color array
for ($row = 0; $row < 10; $row++) {
    echo '<tr>';

    echo '<td class="week">' . (date('W', $now) + $row) . '</td>';
    for ($col = 0; $col < 7; $col++) {

        // Skip past days from the first week (leave it blank)
        if ($row == 0 && $col < $week_day-1) {
            echo '<td></td>';

        $current_day = $now + ($row)*(7*60*60*24) + ($col-($week_day-1))*(60*60*24);
        $current_day = mktime(0,0,0, date('n', $current_day), date('j', $current_day), date('Y', $current_day)); // Working around daylight saving time
        if (isset($day_color[$current_day]) && isset($day_color[$current_day][0])) {
            // We choose to get the first color of the array,
            //   but if you want to display overlapping events colors,
            //   you can use the other values of the array
            $color = $day_color[$current_day][0];
            echo '<td class="' . $color . '">';
        } else {
            echo '<td>';
        // Debugging purpose
        //echo '<pre>' . $current_day . '</pre>';
        //echo date('r', $current_day);
        // Label the cell with the current day
        echo date('j', $current_day) . ' ' . date('M', $current_day) . ' ' . date('Y', $current_day);
        echo '</td>';
    echo '</tr>';

echo '</table>';

$html = ob_get_clean();

file_put_contents('calendar.cache.html', $html);

} else {
    $html = file_get_contents('calendar.cache.html');

echo $html;

Copy and paste it to an empty PHP file.

Please note that it has a small caching system (calendar.cache.html) to prevent fetching events every time someone loads the page. You can easily reduce the delay or deactivate this feature.

Refer to the comments to extend it.