Create a gist now

Instantly share code, notes, and snippets.

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

PHP to ICS

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.

index.html

<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="http://example.com">
  <input type="submit" value="Add to Calendar">
</form>

download-ics.php

<?php

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();
<?php
/**
* 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(
'description',
'dtend',
'dtstart',
'location',
'summary',
'url'
);
public function __construct($props) {
$this->set($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(
'BEGIN:VCALENDAR',
'VERSION:2.0',
'PRODID:-//hacksw/handcal//NONSGML v1.0//EN',
'CALSCALE:GREGORIAN',
'BEGIN:VEVENT'
);
// 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);
break;
default:
$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);
}
}
@danbirlem

This comment has been minimized.

Show comment
Hide comment
@danbirlem

danbirlem Oct 20, 2010

VERY NICE.

VERY NICE.

@vtedesco

This comment has been minimized.

Show comment
Hide comment
@vtedesco

vtedesco May 29, 2013

Good Job soldier !

Good Job soldier !

@eduardo-marcolino

This comment has been minimized.

Show comment
Hide comment

Good job!

@ConnyOnny

This comment has been minimized.

Show comment
Hide comment
@ConnyOnny

ConnyOnny Jul 1, 2013

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 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.

@DanielWarnaar

This comment has been minimized.

Show comment
Hide comment
@DanielWarnaar

DanielWarnaar Jul 5, 2013

Thanks, exactly what i was looking for!

Thanks, exactly what i was looking for!

@jakebellacera

This comment has been minimized.

Show comment
Hide comment
@jakebellacera

jakebellacera 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 ;)

Owner

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

This comment has been minimized.

Show comment
Hide comment
@jakebellacera

jakebellacera 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.

Owner

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.

@EdgeMarketing

This comment has been minimized.

Show comment
Hide comment
@EdgeMarketing

EdgeMarketing Jul 16, 2013

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

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

@jakebellacera

This comment has been minimized.

Show comment
Hide comment
@jakebellacera

jakebellacera 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.

Owner

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.

@Sprungfeld

This comment has been minimized.

Show comment
Hide comment
@Sprungfeld

Sprungfeld Aug 3, 2013

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?

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

This comment has been minimized.

Show comment
Hide comment
@jakebellacera

jakebellacera 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.

Owner

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

This comment has been minimized.

Show comment
Hide comment
@jmarus

jmarus Sep 18, 2013

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

jmarus commented Sep 18, 2013

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

@MacTEC

This comment has been minimized.

Show comment
Hide comment
@MacTEC

MacTEC 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".

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

This comment has been minimized.

Show comment
Hide comment
@jakebellacera

jakebellacera Oct 10, 2013

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

Owner

jakebellacera commented Oct 10, 2013

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

@jakebellacera

This comment has been minimized.

Show comment
Hide comment
@jakebellacera

jakebellacera 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).

Owner

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

This comment has been minimized.

Show comment
Hide comment
@gbarcun

gbarcun 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:
BEGIN:VEVENT
UID:20131016T115312EEST-65023B6WBG
DTSTAMP:20131016T085312Z
CATEGORIES:Court
DESCRIPTION:some description
DTSTART:20131009T090000Z
DTEND:20131009T100000Z
LOCATION:home town
SUMMARY: some summary
END:VEVENT

plus, I also hav the following as header:
X-WR-TIMEZONE:Europe/Bucharest

Do you have any advice?

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:
BEGIN:VEVENT
UID:20131016T115312EEST-65023B6WBG
DTSTAMP:20131016T085312Z
CATEGORIES:Court
DESCRIPTION:some description
DTSTART:20131009T090000Z
DTEND:20131009T100000Z
LOCATION:home town
SUMMARY: some summary
END:VEVENT

plus, I also hav the following as header:
X-WR-TIMEZONE:Europe/Bucharest

Do you have any advice?

@rappscott

This comment has been minimized.

Show comment
Hide comment
@rappscott

rappscott Jan 10, 2014

@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.

@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

This comment has been minimized.

Show comment
Hide comment
@ioane

ioane 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 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.

@TorbenKoehn

This comment has been minimized.

Show comment
Hide comment
@TorbenKoehn

TorbenKoehn Jun 10, 2014

@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.

@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.

@jacquelineflemming

This comment has been minimized.

Show comment
Hide comment
@jacquelineflemming

jacquelineflemming Jul 10, 2014

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"

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"

@pawandhanda

This comment has been minimized.

Show comment
Hide comment
@pawandhanda

pawandhanda Aug 8, 2014

How we can write 2 events in the ics file . i tried the below , but not working:
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
DTSTART:20140808T031200Z
DTEND:20140808T041200Z
UID:53e49c0e7f8dd
DTSTAMP:20140808T031200Z
LOCATION:At paty hall
DESCRIPTION:sdpfjosdfoids uf dsoifd
URL;VALUE=URI:http://pawandhanda.com
SUMMARY:NEW EVENT
END:VEVENT

BEGIN:VEVENT
DTSTART:20140809T031200Z
DTEND:20140809T041200Z
UID:53e49c0e7f8dd
DTSTAMP:20140809T031200Z
LOCATION:At paty hall
DESCRIPTION:sdpfjosdfoids uf dsoifd
URL;VALUE=URI:http://pawandhanda.com
SUMMARY:NEW EVENT
END:VEVENT
END:VCALENDAR

How we can write 2 events in the ics file . i tried the below , but not working:
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
DTSTART:20140808T031200Z
DTEND:20140808T041200Z
UID:53e49c0e7f8dd
DTSTAMP:20140808T031200Z
LOCATION:At paty hall
DESCRIPTION:sdpfjosdfoids uf dsoifd
URL;VALUE=URI:http://pawandhanda.com
SUMMARY:NEW EVENT
END:VEVENT

BEGIN:VEVENT
DTSTART:20140809T031200Z
DTEND:20140809T041200Z
UID:53e49c0e7f8dd
DTSTAMP:20140809T031200Z
LOCATION:At paty hall
DESCRIPTION:sdpfjosdfoids uf dsoifd
URL;VALUE=URI:http://pawandhanda.com
SUMMARY:NEW EVENT
END:VEVENT
END:VCALENDAR

@sergio-bobillier

This comment has been minimized.

Show comment
Hide comment
@sergio-bobillier

sergio-bobillier Jan 22, 2015

@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)

@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)

@theranch

This comment has been minimized.

Show comment
Hide comment
@theranch

theranch Feb 21, 2015

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?
BEGIN:VEVENT
DTEND:
UID:
DTSTAMP:
LOCATION:
DESCRIPTION:
URL;VALUE=URI:
SUMMARY:
DTSTART:
END:VEVENT

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?
BEGIN:VEVENT
DTEND:
UID:
DTSTAMP:
LOCATION:
DESCRIPTION:
URL;VALUE=URI:
SUMMARY:
DTSTART:
END:VEVENT

@furkanmustafa

This comment has been minimized.

Show comment
Hide comment
@furkanmustafa

furkanmustafa Apr 6, 2015

Thanks for the nice work.
Converted into a class here => https://gist.github.com/furkanmustafa/d9f62450bf7db9b825e3

Thanks for the nice work.
Converted into a class here => https://gist.github.com/furkanmustafa/d9f62450bf7db9b825e3

@kwoot

This comment has been minimized.

Show comment
Hide comment
@kwoot

kwoot Sep 11, 2015

You made my day. :-)

kwoot commented Sep 11, 2015

You made my day. :-)

@vaultdweller123

This comment has been minimized.

Show comment
Hide comment
@vaultdweller123

vaultdweller123 Oct 9, 2015

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

UID:

must be...

UID:

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

UID:

must be...

UID:

@ADoebeling

This comment has been minimized.

Show comment
Hide comment
@ADoebeling

ADoebeling Apr 12, 2016

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

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

@Jany-M

This comment has been minimized.

Show comment
Hide comment
@Jany-M

Jany-M 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!

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

This comment has been minimized.

Show comment
Hide comment
@jakebellacera

jakebellacera Aug 17, 2016

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

Owner

jakebellacera commented Aug 17, 2016

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

@Malatesh144

This comment has been minimized.

Show comment
Hide comment
@Malatesh144

Malatesh144 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 messge to the perticular user whenever an event occurs. Could you help me in that case

@Malatesh144

This comment has been minimized.

Show comment
Hide comment
@Malatesh144

Malatesh144 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

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

@developius

This comment has been minimized.

Show comment
Hide comment
@developius

developius Sep 28, 2016

Fantastic, thanks very much!

Fantastic, thanks very much!

@okierie

This comment has been minimized.

Show comment
Hide comment
@okierie

okierie Oct 27, 2016

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

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

@adeelaak

This comment has been minimized.

Show comment
Hide comment
@adeelaak

adeelaak Nov 22, 2016

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?

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?

@cialtronale

This comment has been minimized.

Show comment
Hide comment
@cialtronale

cialtronale Feb 10, 2017

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.

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.

@kstawiski

This comment has been minimized.

Show comment
Hide comment
@kstawiski

kstawiski Feb 21, 2017

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 :)

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 :)

@raglannyc

This comment has been minimized.

Show comment
Hide comment
@raglannyc

raglannyc Feb 24, 2017

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 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.

@toshihashimoto

This comment has been minimized.

Show comment
Hide comment
@toshihashimoto

toshihashimoto Mar 2, 2017

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!!

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!!

@peopzen

This comment has been minimized.

Show comment
Hide comment
@peopzen

peopzen 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);

peopzen 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

This comment has been minimized.

Show comment
Hide comment
@HugoAcosta

HugoAcosta Mar 13, 2017

thanks raglannyc, I had that problem too...

There's a way to manage ATTENDEES too? Thanks

HugoAcosta commented Mar 13, 2017

thanks raglannyc, I had that problem too...

There's a way to manage ATTENDEES too? Thanks

@LeonelLeonel

This comment has been minimized.

Show comment
Hide comment
@LeonelLeonel

LeonelLeonel Mar 21, 2017

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.

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.

@phpHobbyist

This comment has been minimized.

Show comment
Hide comment
@phpHobbyist

phpHobbyist Mar 23, 2017

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).

Cheers!

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).

Cheers!

@toshihashimoto

This comment has been minimized.

Show comment
Hide comment
@toshihashimoto

toshihashimoto Apr 3, 2017

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.

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

This comment has been minimized.

Show comment
Hide comment
@aidron

aidron Apr 23, 2017

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

aidron commented Apr 23, 2017

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

@hawaien88

This comment has been minimized.

Show comment
Hide comment
@hawaien88

hawaien88 May 26, 2017

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.

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.

@LeonelLeonel

This comment has been minimized.

Show comment
Hide comment
@LeonelLeonel

LeonelLeonel Jul 9, 2017

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

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

@Jeremuelle

This comment has been minimized.

Show comment
Hide comment
@Jeremuelle

Jeremuelle Sep 11, 2017

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

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

@aditeeau

This comment has been minimized.

Show comment
Hide comment
@aditeeau

aditeeau Sep 29, 2017

How to give specific folder path for download file ?

aditeeau commented Sep 29, 2017

How to give specific folder path for download file ?

@Raymond-Naseef

This comment has been minimized.

Show comment
Hide comment
@Raymond-Naseef

Raymond-Naseef 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

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

This comment has been minimized.

Show comment
Hide comment
@Scott-N

Scott-N 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.

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

This comment has been minimized.

Show comment
Hide comment
@agdm619

agdm619 Nov 30, 2017

I was able to incorporate this script and it works fine when I create a static html file. See here https://webapps.genprod.com/wa/cal/cal.html 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="https://webapps.genprod.com/wa/cal/download-ics.php">
  <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="http://www.genprod.com">
  <input type="submit" value="Add to Calendar">
</form>

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!

Thanks,
Aaron

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 https://webapps.genprod.com/wa/cal/cal.html 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="https://webapps.genprod.com/wa/cal/download-ics.php">
  <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="http://www.genprod.com">
  <input type="submit" value="Add to Calendar">
</form>

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!

Thanks,
Aaron

@agdm619

This comment has been minimized.

Show comment
Hide comment
@agdm619

agdm619 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="https://webapps.genprod.com/wa/cal/download-ics.php?date_end={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
</a>

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="https://webapps.genprod.com/wa/cal/download-ics.php?date_end={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
</a>
@pedger

This comment has been minimized.

Show comment
Hide comment
@pedger

pedger Jan 17, 2018

Thanks for this, @jakebellacera. Nice job.

pedger commented Jan 17, 2018

Thanks for this, @jakebellacera. Nice job.

@Romain

This comment has been minimized.

Show comment
Hide comment
@Romain

Romain Jan 19, 2018

Thanks!
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.

Romain commented Jan 19, 2018

Thanks!
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.

@mlevesque1975

This comment has been minimized.

Show comment
Hide comment
@mlevesque1975

mlevesque1975 Mar 3, 2018

I'm having an issue with calling this from within another framework.
Everything works except the ics file is polluted with a bunch of html code from the framework.
If I clear it all out, the ics works correctly. I can't figure out how this stray html is getting injected into the stream
Any suggestions?

I'm collecting the variables in my main script and then calling a separate php script file to create the download.

Called like this:
create_ical($start_datetime,$end_datetime,$appt_title,$activity_note);

function is in this file:

'Online', 'description' => $activity_note, 'dtstart' => $start_datetime, 'dtend' => $end_datetime, 'summary' => $appt_title, 'url' => 'www.mydomain.com')); header('Content-Type: text/calendar; charset=utf-8'); header('Content-Disposition: attachment; filename=invite.ics'); $ics_file_contents = $ics->to_string(); echo $ics_file_contents; return; } ?>

I'm having an issue with calling this from within another framework.
Everything works except the ics file is polluted with a bunch of html code from the framework.
If I clear it all out, the ics works correctly. I can't figure out how this stray html is getting injected into the stream
Any suggestions?

I'm collecting the variables in my main script and then calling a separate php script file to create the download.

Called like this:
create_ical($start_datetime,$end_datetime,$appt_title,$activity_note);

function is in this file:

'Online', 'description' => $activity_note, 'dtstart' => $start_datetime, 'dtend' => $end_datetime, 'summary' => $appt_title, 'url' => 'www.mydomain.com')); header('Content-Type: text/calendar; charset=utf-8'); header('Content-Disposition: attachment; filename=invite.ics'); $ics_file_contents = $ics->to_string(); echo $ics_file_contents; return; } ?>
@shaunw

This comment has been minimized.

Show comment
Hide comment
@shaunw

shaunw Mar 10, 2018

Thanks @raglannyc

removing /Z from line 44 and adding 'X-WR-TIMEZONE:America/New_York', to line 83 worked like a charm for my event time issues!

shaunw commented Mar 10, 2018

Thanks @raglannyc

removing /Z from line 44 and adding 'X-WR-TIMEZONE:America/New_York', to line 83 worked like a charm for my event time issues!

@asoki

This comment has been minimized.

Show comment
Hide comment
@asoki

asoki Mar 12, 2018

Hi, which license has this script? if not defined, i would prefer MIT or BSD. Thanks

asoki commented Mar 12, 2018

Hi, which license has this script? if not defined, i would prefer MIT or BSD. Thanks

@taskrunnerboy

This comment has been minimized.

Show comment
Hide comment
@taskrunnerboy

taskrunnerboy Apr 4, 2018

is there a possible way to add attendees to the .ics file , can't seems to be a way to do so ?
any possible suggestions ?

is there a possible way to add attendees to the .ics file , can't seems to be a way to do so ?
any possible suggestions ?

@ppundirgys

This comment has been minimized.

Show comment
Hide comment
@ppundirgys

ppundirgys May 9, 2018

Thanks in Advance.
Dear jakebellacera,
I am using your code it's working fine for each events. But when i opened downloaded .ics file in MS Office then Date time is not correct while in libre office it is fine.
Any Suggestion.

ppundirgys commented May 9, 2018

Thanks in Advance.
Dear jakebellacera,
I am using your code it's working fine for each events. But when i opened downloaded .ics file in MS Office then Date time is not correct while in libre office it is fine.
Any Suggestion.

@kishancs

This comment has been minimized.

Show comment
Hide comment
@kishancs

kishancs Jun 27, 2018

To add multiple events use below code, i created new class which is not depend on this existing class, still lots of chance for optimization on this

class ics_events
{
const DT_FORMAT = 'Ymd\THis'; //define date format here
public $events;

function ics_events($events)
{
	
	if(count($events)>0)	
	{
		for($p=0;$p<=count($events)-1;$p++)
		{
			//echo '<br>p is:'.$p;
			foreach($events[$p] as $key => $val) 
			{
			  $events[$p][$key] = $this->sanitize_val($val, $key);
			}			
		}
	}
	$this->events=$events;
	
}

function prepare()
{
	$cp=array();
	if(count($this->events)>0)	
	{
	$cp[]= 'BEGIN:VCALENDAR';
	$cp[]= 'VERSION:2.0';
	$cp[]= 'PRODID:-//hacksw/handcal//NONSGML v1.0//EN';
	$cp[]= 'CALSCALE:GREGORIAN';

		for($p=0;$p<=count($this->events)-1;$p++)
		{
			$cp[]='BEGIN:VEVENT';
			foreach($this->events[$p] as $key => $val) 
			{
			  
			  $cp[]= strtoupper($key).':'.$val;
			 
			}
			  $cp[]= 'END:VEVENT';			
		}
		$cp[]='END:VCALENDAR';

	}
	
	echo "<pre>";
	print_r($cp);
	
	return implode("\r\n", $cp);

}

private function sanitize_val($val, $key = false) 
{
	switch($key) 
	{
  		case 'dtend':
        case 'dtstamp':
  		case 'dtstart':
	        $val = $this->format_timestamp($val);
    		break;
      default:
	    $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);
}

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

$sk='';
$events=array();

$events[]=array(
'location' => 'ahemdabad',
'description' => 'Desc1',
'dtstart' => '2018-06-30T153000',
'dtend' => '2018-06-30T183500',
'summary' => 'Summary By Rajesh Patel',
'url' => ''
);

$events[]=array(
'location' => 'Surat',
'description' => 'Desc12',
'dtstart' => '2018-06-30T090000',
'dtend' => '2018-06-30T103500',
'summary' => 'Summary By Raj Patel',
'url' => ''
);

$ics = new ics_events($events);
echo $ics->prepare();
unset($ics);
?>

kishancs commented Jun 27, 2018

To add multiple events use below code, i created new class which is not depend on this existing class, still lots of chance for optimization on this

class ics_events
{
const DT_FORMAT = 'Ymd\THis'; //define date format here
public $events;

function ics_events($events)
{
	
	if(count($events)>0)	
	{
		for($p=0;$p<=count($events)-1;$p++)
		{
			//echo '<br>p is:'.$p;
			foreach($events[$p] as $key => $val) 
			{
			  $events[$p][$key] = $this->sanitize_val($val, $key);
			}			
		}
	}
	$this->events=$events;
	
}

function prepare()
{
	$cp=array();
	if(count($this->events)>0)	
	{
	$cp[]= 'BEGIN:VCALENDAR';
	$cp[]= 'VERSION:2.0';
	$cp[]= 'PRODID:-//hacksw/handcal//NONSGML v1.0//EN';
	$cp[]= 'CALSCALE:GREGORIAN';

		for($p=0;$p<=count($this->events)-1;$p++)
		{
			$cp[]='BEGIN:VEVENT';
			foreach($this->events[$p] as $key => $val) 
			{
			  
			  $cp[]= strtoupper($key).':'.$val;
			 
			}
			  $cp[]= 'END:VEVENT';			
		}
		$cp[]='END:VCALENDAR';

	}
	
	echo "<pre>";
	print_r($cp);
	
	return implode("\r\n", $cp);

}

private function sanitize_val($val, $key = false) 
{
	switch($key) 
	{
  		case 'dtend':
        case 'dtstamp':
  		case 'dtstart':
	        $val = $this->format_timestamp($val);
    		break;
      default:
	    $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);
}

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

$sk='';
$events=array();

$events[]=array(
'location' => 'ahemdabad',
'description' => 'Desc1',
'dtstart' => '2018-06-30T153000',
'dtend' => '2018-06-30T183500',
'summary' => 'Summary By Rajesh Patel',
'url' => ''
);

$events[]=array(
'location' => 'Surat',
'description' => 'Desc12',
'dtstart' => '2018-06-30T090000',
'dtend' => '2018-06-30T103500',
'summary' => 'Summary By Raj Patel',
'url' => ''
);

$ics = new ics_events($events);
echo $ics->prepare();
unset($ics);
?>

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