Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
A convenient script to generate iCalendar (.ics) files on the fly in PHP.


This gist contains a convenient script to generate iCalendar (.ics) files on the fly in PHP.

Basic usage

include 'ICS.php'

$properties = array(
  'dtstart' => 'now',
  'dtend' => 'now + 30 minutes'

$ics = new ICS($properties);
$ics_file_contents = $ics->to_string();

Available properties

  • description - string description of the event.
  • dtend - date/time stamp designating the end of the event. You can use either a DateTime object or a PHP datetime format string (e.g. "now + 1 hour").
  • dtstart - date/time stamp designating the start of the event. You can use either a DateTime object or a PHP datetime format string (e.g. "now + 1 hour").
  • location - string address or description of the location of the event.
  • summary - string short summary of the event - usually used as the title.
  • url - string url to attach to the the event. Make sure to add the protocol (http:// or https://).

Detailed examples

Button that downloads an ICS file when clicked

This example contains a form on the front-end that submits to a PHP script that initiates a download of an ICS file. This example uses hidden form fields to set the properties dynamically.


<form method="post" action="/download-ics.php">
  <input type="hidden" name="date_start" value="2017-1-16 9:00AM">
  <input type="hidden" name="date_end" value="2017-1-16 10:00AM">
  <input type="hidden" name="location" value="123 Fake St, New York, NY">
  <input type="hidden" name="description" value="This is my description">
  <input type="hidden" name="summary" value="This is my summary">
  <input type="hidden" name="url" value="">
  <input type="submit" value="Add to Calendar">



include 'ICS.php';

header('Content-Type: text/calendar; charset=utf-8');
header('Content-Disposition: attachment; filename=invite.ics');

$ics = new ICS(array(
  'location' => $_POST['location'],
  'description' => $_POST['description'],
  'dtstart' => $_POST['date_start'],
  'dtend' => $_POST['date_end'],
  'summary' => $_POST['summary'],
  'url' => $_POST['url']

echo $ics->to_string();
* ICS.php
* =======
* Use this class to create an .ics file.
* Usage
* -----
* Basic usage - generate ics file contents (see below for available properties):
* $ics = new ICS($props);
* $ics_file_contents = $ics->to_string();
* Setting properties after instantiation
* $ics = new ICS();
* $ics->set('summary', 'My awesome event');
* You can also set multiple properties at the same time by using an array:
* $ics->set(array(
* 'dtstart' => 'now + 30 minutes',
* 'dtend' => 'now + 1 hour'
* ));
* Available properties
* --------------------
* description
* String description of the event.
* dtend
* A date/time stamp designating the end of the event. You can use either a
* DateTime object or a PHP datetime format string (e.g. "now + 1 hour").
* dtstart
* A date/time stamp designating the start of the event. You can use either a
* DateTime object or a PHP datetime format string (e.g. "now + 1 hour").
* location
* String address or description of the location of the event.
* summary
* String short summary of the event - usually used as the title.
* url
* A url to attach to the the event. Make sure to add the protocol (http://
* or https://).
class ICS {
const DT_FORMAT = 'Ymd\THis\Z';
protected $properties = array();
private $available_properties = array(
public function __construct($props) {
public function set($key, $val = false) {
if (is_array($key)) {
foreach ($key as $k => $v) {
$this->set($k, $v);
} else {
if (in_array($key, $this->available_properties)) {
$this->properties[$key] = $this->sanitize_val($val, $key);
public function to_string() {
$rows = $this->build_props();
return implode("\r\n", $rows);
private function build_props() {
// Build ICS properties - add header
$ics_props = array(
'PRODID:-//hacksw/handcal//NONSGML v1.0//EN',
// Build ICS properties - add header
$props = array();
foreach($this->properties as $k => $v) {
$props[strtoupper($k . ($k === 'url' ? ';VALUE=URI' : ''))] = $v;
// Set some default values
$props['DTSTAMP'] = $this->format_timestamp('now');
$props['UID'] = uniqid();
// Append properties
foreach ($props as $k => $v) {
$ics_props[] = "$k:$v";
// Build ICS properties - add footer
$ics_props[] = 'END:VEVENT';
$ics_props[] = 'END:VCALENDAR';
return $ics_props;
private function sanitize_val($val, $key = false) {
switch($key) {
case 'dtend':
case 'dtstamp':
case 'dtstart':
$val = $this->format_timestamp($val);
$val = $this->escape_string($val);
return $val;
private function format_timestamp($timestamp) {
$dt = new DateTime($timestamp);
return $dt->format(self::DT_FORMAT);
private function escape_string($str) {
return preg_replace('/([\,;])/','\\\$1', $str);


Good Job soldier !

Good job!

Thanks a lot. But one remark concerning security: Don't use GET- or POST-Data as input. If you do this, people might create links to your website and give them fake ical files with fake events directly from your website (even encrypted with your certificate, if you have https).

Better take the event data from a database or something.

Thanks, exactly what i was looking for!


jakebellacera commented Jul 15, 2013

@ConnyOnny, this script was made for a very simple webpage, but utilizing the database is a must-have in a heavily trafficked application. I would not use this script as-is in a production environment. Tweak as necessary ;)


jakebellacera commented Jul 15, 2013

I've updated the script to help make things a bit clearer to understand. Also I'd highly suggest reading up on RFC5545 if you're interested in using this format in production.

Outlook says it isn't a valid ics file. Did anyone else have this issue?


jakebellacera commented Jul 23, 2013

@EdgeMarketing I don't have Outlook to test. Could you try again with the newest version of this script? Check the revisions tab, I got rid of some whitespace.

Thanks a lot! Very good!
2 Questions:

  • why date('Ymd\Tgis\Z', $timestamp); and not date('Ymd\THis\Z', $timestamp); ?
  • if I open the ICS-file, it's in ANSI and not in UTF-8. Do you know why?

jakebellacera commented Aug 28, 2013

@Sprungfeld sorry for the late reply! Basically, I'm using g instead of H because according to RFC 5545 section 3.3.12, we need a 24-hour formatted time, zero-padded (so it maintains two digits if nessecary).

The text format consists of a two-digit, 24-hour of the day (i.e., values 00-23), two-digit minute in the hour (i.e., values 00-59), and two-digit seconds in the minute (i.e., values 00-60).

Also, I'm unsure why the encoding was detected in ANSI, maybe it's because I'm not using any special characters? Most editors just take an educated guess based off of the file's contents. The encoding shouldn't matter as files don't have headers bound to them, the line endings are what does matter. This SO answer might clear things up.

EDIT: as @MacTEC has pointed out, H is the correct representation for 24-hour time. I have updated the gist with that.

jmarus commented Sep 18, 2013

Can someone tell me how to change this to my currently set server timezone?

MacTEC commented Sep 24, 2013

@jakebellacera What you're describing in the specification (zero-padded) matches to using "H" instead of "g" in the date format as Sprungfeld has pointed out. I had to make this change to make the Outlook eat my ICS feed.

Also, all the forked scripts that have been modified have been changed to "H" or "h".


jakebellacera commented Oct 10, 2013

@MacTEC you're right, H is the correct representation for a zero padded 24-hour time


jakebellacera commented Oct 10, 2013

@jmarus see the bottom of page 33 in RFC #5545, section 3.3.5 for the TZID procedure (it will link you to section 3.2.19).

gbarcun commented Oct 16, 2013

Hi, i'm having a problem with the ics output. both outlook and google are creating this event at 12PM, instead of 9AM. here's a snippet of the ics output:
DESCRIPTION:some description
LOCATION:home town
SUMMARY: some summary

plus, I also hav the following as header:

Do you have any advice?

@gmbarcun and others, on line 46 it shouldn't be:

return date('Ymd\THis\Z', $timestamp);
but rather:
return gmdate('Ymd\THis\Z', $timestamp);

Using date instead of gmdate makes it convert the timestamp to the server's timezone, which doesn't accurately coincide with the Z suffix.

ioane commented Apr 13, 2014

I am new to ics. I am looking for a text script (like the above), save it in a php file, upload it to my server, go to a browser with the URL of the file, and see a calendar in a web page. When I copy the above PHPtoICS.php script to a file saved as PHPtoICS.php and upload to my server, without any amendment, it downloads an ics file (in Chrome), which opens the text: BEGIN: vCALENDAR etc. How do I get started with ics calendars embedded into a web page? I am finding documentation begins with the complex.

@ioane: This is about adding a new entry to an existing, local calendar. Nothing here is for displaying calendars on a webpage. For that you need ready scripts and plugins, but surely not this one here.

You might rather get to know web development in general better first.

I had to add the following: date_default_timezone_set('America/Chicago');

I was getting a warning "Warning: mktime(): It is not safe to rely on the system's timezone settings. You are required to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected 'America/Chicago' for 'CDT/-5.0/DST' instead in .../PHPtoICS.php on line 15"

How we can write 2 events in the ics file . i tried the below , but not working:
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
LOCATION:At paty hall
DESCRIPTION:sdpfjosdfoids uf dsoifd

LOCATION:At paty hall
DESCRIPTION:sdpfjosdfoids uf dsoifd

@pawandhanda VEVENT sections should have differente UIDs unless they are instances of the same recurring event (in which case your are missing the RECURRENCE-ID parameter)

I have a database of events that I want to have the option to export as ics. It looks like from this script that I should loop over the following to output each event to a calendar of events. Right?

Thanks for the nice work.
Converted into a class here =>

kwoot commented Sep 11, 2015

You made my day. :-)

important note. guys using PHP, each properties needs to end with a newline. example


must be...


Thank you very much for your template, I've used it to build a new extension for cms contao.

Jany-M commented Aug 13, 2016

I forked your code to make a WordPress script that generates an .ics on demand, for whatever custom post type (that has the necessary data for it). Thank you for your input!


jakebellacera commented Aug 17, 2016

@Jany-M awesome! Glad you found it helpful. 😄

Malatesh144 commented Sep 23, 2016

I want to send a alerts messge to the perticular user whenever an event occurs. Could you help me in that case

Malatesh144 commented Sep 23, 2016

I want to send a alerts message to the particular user whenever an event occurs. By using .ics calendar.Could someone help me in that case

Fantastic, thanks very much!

okierie commented Oct 27, 2016

Thanks for the template! It is really helpful
I use it for generating events in our company's internal system

Thanks for the file. Great work! I have used this to also include participant and organiser information. However when I open the ICS file in outlook all information is there apart from participant information and these need to be added manually in outlook (even though this information exists in the ICS file). I have tried various derivates of this PHP script to generate ICS files but can never get Organiser/Participant information in the invite - do you know why this is?

According with your description, you can use either a DateTime object or String, but it is not true: in line 124 you pass $timestamp directly to DateTime constructor. If it is a DateTime Object, an Exception is cast, because the constructor need a String.
I modify you code in this (rude) way:
$dt = (is_object($timestamp))?$timestamp:new DateTime($timestamp);
Thanks for your work.

Beware of typo in header('Content-type: text/calendar; charset=utf-8') in the example. This should be corrected to Content-Type. If not browsers will download html file instead of ics. It took me 1 hour to find what's wrong :)

Thanks for the script and the detailed example. I was having a problem with the times on my events being wrong, they were coming into my calendar 4 hours earlier, not at UTC time but 9 hours earlier than it… I was running the script on my local machine, so not sure where the times were coming from. To fix it, I added 'X-WR-TIMEZONE:America/New_York', to the .ics header (line 83 in ICS.php) and removed \Z from the DT_FORMAT const (line 44) and that seems to have fixed it. It would be nice to be able to declare a timezone with a variable passed to the script.

Thanks for sharing, it worked as it described in document.
I too had to remove Z to get local time to work though.
Great work!!

haolle commented Mar 6, 2017

hello, just wonder how can i send a meeting request email with an ICS attachment?
seems it doesn't work simply like: $result=mail($to, $subject, $ics->to_string(), $headers);

HugoAcosta commented Mar 13, 2017

thanks raglannyc, I had that problem too...

There's a way to manage ATTENDEES too? Thanks

My server is in utf-7 and I am in utf-8 ... I tried using @raglannyc fix X-WR-TIMEZONE:America/New_York', to the .ics header (line 83 in ICS.php) and removed \Z except in my case I did America/Los_Angeles and my times were still wrong. Always off by 1 hour.
Any suggestions on how to prevent converting the timestamp to the server's timezone, which doesn't accurately coincide with the Z suffix.

Made a few changes to correct the local timezone issue, add an alarm feature, and cleanup the .ics file a bit. In summary:

  • changed line 44 from const DT_FORMAT = 'Ymd\THis\Z'; to const DT_FORMAT = 'Ymd\THis'; to remove the GMT (zulu) reference
  • added an alarm value to $available_properties array on line 54
  • changed the Build ICS properties loop (line 90) from $props[strtoupper($k . ($k === 'url' ? ';VALUE=URI' : ''))] = $v; to:
    if ( $k === 'url' ) $props['URL;VALUE=URI'] = $v;
    else if ( $k === 'dtstart' || $k === 'dtend' ) $props[strtoupper($k).';TZID=America/Chicago'] = $v; // change TZID to local
    else $props[strtoupper($k)] = $v;
  • changed the Append Properties loop (line 99) from $ics_props[] = "$k:$v"; to if ( $k !== 'ALARM' ) $ics_props[] = "$k:$v";
  • inserted an if clause after the Append Properties loop (at line 102) to add a VALARM block to the event:
    // Add alarm, if indicated
    if ( $props['ALARM'] )
    $ics_props[] = 'BEGIN:VALARM';
    $ics_props[] = 'TRIGGER:-PT' . $props['ALARM'];
    $ics_props[] = 'ACTION:DISPLAY';
    $ics_props[] = 'END:VALARM';

After making these changes the ics.php file, I then went back and adjusted the declaration of the $properties array in the php file that includes ics.php, as follows:
$properties = array(
'summary' => $event_title,
'dtstart' => date ( "M j Y G:i:s", $event_time ),
'dtend' => date ( "M j Y G:i:s", $event_time + $event_duration ),
'description' => $description,
'location' => "$event_venue",
'url' => "$server_protocol$server_domain/path_to_display_event.php",
'alarm' => '30M'

where $event_title is a short (< 32 character) subject for the event, $event_time is the local time of the event as computed by mktime(), $event_duration is the length of the event in seconds (i.e. #hours3600+#minutes60+#seconds), and $event_venue is as detailed a street address as possible, to facilitate google maps finding the location. Note that I'm also always setting the alarm to 30 minutes prior to the event, although this parameter will be omitted from the VEVENT if it's value is an empty string.

The resulting .ics file appears to work correctly in Outlook, Android (Google Calendar), and iOS (Apple iPhone).


Thanks phpHobbyist,
I used your snippet to add alarm string to ics and seems working well. Mine is select value of "none, 15 min, 30 min", checked on iOS.

aidron commented Apr 23, 2017

How put it in to the php foreach to generate multiple events?

Thanks a lot for this. It's exactly what I need to add many many event in iOS calendar. @thanks phpHobbyist for the timezone.
I'm also interesting in generate multiple events. A + tab to add line, and loop the event in one ics files.

Thanks again for this amazing job that work very well.

The ICS.php script above is converting to the server timezone, how do I modify this?

Hi guys, same question as @pawandhanda, how do I add 2 events in one ICS?

aditeeau commented Sep 29, 2017

How to give specific folder path for download file ?

Raymond-Naseef commented Oct 18, 2017

Is preg_replace supposed to change '\' into '\\'? If so, I think it needs to be written this way:
return preg_replace('/([\\\\,;])/','\\\$1', $str);
For example:
preg_replace('/([\\\\,;])/','\\\$1', 'a\\b,c;d'); // not sure it should be 'a\b' or 'a\b' for text: not familiar with php using backslash to escape string character after it

Scott-N commented Nov 27, 2017

@phpHobbyist -- Thanks for the timezone modifications. Is there a particular reason that $event_title ('summary') should be less than 32 characters? The ICS spec doesn't mention a 32-char limit, as far as I know.

agdm619 commented Nov 30, 2017

I was able to incorporate this script and it works fine when I create a static html file. See here but if I put this html in an email so users can to add it to there calendar it drops all the hidden property fields when it creates the .ics file. You might say just email them a link to the static page, but this information changes dynamically based on user input on a form.
This is the HTML that is in the email. ( the dates and times change based on user input.)

<form method="post" action="">
  <input type="hidden" name="date_start" value="2018-1-16 9:00AM">
  <input type="hidden" name="date_end" value="2018-1-16 10:00AM">
  <input type="hidden" name="location" value="1333 Kern Street">
  <input type="hidden" name="description" value="Reminder">
  <input type="hidden" name="summary" value="Testing the Summary">
  <input type="hidden" name="url" value="">
  <input type="submit" value="Add to Calendar">

Is seems possible to put the html in an email and have it post because it does, but it seems to be striping all the pertinent info.

Any help with this would be awesome!


agdm619 commented Nov 30, 2017

I was able to get it to work by putting the data in a hyperlink instead of using a form post method. Just had to change the download-ics.php file from $_POST to $_GET and it works great. I've tested in Outlook 2007 and iOS 11+

<a href="{element_19}{element_20}{element_21} 16:00&date_start={element_19}{element_20}{element_21} 15:00&summary={element_1}&location=GPS&description={element_6}">Add to Calendar

pedger commented Jan 17, 2018

Thanks for this, @jakebellacera. Nice job.

Romain commented Jan 19, 2018

I think that you should add a backslash on line 124 to your DateTime class to avoid any conflict: $dt = new \DateTime($timestamp);
Also, for some reason, GMail doesn't recognize the ICS file and doesn't create automatically a snippet with the details of the event.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment