116

Any Google search on PHP ical just brings up phpicalendar and how to parse or read IN ical files. I just want to write a PHP file that pulls events from my database and writes them out in ical format.

My problem is I can't find anywhere that will answer two questions:

  1. What is the exact ical format, including headers, file format, footers, etc.? In other words, what does the file have to have, exactly, in order to be properly read in by Google Calendar, etc.?
  2. If I build this file using a .php extension, how do I publish it as ical? Do I have to write to a new .ics file? Or will Google Calendar etc. read a .php file as ical so long as the contents are in the correct format? (Much like a style.css.php file will be read as a CSS file if the contents are actually CSS, etc.)

Any help you all can give or point me to will be greatly appreciated!!!

rhodesjason
  • 4,904
  • 9
  • 43
  • 59

7 Answers7

137

This should be very simple if Google Calendar does not require the *.ics-extension (which will require some URL rewriting in the server).

$ical = "BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
BEGIN:VEVENT
UID:" . md5(uniqid(mt_rand(), true)) . "@yourhost.test
DTSTAMP:" . gmdate('Ymd').'T'. gmdate('His') . "Z
DTSTART:19970714T170000Z
DTEND:19970715T035959Z
SUMMARY:Bastille Day Party
END:VEVENT
END:VCALENDAR";

//set correct content-type-header
header('Content-type: text/calendar; charset=utf-8');
header('Content-Disposition: inline; filename=calendar.ics');
echo $ical;
exit;

That's essentially all you need to make a client think that you're serving a iCalendar file, even though there might be some issues regarding caching, text encoding and so on. But you can start experimenting with this simple code.

Kieran Andrews
  • 5,845
  • 2
  • 33
  • 57
Stefan Gehrig
  • 82,642
  • 24
  • 155
  • 189
  • 1
    Thanks. I think those headers is what I was missing. I assume there are a few final steps in making this Google Calendar ready, as when I try to feed this file to Google Calendar via URL, it says "Importing calendar from url..." but hangs on that forever. Maybe that's a different question to post? – rhodesjason Sep 23 '09 at 16:52
  • Have you tried to access the script from your browser? Does it prompt you to download "caneldar.ics"? Can you import the file into iCal or Outlook for example? – Stefan Gehrig Sep 23 '09 at 16:59
  • Yes it works fine there, and Entourage loads it up fine as well. I just need the ability to create a file that Google Calendar (GC) will ping over and over to refresh, so that it stays up to date with a calendar of events in my database. Right now, GC won't accept it. – rhodesjason Sep 23 '09 at 17:06
  • Can you import this file into GC (using the import calendar option)? – Stefan Gehrig Sep 23 '09 at 17:13
  • That wouldn't help because it needs to be subscribed to the URL so that it updates regularly. HOWEVER, after a long wait, I refreshed and the calendar had been added to GC. Good news there. However, adding another VEVENT to the .php file and saving it to the server, it's still not appeared on GC. I've read that GC refreshes its subscriptions about once every 3 hours, so I'll check back in 3 hours and see if it's updated then. – rhodesjason Sep 23 '09 at 17:20
  • Nice to hear... One thing that obviously is required is a UID field within the VEVENT that contains a unique identifier. – Stefan Gehrig Sep 23 '09 at 17:23
  • Ok, didn't work. But I'm not sure what you mean by UID field. Just add UID:xxx into each VEVENT block where xxx=a unique id? – rhodesjason Sep 24 '09 at 07:04
  • 3
    Exactly. I updated the example above - and I also added a DTSTAMP property which will tell a client when the events has been updated. – Stefan Gehrig Sep 24 '09 at 07:44
  • 1
    Okay Gehrig, you're a genius. That worked. Thanks. (So far as I can tell Google Calendar is updating almost immediately, too.) – rhodesjason Sep 24 '09 at 18:40
  • 3
    If i'm not mistaken. Programs use the UID to see if an event is deleted. If a php-script always generates another UID (->mt_rand), programs will always think the whole content has changed. Everything vanished and everything is new. Personally I would stick to the same UID if the event is the same in the database and just use the recordID (and some host information). The DTSTAMP is there to show something has changed. That should be enough. – Seirddriezel Apr 23 '14 at 16:31
  • 4
    Google calendar DOES require the *.ics-extension. If you are using .htaccess, you can make it by adding `RewriteEngine on` `RewriteRule ^calendar.ics$ my_php_script.php [QSA]` – Fanky Jan 25 '17 at 10:31
  • Am I correct in believing the DTSTAMP should *not* be whatever "now" is, but the datetime of the last change to the particular event? – Stephen R Sep 19 '19 at 00:12
  • What if you have a lot and a lot of events ? Can you do some pagination server side to display only the data the calendar needs ? (like Google Agenda call your script with params ?) I checked on console, but it loads all events – Vincent Decaux Feb 08 '21 at 15:12
25

A note of personal experience in addition to both Stefan Gehrig's answer and Dave None's answer (and mmmshuddup's reply):

I was having validation problems using both \n and PHP_EOL when I used the ICS validator at http://severinghaus.org/projects/icv/

I learned I had to use \r\n in order to get it to validate properly, so this was my solution:

function dateToCal($timestamp) {
  return date('Ymd\Tgis\Z', $timestamp);
}

function escapeString($string) {
  return preg_replace('/([\,;])/','\\\$1', $string);
}    

    $eol = "\r\n";
    $load = "BEGIN:VCALENDAR" . $eol .
    "VERSION:2.0" . $eol .
    "PRODID:-//project/author//NONSGML v1.0//EN" . $eol .
    "CALSCALE:GREGORIAN" . $eol .
    "BEGIN:VEVENT" . $eol .
    "DTEND:" . dateToCal($end) . $eol .
    "UID:" . $id . $eol .
    "DTSTAMP:" . dateToCal(time()) . $eol .
    "DESCRIPTION:" . htmlspecialchars($title) . $eol .
    "URL;VALUE=URI:" . htmlspecialchars($url) . $eol .
    "SUMMARY:" . htmlspecialchars($description) . $eol .
    "DTSTART:" . dateToCal($start) . $eol .
    "END:VEVENT" . $eol .
    "END:VCALENDAR";

    $filename="Event-".$id;

    // Set the headers
    header('Content-type: text/calendar; charset=utf-8');
    header('Content-Disposition: attachment; filename=' . $filename);

    // Dump load
    echo $load;

That stopped my parse errors and made my ICS files validate properly.

Kane Ford
  • 426
  • 6
  • 12
  • The header information is the important part FYI to anyone looking in the future. For the most part most apps and programs don't worry about the NewLine breaks. Only the validators do it seems. But the most important thing is the header part. We tried for awhile without it and were having many problems. – jfreak53 Mar 11 '15 at 18:58
  • 1
    What is escapeString for? I assumed it should escape a thing or two but you seem to use `htmlspecialchars` for that instead. – Luc May 12 '16 at 06:15
  • 2
    A quick fix: date('Ymd\THis\Z', $timestamp). Should be a H instead of g. – Pedro Góes Nov 24 '17 at 13:22
7

There is an excellent eluceo/ical package that allows you to easily create ics files.

Here is an example usage from docs:

// 1. Create new calendar
$vCalendar = new \Eluceo\iCal\Component\Calendar('www.example.com');

// 2. Create an event
$vEvent = new \Eluceo\iCal\Component\Event();
$vEvent->setDtStart(new \DateTime('2012-12-24'));
$vEvent->setDtEnd(new \DateTime('2012-12-24'));
$vEvent->setNoTime(true);
$vEvent->setSummary('Christmas');

// Adding Timezone (optional)
$vEvent->setUseTimezone(true);

// 3. Add event to calendar
$vCalendar->addComponent($vEvent);

// 4. Set headers
header('Content-Type: text/calendar; charset=utf-8');
header('Content-Disposition: attachment; filename="cal.ics"');

// 5. Output
echo $vCalendar->render();
Ivan Yarych
  • 1,931
  • 17
  • 15
5

Maybe a little late, but here's a link to the actual specification. https://www.rfc-editor.org/rfc/rfc5545[1]

Community
  • 1
  • 1
Seirddriezel
  • 593
  • 6
  • 11
4

http://www.kanzaki.com/docs/ical/ has a slightly more readable version of the older spec. It helps as a starting point - many things are still the same.

Also on my site, I have

  1. Some lists of useful resources (see sidebar bottom right) on
    • ical Spec RFC 5545
    • ical Testing Resources
  2. Some notes recorded on my journey working with .ics over the last few years. In particular, you may find this repeating events 'cheatsheet' to be useful.

.ics areas that need careful handling:

  • 'all day' events
  • types of dates (timezone, UTC, or local 'floating') - nb to understand distinction
  • interoperability of recurrence rules
Community
  • 1
  • 1
anmari
  • 3,830
  • 1
  • 15
  • 15
2
  1. Exact ical format: http://www.ietf.org/rfc/rfc2445.txt
  2. According to the spec, it has to end in .ics

Edit: actually I'm not sure - line 6186 gives an example in .ics naming format, but it also states you can use url parameters. I don't think it matters, so long as the MIME type is correct.

Edit: Example from wikipedia: http://en.wikipedia.org/wiki/ICalendar

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
BEGIN:VEVENT
DTSTART:19970714T170000Z
DTEND:19970715T035959Z
SUMMARY:Bastille Day Party
END:VEVENT
END:VCALENDAR

MIME type is configured on the server.

lod3n
  • 2,893
  • 15
  • 16
  • 1
    I've tried to read that spec many times but I can't make heads or tails of it as far as what the ical file will look like. Can you at least point me to some lines where it begins to actually talk about what the .ics file should contain as far as header, where to put the MIME type, etc? – rhodesjason Sep 23 '09 at 01:26
2

Make sure you format the string like this or it wont work

 $content = "BEGIN:VCALENDAR\n".
            "VERSION:2.0\n".
            "PRODID:-//hacksw/handcal//NONSGML v1.0//EN\n".
            "BEGIN:VEVENT\n".
            "UID:".uniqid()."\n".
            "DTSTAMP:".$time."\n".
            "DTSTART:".$time."\n".
            "DTEND:".$time."\n".
            "SUMMARY:".$summary."\n".
            "END:VEVENT\n".
            "END:VCALENDAR";
Dave None
  • 663
  • 5
  • 4