1 # SabreTooth VObject library
3 [![Build Status](https://secure.travis-ci.org/evert/sabre-vobject.png?branch=master)](http://travis-ci.org/evert/sabre-vobject)
5 The VObject library allows you to easily parse and manipulate [iCalendar](https://tools.ietf.org/html/rfc5545)
6 and [vCard](https://tools.ietf.org/html/rfc6350) objects using PHP.
7 The goal of the VObject library is to create a very complete library, with an easy to use API.
9 This project is a spin-off from [SabreDAV](http://code.google.com/p/sabredav/), where it has
10 been used for several years. The VObject library has 100% unittest coverage.
14 VObject requires PHP 5.3, and should be installed using composer.
15 The general composer instructions can be found on the [composer website](http://getcomposer.org/doc/00-intro.md composer website).
17 After that, just declare the vobject dependency as follows:
21 "sabre/vobject" : "dev-master"
25 Then, run `composer.phar update` and you should be good. As soon as the first release is out, you should switch `dev-master` to `2.0.*` though.
31 For our example, we will be using the following vcard:
36 PRODID:-//Sabre//Sabre VObject 2.0//EN
39 EMAIL;TYPE=WORK:mplanck@example.org
40 item1.TEL;TYPE=CELL:(+49)3144435678
41 item1.X-ABLabel:Private cell
42 item2.TEL;TYPE=WORK:(+49)5554564744
48 If we want to just print out Max' full name, you can just use property access:
55 $card = VObject\Reader::read($data);
60 ## Changing properties
62 Creating properties is pretty similar. If we like to add his birthday, we just
67 $card->BDAY = '1858-04-23';
71 Note that in the previous example, we're actually updating any existing BDAY that
72 may already exist. If we want to add a new property, without overwriting the previous
73 we can do this with the `add` method.
77 $card->add('EMAIL','max@example.org');
83 If we want to also specify that this is max' home email addresses, we can do this with
88 $card->add('EMAIL', 'max@example'org', array('type' => 'HOME'));
92 If we want to read out all the email addresses and their type, this would look something
96 foreach($card->EMAIL as $email) {
98 echo $email['TYPE'], ' - ', $email;
105 In our example, you can see that the TEL properties are prefixed. These are 'groups' and
106 allow you to group multiple related properties together. The group can be any user-defined
109 This particular example as generated by the OS X addressbook, which uses the `X-ABLabel`
110 to allow the user to specify custom labels for properties. OS X addressbook uses groups
111 to tie the label to the property.
113 The VObject library simply ignores the group if you don't specify it, so this will work:
117 foreach($card->TEL as $tel) {
122 But if you would like to target a specific group + property, this is possible too:
126 echo $card->{'ITEM1.TEL'};
130 So if you would like to show all the phone numbers, along with their custom label, the
131 following syntax is used:
134 foreach($card->TEL as $tel) {
136 echo $card->{$tel->group . '.X-ABLABEL'}, ": ";
142 ## Serializing / Saving
144 If you want to generate your updated VObject, you can simply call the serialize() method.
148 echo $card->serialize();
154 iCalendar, unlike vCards always have sub-components. Where vCards are often just a flat
155 list, iCalendar objects tend to have a tree-like structure. For the following paragraphs,
156 we will use the following object as the example:
161 PRODID:-//Sabre//Sabre VObject 2.0//EN
163 SUMMARY:Curiosity landing
164 DTSTART:20120806T051439Z
170 Since events, tasks and journals are always in a sub component, this is also how we
177 $calendar = VObject\Reader::read($data);
178 echo $calendar->VEVENT->SUMMARY;
182 Adding components to a calendar is done with a factory method:
186 $event = VObject\Component::create('VEVENT');
187 $calendar->add($event);
189 $event->SUMMARY = 'Curiosity launch';
190 $event->DTSTART = '20111126T150202Z';
191 $event->LOCATION = 'Cape Carnival';
195 By the way.. cloning also works as expected, as the entire structure is cloned along with it:
199 $clonedEvent = clone $calendar->VEVENT[0];
200 $calendar->add($clonedEvent);
204 ## Date and time handling
206 If you ever had to deal with iCalendar timezones, you know it can be complicated.
207 The way timezones are specified is flawed, which is something I may write an essay about some
208 day. VObject does its best to determine the correct timezone. Many standard formats
209 have been tested and verified, and special code has been implemented for special-casing
210 microsoft generated timezone information, and others.
212 To get a real php `DateTime` object, you can request this as follows:
215 $event = $calendar->VEVENT;
216 $start = $event->DTSTART->getDateTime();
217 echo $start->format(\DateTime::W3C);
220 To set the property with a DateTime object, you can use the following syntax:
223 $dateTime = new \DateTime('2012-08-07 23:53:00', new DateTimeZone('Europe/Amsterdam'));
224 $event->DTSTART->setDateTime($dateTime, VObject\Property\DateTime::DATE);
227 The second argument specifies the type of date you're setting. The following three
230 1. `LOCAL` This is a floating time, with no timezone information. This basically specifies that the event happens in whatever the timezone's currently in. This would be encoded as `DTSTART;VALUE=DATE-TIME:20120807235300`
231 2. `UTC` This specifies that the time should be encoded as a UTC time. This is encoded as `DTSTART;VALUE=DATE-TIME:20120807205300Z`. Note the extra Z and the fact that it's two hours 'earlier'.
232 3. `LOCALTZ` specifies that it's supposed to be encoded in its local timezone. For example `DTSTART;VALUE=DATE-TIME;TZID=Europe/Amsterdam:20120807235300`.
233 4. `DATE` This is a date-only, and does not contain the time. In this case this would be encoded as `DTSTART;VALUE=DATE:20120807`.
235 A few important notes:
237 * When a `TZID` is specified, there should also be a matching `VTIMEZONE` object with all the timezone information. VObject cannot currently automatically embed this. However, in reality other clients seem to do fine without this information. Yet, for completeness, this will be added in the future.
238 * As mentioned, the timezone-determination process may sometimes fail. Report any issues you find, and I'll be quick to add workarounds!
242 Recurrence rules allow events to recur, for example for a weekly meeting, or an anniversary.
243 This is done with the `RRULE` property. The `RRULE` property allows for a LOT of different
244 rules. VObject only implements the ones that actually appear in calendar software.
246 To read more about `RRULE` and all the options, check out [RFC5545](https://tools.ietf.org/html/rfc5545#section-3.8.5).
247 VObject supports the following options:
249 1. `UNTIL` for an end date.
250 2. `INTERVAL` for for example "every 2 days".
251 3. `COUNT` to stop recurring after x items.
252 4. `FREQ=DAILY` to recur every day, and `BYDAY` to limit it to certain days.
253 5. `FREQ=WEEKLY` to recur every week, `BYDAY` to expand this to multiple weekdays in every week and `WKST` to specify on which day the week starts.
254 6. `FREQ=MONTHLY` to recur every month, `BYMONTHDAY` to expand this to certain days in a month, `BYDAY` to expand it to certain weekdays occuring in a month, and `BYSETPOS` to limit the last two expansions.
255 7. `FREQ=YEARLY` to recur every year, `BYMONTH` to expand that to certain months in a year, and `BYDAY` and `BYWEEKDAY` to expand the `BYMONTH` rule even further.
257 VObject supports the `EXDATE` property for exclusions, but not yet the `RDATE` and `EXRULE`
258 properties. If you're interested in this, please file a github issue, as this will put it
261 This is a bit of a complex subject to go in excruciating detail. The
262 [RFC](https://tools.ietf.org/html/rfc5545#section-3.8.5) has a lot of examples though.
264 The hard part is not to write the RRULE, it is to expand them. The most complex and
265 hard-to-read code is hidden in this component. Dragons be here.
267 So, if we have a meeting every 2nd monday of the month, this would be specified as such:
273 UID:1102c450-e0d7-11e1-9b23-0800200c9a66
274 DTSTART:20120109T140000Z
275 RRULE:FREQ=MONTHLY;BYDAY=MO;BYSETPOS=2
280 Note that normally it's not allowed to indent the object like this, but it does make
281 it easier to read. This is also the first time I added in a UID, which is required
282 for all VEVENT, VTODO and VJOURNAL objects!
284 To figure out all the meetings for this year, we can use the following syntax:
289 $calendar = VObject\Reader::read($data);
290 $calendar->expand(new DateTime('2012-01-01'), new DateTime('2012-12-31'));
293 What the expand method does, is look at its inner events, and expand the recurring
294 rule. Our calendar now contains 12 events. The first will have its RRULE stripped,
295 and every subsequent VEVENT has the correct meeting date and a `RECURRENCE-ID` set.
297 This results in something like this:
303 UID:1102c450-e0d7-11e1-9b23-0800200c9a66
304 DTSTART:20120109T140000Z
307 UID:1102c450-e0d7-11e1-9b23-0800200c9a66
308 RECURRENCE-ID:20120213T140000Z
309 DTSTART:20120213T140000Z
312 UID:1102c450-e0d7-11e1-9b23-0800200c9a66
313 RECURRENCE-ID:20120312T140000Z
314 DTSTART:20120312T140000Z
320 To show the list of dates, we would do this as such:
323 foreach($calendar->VEVENT as $event) {
324 echo $event->DTSTART->getDateTime()->format(\DateTime::ATOM);
328 In a recurring event, single instances can also be overriden. VObject also takes these
329 into consideration. The reason we needed to specify a start and end-date, is because
330 some recurrence rules can be 'never ending'.
332 You should make sure you pick a sane date-range. Because if you pick a 50 year
333 time-range, for a daily recurring event; this would result in over 18K objects.
335 # Free-busy report generation
337 Some calendaring software can make use of FREEBUSY reports to show when people are
340 You can automatically generate these reports from calendars using the `FreeBusyGenerator`.
342 Example based on our last event:
346 // We're giving it the calendar object. It's also possible to specify multiple objects,
347 // by setting them as an array.
349 // We must also specify a start and end date, because recurring events are expanded.
350 $fbGenerator = new VObject\FreeBusyGenerator(
351 new DateTime('2012-01-01'),
352 new DateTime('2012-12-31'),
356 // Grabbing the report
357 $freebusy = $fbGenerator->result();
359 // The freebusy report is another VCALENDAR object, so we can serialize it as usual:
360 echo $freebusy->serialize();
363 The output of this script will look like this:
368 PRODID:-//Sabre//Sabre VObject 2.0//EN
371 DTSTART;VALUE=DATE-TIME:20111231T230000Z
372 DTEND;VALUE=DATE-TIME:20111231T230000Z
373 DTSTAMP;VALUE=DATE-TIME:20120808T131628Z
374 FREEBUSY;FBTYPE=BUSY:20120109T140000Z/20120109T140000Z
375 FREEBUSY;FBTYPE=BUSY:20120213T140000Z/20120213T140000Z
376 FREEBUSY;FBTYPE=BUSY:20120312T140000Z/20120312T140000Z
377 FREEBUSY;FBTYPE=BUSY:20120409T140000Z/20120409T140000Z
378 FREEBUSY;FBTYPE=BUSY:20120514T140000Z/20120514T140000Z
379 FREEBUSY;FBTYPE=BUSY:20120611T140000Z/20120611T140000Z
380 FREEBUSY;FBTYPE=BUSY:20120709T140000Z/20120709T140000Z
381 FREEBUSY;FBTYPE=BUSY:20120813T140000Z/20120813T140000Z
382 FREEBUSY;FBTYPE=BUSY:20120910T140000Z/20120910T140000Z
383 FREEBUSY;FBTYPE=BUSY:20121008T140000Z/20121008T140000Z
384 FREEBUSY;FBTYPE=BUSY:20121112T140000Z/20121112T140000Z
385 FREEBUSY;FBTYPE=BUSY:20121210T140000Z/20121210T140000Z