<p>Thanks to the starter from khalwat I got this working with the following simplified template code. There do seem to be a few variations on what validates and I got a lot of info from <a href="http://icalendar.org/" rel="noreferrer">http://icalendar.org/</a>
The GEO field doesn't seem to be used by Google calendar or Apple calendar but I'm guessing some calendars out there can use it.</p>

<pre><code>{% spaceless %}
    {% set events = craft.entries.find({section:'events,activities', eventDate : "&gt;= " ~ now.w3cDate() , order:'eventDate'}) %}
    {% header "Content-Type: text/calendar; charset=utf-8" %}
    {% header "Content-Disposition: attachment; filename=calendar.ics" %}
{% endspaceless %}
BEGIN:VCALENDAR
PRODID:-//{{ siteName }}//NONSGML v1.0//EN
CALSCALE:GREGORIAN
X-WR-CALNAME:{{ siteName }}
VERSION:2.0
{% spaceless %}
    {% for entry in events %}
    BEGIN:VEVENT
    UID:{{ entry.id }}@orienteeringclub.org.uk
    DTSTAMP:{{ entry.eventDate.format('Ymd') }}T120101Z
    DTSTART:{{ entry.eventDate.format('Ymd') }}T120101Z
    DTEND:{{ entry.eventDate.format('Ymd') }}T120101Z
    LOCATION:Near {{ entry.map.parts.postal_town }}
    GEO:{{ entry.map.lat|round(6) }};{{ entry.map.lng|round(6) }}
    SUMMARY:Orienteering, {{ entry.eventName }}
    DESCRIPTION:{{ entry.times|striptags }}
    URL:{{ entry.url }}
    END:VEVENT
    {% endfor %}
    END:VCALENDAR
{% endspaceless %}
</code></pre>

CC BY-SA 3.0
2016-08-31T15:04:30.210Z
2016-08-31T15:04:30.210Z
Paul Frost
<p>Thanks for these examples above. To handle an early event start (i.e. 9am) then the time format works only if you have a leading 0 so <strong>H</strong> instead of <strong>G</strong></p>

<pre><code>date(“Hi", “UTC")
</code></pre>

CC BY-SA 3.0
2017-03-24T06:23:30.440Z
2017-03-24T06:23:30.440Z
Simon Clayson
<p>You don't need a plugin.  Here's some example code from an old project of mine.</p>

<pre><code>{% spaceless %}

    {% set thisPackage = "" %}

    {% set packageId = craft.request.getSegment(2) %}
    {% if packageId %}
        {% set thisPackage = craft.entries.section('packages').id(packageId).find()[0] %}
    {% endif %}
    {% set craftAdminId = craft.users.admin('1').first().id %}

    {# -- Do some sanity checking, make sure they have perms #}

    {% if thisPackage %}
        {% set authorInfo = craft.users.id(thisPackage.authorId)[0] %}
        {% set thisCurrency = thisPackage.currency.value %}
        {% if authorInfo.id != currentUser.id and craftAdminId != currentUser.id %}
            {% redirect siteUrl %}
        {% endif %}
    {% else %}
            {% redirect siteUrl %}
    {% endif %}

    {% header "Content-Type: text/calendar; charset=utf-8" %}
    {% header "Content-Disposition: attachment; filename=cal.ics" %}
{% endspaceless %}
BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-WR-CALNAME:{{ thisPackage.packageTitle | raw | escape }}
PRODID:-//tastystakes/tourneycal//NONSGML v1.0//EN
{% spaceless %}
{% for block in thisPackage.packageTournaments %}
BEGIN:VEVENT
UID:{{ thisPackage.id }}{{ block.id }}{{ authorInfo.email }}
DTSTART:{{ block.date.getTimestamp() | date_modify("+3 hours") | date("Ymd", "UTC") }}T{{ block.date.getTimestamp() | date_modify("+3 hours") | date("Gi", "UTC") }}00Z
DTEND:{{ block.date.getTimestamp() | date_modify("+3 hours") | date_modify("+" ~ block.days ~ " days") | date("Ymd", "UTC") }}T{{ block.date.getTimestamp() | date_modify("+3 hours") | date_modify("+" ~ block.days ~ " days") |date("Gi", "UTC") }}00Z
SUMMARY:{{ block.tourneyName }}
DESCRIPTION:{{ block.tourneyName }}
END:VEVENT
{% endfor %}
END:VCALENDAR
{% endspaceless %}
</code></pre>

CC BY-SA 3.0
2016-08-31T15:05:58.473Z
Thanks, I managed to extract out the key parts I needed and got it working. See my edited code below.
Paul Frost
CC BY-SA 3.0
2016-08-31T11:08:21.683Z
2016-08-31T11:08:21.683Z
andrew.welch
<p>I want to create an ICS file that people can download to add events to their calendar. I have done this with ExpressionEngine by creating an xml template and just calling it. Below is the code I have so far but I think the problem is that twig doesn't allow php in a template. I'm guessing that means I need to put this in a plugin, but that's beyond my skills. Any help/advice appreciated.</p>

<pre><code>&lt;?php
$ical = "BEGIN:VCALENDAR
X-WR-TIMEZONE:Europe/London
PRODID:-//Orienteering Club//NONSGML v1.0//EN
CALSCALE:GREGORIAN
X-WR-CALNAME:Orienteering Club
VERSION:2.0
{% set events = craft.entries.find({section:'events,activities', eventDate : "&gt;= " ~ now.w3cDate() , order:'eventDate'}) %}
{% if events %}
{% for entry in events %}
BEGIN:VEVENT
UID:{entry_id}
URL;VALUE=URI:{{ entry.url }}
DTSTAMP:{{ entry.eventDate.format('Ymd') }}
DTSTART:{{ entry.eventDate.format('Ymd') }}
DTEND;VALUE=DATE:{{ entry.eventDate.format('Ymd') }}
LOCATION:{{ entry.eventName }}
SUMMARY:Orienteering,nr.{{ entry.map.parts.postal_town }}
END:VEVENT
{% endfor %}
{% endif %}
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;
?&gt;
</code></pre>

<calendar>
How can I create an ICS calendar file
# How can I create an ICS calendar file

(Asked by: username_0 on 2016-08-31)

I want to create an ICS file that people can download to add events to their calendar. I have done this with ExpressionEngine by creating an xml template and just calling it. Below is the code I have so far but I think the problem is that twig doesn't allow php in a template. I'm guessing that means I need to put this in a plugin, but that's beyond my skills. Any help/advice appreciated.
= " ~ now.w3cDate() , order:'eventDate'}) %}
{% if events %}
{% for entry in events %}
BEGIN:VEVENT
UID:{entry_id}
URL;VALUE=URI:{{ entry.url }}
DTSTAMP:{{ entry.eventDate.format('Ymd') }}
DTSTART:{{ entry.eventDate.format('Ymd') }}
DTEND;VALUE=DATE:{{ entry.eventDate.format('Ymd') }}
LOCATION:{{ entry.eventName }}
SUMMARY:Orienteering,nr.{{ entry.map.parts.postal_town }}
END:VEVENT
{% endfor %}
{% endif %}
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;
?>

**Tags:** calendar


---

## 3 answers

### Answer by username_2 on 2016-08-31. Score: 7 | Accepted answer

You don't need a plugin.  Here's some example code from an old project of mine.
{% spaceless %}

    {% set thisPackage = "" %}

    {% set packageId = craft.request.getSegment(2) %}
    {% if packageId %}
        {% set thisPackage = craft.entries.section('packages').id(packageId).find()[0] %}
    {% endif %}
    {% set craftAdminId = craft.users.admin('1').first().id %}

    {# -- Do some sanity checking, make sure they have perms #}

    {% if thisPackage %}
        {% set authorInfo = craft.users.id(thisPackage.authorId)[0] %}
        {% set thisCurrency = thisPackage.currency.value %}
        {% if authorInfo.id != currentUser.id and craftAdminId != currentUser.id %}
            {% redirect siteUrl %}
        {% endif %}
    {% else %}
            {% redirect siteUrl %}
    {% endif %}

    {% header "Content-Type: text/calendar; charset=utf-8" %}
    {% header "Content-Disposition: attachment; filename=cal.ics" %}
{% endspaceless %}
BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-WR-CALNAME:{{ thisPackage.packageTitle | raw | escape }}
PRODID:-//tastystakes/tourneycal//NONSGML v1.0//EN
{% spaceless %}
{% for block in thisPackage.packageTournaments %}
BEGIN:VEVENT
UID:{{ thisPackage.id }}{{ block.id }}{{ authorInfo.email }}
DTSTART:{{ block.date.getTimestamp() | date_modify("+3 hours") | date("Ymd", "UTC") }}T{{ block.date.getTimestamp() | date_modify("+3 hours") | date("Gi", "UTC") }}00Z
DTEND:{{ block.date.getTimestamp() | date_modify("+3 hours") | date_modify("+" ~ block.days ~ " days") | date("Ymd", "UTC") }}T{{ block.date.getTimestamp() | date_modify("+3 hours") | date_modify("+" ~ block.days ~ " days") |date("Gi", "UTC") }}00Z
SUMMARY:{{ block.tourneyName }}
DESCRIPTION:{{ block.tourneyName }}
END:VEVENT
{% endfor %}
END:VCALENDAR
{% endspaceless %}

> **Comments:**
> **username_0**: Thanks, I managed to extract out the key parts I needed and got it working. See my edited code below.  

---

### Answer by username_0 on 2016-08-31. Score: 5

Thanks to the starter from khalwat I got this working with the following simplified template code. There do seem to be a few variations on what validates and I got a lot of info from http://icalendar.org/
The GEO field doesn't seem to be used by Google calendar or Apple calendar but I'm guessing some calendars out there can use it.
{% spaceless %}
    {% set events = craft.entries.find({section:'events,activities', eventDate : ">= " ~ now.w3cDate() , order:'eventDate'}) %}
    {% header "Content-Type: text/calendar; charset=utf-8" %}
    {% header "Content-Disposition: attachment; filename=calendar.ics" %}
{% endspaceless %}
BEGIN:VCALENDAR
PRODID:-//{{ siteName }}//NONSGML v1.0//EN
CALSCALE:GREGORIAN
X-WR-CALNAME:{{ siteName }}
VERSION:2.0
{% spaceless %}
    {% for entry in events %}
    BEGIN:VEVENT
    UID:{{ entry.id }}@orienteeringclub.org.uk
    DTSTAMP:{{ entry.eventDate.format('Ymd') }}T120101Z
    DTSTART:{{ entry.eventDate.format('Ymd') }}T120101Z
    DTEND:{{ entry.eventDate.format('Ymd') }}T120101Z
    LOCATION:Near {{ entry.map.parts.postal_town }}
    GEO:{{ entry.map.lat|round(6) }};{{ entry.map.lng|round(6) }}
    SUMMARY:Orienteering, {{ entry.eventName }}
    DESCRIPTION:{{ entry.times|striptags }}
    URL:{{ entry.url }}
    END:VEVENT
    {% endfor %}
    END:VCALENDAR
{% endspaceless %}


---

### Answer by username_1 on 2017-03-24. Score: 1

Thanks for these examples above. To handle an early event start (i.e. 9am) then the time format works only if you have a leading 0 so H instead of G
date(“Hi", “UTC")


---

