How to Build an iCal Calendar with PHP and MySQL

When I wanted to produce an iCalendar feed from my PHP application, I was somewhat surprised at the difficulty I had in finding any basic tutorials on the subject.

Sure, there is the official documentation which I would highly recommended studying if you are serious about building a detailed calendar, but a basic template will probably suffice for most.

The good news is that iCal calendars can be output on a basic LAMP server without any special configuration. It is essentially just plain text.

My iCal is a bunch of appointments retrieved from a database for my clinic management software, and the raw output looks like this:


BEGIN:VCALENDAR
METHOD:PUBLISH
VERSION:2.0
PRODID:-//Thomas Multimedia//Clinic Time//EN
BEGIN:VEVENT
SUMMARY:Emily Henderson
UID:3097
STATUS:CONFIRMED
DTSTART:20120509T031500Z
DTEND:20120509T033000Z
LAST-MODIFIED:20120509T031500Z
LOCATION:Bundall Clinic Room 1
END:VEVENT
END:VCALENDAR

The PHP to generate the calendar looks like this (using the CodeIgniter framework):


<?php

// the iCal date format. Note the Z on the end indicates a UTC timestamp.
define('DATE_ICAL', 'Ymd\THis\Z');

// max line length is 75 chars. New line is \\n

$output = "BEGIN:VCALENDAR
METHOD:PUBLISH
VERSION:2.0
PRODID:-//Thomas Multimedia//Clinic Time//EN\n";

// loop over events
foreach ($query_appointments->result() as $appointment):
 $output .=
"BEGIN:VEVENT
SUMMARY:$appointment->firstname $appointment->surname
UID:$appointment->id
STATUS:" . strtoupper($appointment->status) . "
DTSTART:" . date(DATE_ICAL, strtotime($appointment->starts)) . "
DTEND:" . date(DATE_ICAL, strtotime($appointment->ends)) . "
LAST-MODIFIED:" . date(DATE_ICAL, strtotime($appointment->last_update)) . "
LOCATION:$appointment->location_name $appointment->name
END:VEVENT\n";
endforeach;

// close calendar
$output .= "END:VCALENDAR";

echo $output;

?>

Here is some info on some of the fields:

PRODID:-//Thomas Multimedia//Clinic Mate//EN\n”;
The PRODID are your app / company details in the format Business Name//Product Name//Language.

BEGIN:VEVENT
The start tag for an event. You can have as many vevents as you require.

SUMMARY
This is the event title.

UID
A unique ID for the event. This is important and required, and allows you to push changes to event details after they have been created. If you are retrieving database rows, the primary key is an ideal candidate for the UID value.

STATUS (optional. default value is CONFIRMED)
The event status is optional and can be one of CONFIRMED, TENTATIVE, CANCELLED. A cancelled event shows with a line through text decoration on iOS.

DTSTART, DTEND
The event start and end timestamp. This should be formatted as demonstrated using the defined iCal format.If your dates are not already in the UTC timezone, you should convert them to UTC before outputting as this is the expected timezone when the timestamp ends with “Z”. There are options to specify a timezone which are beyond the scope of this tutorial.

LAST-MODIFIED (optional)
If your event has been modified you might like to set the last modified date in the same format as DTSTART above.

LOCATION (optional)
The title of the event location. There is also a GEO field where you can specify LAT/LONG coordinates, as well as a proposed VVENUE field to contain additional location data, however at least on the iPhone / iPad these tags are unused by the OS. Further reading here. I would expect better map integration in the near future for these fields.

It is worth noting that if you enter an address or phone number into the DESCRIPTION field (which I haven’t documented) in plain text, iOS will auto-link the text and will jump to the Maps app or Phone app with the details pre-filled.

END:VEVENT
The close tag for an event. Remember, you can have as many vevents as you require.

Once your web application is generating calendar data, its time to test!

I did my testing on my iPad. To add a calendar URL, go to: Settings > Mail, Contacts, Calendars, Add Account… Select Other > Add Subscribed Calendar, and then enter your URL in the Server field. Your calendar does not have to have the .ics extension. Then in the Calendar app, you can open the Calendars option and select only your new calendar for testing purposes. The refresh button is your friend! The Apple Calendar updates on its own schedule, which I think is somewhere around every 10-15 minutes.

The major thing to watch out for is formatting errors. iOS devices flatly refused all calendar data if there were any issues with new lines or anything else in contrast to the iCal specifications.

Questions? Comments? Ask below.

22 thoughts on “How to Build an iCal Calendar with PHP and MySQL”

  1. Hey Steve,

    Great tutorial and explanation. I was wondering if the same link works on other calendars like Google, Outlook and Yahoo. I was able to make a calendar feed like you described above and it works on all the apple devices but the same feed url does not work on other calendars.

    Regards

    – Rajeev

  2. Good PHP code but you need to add correct content-type-header :

    header(‘Content-type: text/calendar; charset=utf-8’);
    header(‘Content-Disposition: inline; filename=calendar.ics’);

  3. Hi,

    This looks great!

    ….so I just run a query to pull out fields from my DB and label them with the same names as you’ve used here and it should loop through my DB right?

  4. Hi Steve

    I’ve just stumbled across this, and it’s excellent – thank you. Just a quick question if I can, I’ve looked at the Docs but can’t see it clearly in there so just hoping you might know the answer….

    Your events have a start & end time, which is great. In iCal on Mac you can set an “all day” event, do you know what the mark-up would be for me to do that?

    Thanks again
    Andy

  5. All-day events simply set DTSTART:20130919 and DTEND:20130920 and add
    [X-MICROSOFT-CDO-ALLDAYEVENT] => TRUE

    This would be an all day event on 2013-09-19.

  6. Thanks for sharing that – it works brilliantly.

    Do you know how (or a resource to tell me how) to add an alert/alarm for an event?

  7. This is really great, thanks. I am almost there I think…

    My php file works fine, and generates the ical feed – I can then add it manually to my iphone using Calendars, Add Account… Select Other > Add Subscribed Calendar, and all works fine. It even updates regularly.

    But I want to allow users to subscribe by making the link webcal://mydomain.com/ics.php

    when i try and click this link, it says “could not connect to *.php* on mydomain.com

    Any ideas?

    Tks
    Tom

  8. Hi,
    This looks great! I just have one question about security: do you think there is way to secure the access to the content of the PHP file, maybe with a username and password that could be stored on the iPad or iPhone?
    Thanks a lot,
    Matthieu

  9. Matthieu,

    I’m not sure whether iOS or Android supports something like HTTP Basic Authentication, which you could setup fairly easily on your server – maybe someone else can answer that.

    Otherwise I would recommend using a secure token appended to the url which is attached to the user account.

  10. What if I want to send the file attached to email instead echo to the browser? I have an email sending library capable to attach files. But when I place $output as the attachment file I don’t receive it, the email is sent but without any file attached.

    Some thoughts?

  11. Good code. Not that hard to do it actually, it’s pretty simple once you figure it out. Do you think Android supports HTTP bacis authentication? I’m trying to find the answer on the internet, but no luck. Even looked on http://www.droid.hr, these guys usually have everything when it comes to Android

  12. Well, feed events in php in this way is very easy and useful if u are goong to use it with iphone/outlook but if you want to make it work also with android calendar good luck! I was happy to have found the possibility to restrict access asking username and password too but works only with iphone. If you want something more compatible study caldav like im doin rite now. Google “basic authentication php” for protect the feeder with username and pass 😉

  13. Hie,
    You started me off nicely. I encountered two issues.
    1. When i fetch a calender from a client, it has several events with same uid. Do you know why?
    2. The calender i get from the client has RRULE which simply gives events that repeats like :
    [rrule] => FREQ=WEEKLY;UNTIL=20150520T100000Z;INTERVAL=1;BYDAY=WE;WKST=MO
    How can i deal with this if i am saving anything to the my db

    please help 🙁

  14. Hi,
    how we can use ical in laravel.

    can you give me just simple example for download ical in laravel ?

  15. Hi,

    How can I connect with the DB?
    What code do I put before code

    // the iCal date format. Note the Z on the end indicates a UTC timestamp.
    define(‘DATE_ICAL’, ‘Ymd\THis\Z’);

    Thx,
    Koshi

  16. Hi, thanks for the code. But, I’m not able to view shift assigned between 10:30 to 6:30

    Here is my Code:-

    function __export_schedule($appointment){
    define(‘DATE_ICAL’, ‘Ymd\THis’);
    // max line length is 75 chars. New line is \\n
    $output =”BEGIN:VCALENDAR\rMETHOD:PUBLISH\rVERSION:2.0\rPRODID:-//Thomas Multimedia//Clinic Time//EN\n”;
    $this->user_id=$this->session->userdata(‘user_id’);
    if(!isset($_POST[‘startdate’]))
    {
    $_POST[‘startdate’]=date(‘Y-m-d’);
    }
    if(!isset($_POST[‘enddate’]))
    {
    $_POST[‘enddate’]=date(‘Y-m-d’);
    }
    //$appointment=$this->mrr_common_model->ExportShifts($this->user_id,$_POST[‘startdate’],$_POST[‘enddate’]);
    //$filename=”Export_cal_”.$this->user_id.date(“YmdHis”).”ics”;
    $filename = urlencode( ‘My-Events-‘ . date(‘YmdHis’));
    if($appointment)
    {
    foreach($appointment as $appointmentVal)
    {
    $start=$appointmentVal->schedule_date.” “.$appointmentVal->shift_start_time;
    $ends=$appointmentVal->schedule_date.” “.$appointmentVal->shift_end_time;
    $summary=$appointmentVal->user_funllname.” @ “.$appointmentVal->shift_alias;
    $output .=
    “BEGIN:VEVENT
    SUMMARY:'”.$summary.”‘
    UID:$appointmentVal->schedule_id
    DTSTART:” . date(DATE_ICAL, strtotime($start)) . ”
    DTEND:” . date(DATE_ICAL, strtotime($ends)) . ”
    LAST-MODIFIED:” . date(DATE_ICAL, strtotime($appointmentVal->schedule_updated)) . ”
    LOCATION:’test location’
    END:VEVENT\n”;
    }
    //echo “

    "; echo $start;
    //echo "
    "; echo $ends;
    $output .= "END:VCALENDAR";
    $output=preg_replace('~[\r\n]+~',"\r\n",$output);
        //echo "
    "; echo $filename;
    //echo "
    "; echo $output; exit();
    header("Content-Type: text/Calendar;charset=utf-8");
    header('Content-Disposition: inline; filename='.$filename . '.ics' );
    echo $output;
    exit();
    	
    }
    }
  17. Hi, thanks for the code. But in the mac calendar it shows

    Some Events in this calendar file were unreadable. Events that were readable have been added to your calendar.

    Please help me.

Leave a Reply

Your email address will not be published. Required fields are marked *