Parsing iCalendar

Getting started

Make sure you followed all the steps in the installation instructions, and you've included the autoloader in your code.

A basic parsing example

Assuming there is an iCalendar file in your lcoal directory, named party.ics, the following complete example will parse it and display the SUMMARY property:

use Sabre\VObject;

include 'vendor/autoload.php';

$vcalendar = VObject\Reader::read(
    fopen('party.ics','r')
);
echo $vcalendar->VEVENT->SUMMARY;

That is all.

For all the following examples, we are assuming that you have already included the autoloader, and also that you've called

use Sabre\VObject;

at the top of your script. This is just to avoid repetition.

The actual manual

Creating a new iCalendar object

To create a new iCalendar object, you can construct a VCalendar component, and pass it the structure you want.

Here's an example:

$vcalendar = new VObject\Component\VCalendar([
    'VEVENT' => [
        'SUMMARY' => 'Birthday party!',
        'DTSTART' => new \DateTime('2016-07-04 21:00:00'),
        'DTEND'   => new \DateTime('2016-07-05 03:00:00')
    ]
]);

echo $vcalendar->serialize();

This will output:

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject 4.0.0-beta1//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
UID:sabre-vobject-2930d1fa-ac6d-42c8-92fe-06bb8bc3614e
DTSTAMP:20151202T171911Z
SUMMARY:Birthday party!
DTSTART;TZID=America/New_York:20160704T210000
DTEND;TZID=America/New_York:20160705T030000
END:VEVENT
END:VCALENDAR

Manipulating properties

It's very easy to modify properties on objects:

// Overwrites or sets a property:
$vcalendar->VEVENT->SUMMARY = 'Funeral';

// Removes a property
unset($vcalendar->VEVENT->DTSTAMP);

// Checks for existence of a property:
isset($vcalendar->PRODID);

// Adds a property without overwriting an existing one.
$vcalendar->VEVENT->add('ATTENDEE', 'mailto:foo@example.com');
$vcalendar->VEVENT->add('ATTENDEE', 'mailto:evert@example.com');

If your property name has a special character, such as the property LAST-MODIFIED, you can access it as such:

echo (string)$valendar->VEVENT->{'LAST-MODIFIED'};

Working with parameters

iCalendar also has a concept of parameters on properties. This is a bit like an "attribute" in xml or html.

An example of a property with a parameter:

ATTENDEE;CN="Real name":mailto:foo@example.org

If you would want to get access to that parameter, you can use array-access to do so:

$realName = $vcalendar->VEVENT->ATTENDEE['CN'];

You can change it in the same manner:

$vcalendar->VEVENT->ATTENDEE['RSVP'] = 'TRUE';

It is also possible add a list of parameters while creating the property.

$vcalendar->VEVENT->add(
    'ATTENDEE',
    'foo@example.org',
    [
       'RSVP' => 'TRUE',
       'CN'   => 'Real Name',
    ]
);

Parsing iCalendar

To parse a vCard or iCalendar object, simply call:

// $data must be either a string, or a stream.
$vcard = VObject\Reader::read($data);

When you're working with data generated by broken software (such as the latest Microsoft Outlook), you can pass a 'forgiving' option that will do an attempt to mend the broken data.

$vcalendar = VObject\Reader::read($data, VObject\Reader::OPTION_FORGIVING);

Many iCalendar objects these days are encoded as UTF-8. If however you are running into an object with a different encoding, you can specify this as the third option:

$vcard = VObject\Reader::read($data, 0, 'ISO-8859-1');

Currently ISO-8859-1 and Windows-1252 are supported. This feature appeared in vObject 4.

Looping through properties.

Properties such as ATTENDEE may appear more than once in a vCard. To loop through them, you can simply throw them in a foreach() statement:

foreach($vcalendar->VEVENT->ATTENDEE as $attendee) {
    echo 'Attendee ', (string)$attendee;
}

Working with Components

In iCalendar, VTODO, VEVENT, VJOURNAL, VALARM and several others are all considered 'components'.

A calendar may have any one of these, and often multiple.

This is an example of a new VEVENT being added to a calendar:

$vcalendar = new VObject\Component\VCalendar();

$vcalendar->add('VEVENT', [
    'SUMMARY' => 'Birthday party',
    'DTSTART' => new \DateTime('2013-04-07'),
    'RRULE' => 'FREQ=YEARLY',
]);

echo $vcalendar->serialize();

The add() method will always return the instance of the property or sub-component it's creating. This makes for easy further manipulation.

Here's another example that adds an attendee and an organizer:

$vcalendar = new VObject\Component\VCalendar();

$vevent = $vcalendar->add('VEVENT', [
    'SUMMARY' => 'Meeting',
    'DTSTART' => new \DateTime('2013-04-07'),
]);

$vevent->add('ORGANIZER','mailto:organizer@example.org');
$vevent->add('ATTENDEE','mailto:attendee1@example.org');
$vevent->add('ATTENDEE','mailto:attendee2@example.org');

If a VCALENDAR has multiple components, and you'd like to loop through them, you can simply use foreach. This example goes through all the VTODO components in a calendar.

foreach($vcalendar->VTODO as $todo) {

    echo (string)$todo->SUMMARY;

}

Date and time handling

Parsing Dates and Times from iCalendar and vCard can be difficult. Most of this is abstracted by the VObject library.

Given an event, in a calendar, you can get a real PHP DateTime object using the following syntax:

$start = $vcalendar->VEVENT->DTSTART->getDateTime();
echo $start->format(\DateTime::W3C);

To update the property with a new DateTime object, just use the following syntax:

$dateTime = new \DateTime('2012-08-07 23:53:00', new \DateTimeZone('Europe/Amsterdam'));
$event->DTSTART = $dateTime;

Free-busy report generation

Some calendaring software can make use of FREEBUSY reports to show when people are available.

You can automatically generate these reports from calendars using the FreeBusyGenerator.

Example based on our last event:

// We're giving it the calendar object. It's also possible to specify multiple objects,
// by setting them as an array.
//
// We must also specify a start and end date, because recurring events are expanded.
$fbGenerator = new VObject\FreeBusyGenerator(
    new DateTime('2012-01-01'),
    new DateTime('2012-12-31'),
    $vcalendar
);

// Grabbing the report
$freebusy = $fbGenerator->getResult();

// The freebusy report is another VCALENDAR object, so we can serialize it as usual:
echo $freebusy->serialize();

The output of this script will look like this:

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject 2.0//EN
CALSCALE:GREGORIAN
BEGIN:VFREEBUSY
DTSTART;VALUE=DATE-TIME:20111231T230000Z
DTEND;VALUE=DATE-TIME:20111231T230000Z
DTSTAMP;VALUE=DATE-TIME:20120808T131628Z
FREEBUSY;FBTYPE=BUSY:20120109T140000Z/20120109T140000Z
FREEBUSY;FBTYPE=BUSY:20120213T140000Z/20120213T140000Z
FREEBUSY;FBTYPE=BUSY:20120312T140000Z/20120312T140000Z
FREEBUSY;FBTYPE=BUSY:20120409T140000Z/20120409T140000Z
FREEBUSY;FBTYPE=BUSY:20120514T140000Z/20120514T140000Z
FREEBUSY;FBTYPE=BUSY:20120611T140000Z/20120611T140000Z
FREEBUSY;FBTYPE=BUSY:20120709T140000Z/20120709T140000Z
FREEBUSY;FBTYPE=BUSY:20120813T140000Z/20120813T140000Z
FREEBUSY;FBTYPE=BUSY:20120910T140000Z/20120910T140000Z
FREEBUSY;FBTYPE=BUSY:20121008T140000Z/20121008T140000Z
FREEBUSY;FBTYPE=BUSY:20121112T140000Z/20121112T140000Z
FREEBUSY;FBTYPE=BUSY:20121210T140000Z/20121210T140000Z
END:VFREEBUSY
END:VCALENDAR

Validating iCalendar

When you parse an iCalendar document, the parser grabs all the values and does basic syntax checking. It does not however, validate every value.

You can ask the parser to validate the entire document by calling the validate function though:

$result = $vcalendar->validate();

The returned value is an array of messages and might look like this:

[
    [
        'level' => 2,
        'message' => '...',
        'node' => ...A VObject component or property...
    ]
]

Each item constitutes a problem with the document. Every item contains a level containing a number between 1 and 3. 3 Means that the document is invalid, 2 means a warning. A warning means it's valid but it could cause interopability issues, and 1 means that there was a problem earlier, but the problem was automatically repaired.

The message is a human-readable string with more information about the problem, and lastly the node refers to the actual VObject Component or Property object that had the issue.

You can also pass several options. The options have to be passed as a bitfield:

$vcalendar->validate(Sabre\VObject\Node::REPAIR);
$vcalendar->validate(Sabre\VObject\Node::PROFILE_CALDAV);

The REPAIR option automatically repeairs the object, if possible. Without REPAIR the validator does not change the object.

The PROFILE_CALDAV validates the object specifically for within the use of CalDAV. iCalendar objects on CalDAV servers have a few additional special restrictions (no METHOD may appear, only one component per document, etc..).

The validator is not perfect, and we improve it when we come across new issues, so any suggestions are welcome.

Support

Head over to the SabreDAV mailing list for any questions.


Comments