Export Custom Post Type Events to iCal (.ics) Calendar Format

Creating events in WordPress has become second nature to us. But it wouldn’t have been so clear were it not for the incredible brain of Noel Tock. He wrote a tremendous tutorial series covering creating a calendar system that is actually usable by clients. Best of all, it doesn’t suffer from the bloat we typically see in other event calendar wordpress plugins.

Getting WordPress To Jive with CPT Events and exported into iCal

If you’re new to creating events via custom post types, Noel Tock’s series is well worth the read. He breaks down each section bit by bit. The sheer amount of other developers chiming in within the comments is enough to shed a tear.

Part 1 – Custom Post Types Events

Part 2 – Custom Post Types Events

Part 3 – Integrating iCal with Custom Post Type Events

Part three of this series was a revelation in how to get events to spit out into a format that’s usable by calendars such as Google Calendar, Outlook Cal, or iCalendar. Unfortunately, a few errors were preventing the calendar from working properly for me. Fortunately, like-minded developers gathered in his comments section to try to piece it back together.

My Minor Fixes to Noel Tocks Masterpiece

I compiled those fixes, and included a few of my own into this:

<?php
// see: http://www.noeltock.com/web-design/wordpress/how-to-ical-with-custom-post-types/ for more info
//add to functions.php: load_template( dirname( __FILE__ ) . '/lib/cpt-events-ical.php' );

function tf_events_ical() {
 
// - start collecting output -
ob_start();
 
// - file header -
header('Content-type: text/calendar');
header('Content-Disposition: attachment; filename="ical.ics"');
 
// - content header -
?>
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//<?php echo esc_attr( get_bloginfo( 'name', 'display' ) ); ?>//NONSGML Events //EN
X-WR-CALNAME:<?php echo esc_attr( get_bloginfo( 'name', 'display' ) ); ?> – Events
X-ORIGINAL-URL:<?php echo esc_url( home_url( '/' ) ); ?>/?feed=tf-events-ical
X-WR-CALDESC:<?php echo esc_attr( get_bloginfo( 'name', 'display' ) ); ?> – Upcoming Events
CALSCALE:GREGORIAN

<?php 
// - grab date barrier -
$today6am = strtotime('today 6:00') + ( get_option( 'gmt_offset' ) * 3600 );
//removed $limit in $querystr and set to 500 for the time being (originally: $limit = get_option('pubforce_rss_limit');)

// - query -
global $wpdb;
$querystr = "
    SELECT *
    FROM $wpdb->posts wposts, $wpdb->postmeta metastart, $wpdb->postmeta metaend
    WHERE (wposts.ID = metastart.post_id AND wposts.ID = metaend.post_id)
    AND (metaend.meta_key = 'event_end' )
    AND metastart.meta_key = 'event_end'
    AND wposts.post_type = 'base_events'
    AND wposts.post_status = 'publish'
    ORDER BY metastart.meta_value ASC LIMIT 500
 ";

$events = $wpdb->get_results($querystr, OBJECT);

// - loop -
if ($events):
global $post;
foreach ($events as $post):
setup_postdata($post);

// - custom variables -
$custom = get_post_custom(get_the_ID());
$sd = $custom["event_start"][0];
$ed = $custom["event_end"][0];

// - grab gmt for start -
$gmts = date('Y-m-d H:i:s', $sd);
$gmts = get_gmt_from_date($gmts); // this function requires Y-m-d H:i:s
$gmts = strtotime($gmts);

// - grab gmt for end -
$gmte = date('Y-m-d H:i:s', $ed);
$gmte = get_gmt_from_date($gmte); // this function requires Y-m-d H:i:s
$gmte = strtotime($gmte);

// - Set to UTC ICAL FORMAT -
$stime = date('Ymd\THis\Z', $gmts);
$etime = date('Ymd\THis\Z', $gmte);
// - item output -
?>

BEGIN:VEVENT
DTSTART:<?php echo $stime . "\n";?>
DTEND:<?php echo $etime . "\n";?>
SUMMARY:<?php echo the_title() . "\n"; ?>
DESCRIPTION:<?php echo substr(get_the_excerpt(), 0,50) . "\n"; ?>
END:VEVENT
<?php
endforeach;
else :
endif;
?>
END:VCALENDAR

<?php 

// - full output -
$tfeventsical = ob_get_contents();
ob_end_clean();
echo $tfeventsical;
}
 
function add_tf_events_ical_feed () {
    // - add it to WP RSS feeds -
    add_feed('tf-events-ical', 'tf_events_ical');
}
 
add_action('init','add_tf_events_ical_feed');

?>

The Calendar fixes included:

  1. Changing the iCal title to pull the site title
  2. Use get_the_excerpt() to grab the excerpt without any markup
  3. Remove the $limit from the Querystr as I wasn’t using this custom meta field. A limit of 500 should suffice, but that can be changed if need be.
  4. Added the appropriate line breaks to ensure the ical file is formatted properly to work with Calendar programs.

When ran on my local machine, my .ICS file looks like this. (Feel free to open it in iCal to test it)

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Test//NONSGML Events //EN
X-WR-CALNAME:Test – Events
X-ORIGINAL-URL:http://corbins-macbook-pro-2.local:5757//?feed=tf-events-ical
X-WR-CALDESC:Test – Upcoming Events
CALSCALE:GREGORIAN


BEGIN:VEVENT
DTSTART:20141002T060000Z
DTEND:20141011T060000Z
SUMMARY:Past event
DESCRIPTION:Cras mattis consectetur purus sit amet fermentum. 
END:VEVENT

BEGIN:VEVENT
DTSTART:20141219T113000Z
DTEND:20141015T230000Z
SUMMARY:Test Event
DESCRIPTION:Curabitur blandit tempus porttitor. Aenean eu leo 
END:VEVENT

BEGIN:VEVENT
DTSTART:20141115T060000Z
DTEND:20141019T060000Z
SUMMARY:Business Conference
DESCRIPTION:Aenean eu leo quam. Pellentesque ornare sem lacini
END:VEVENT

BEGIN:VEVENT
DTSTART:20141030T150000Z
DTEND:20141101T050000Z
SUMMARY:Country Music Festival
DESCRIPTION:Aenean eu leo quam. Pellentesque ornare sem lacini
END:VEVENT

BEGIN:VEVENT
DTSTART:20141031T150000Z
DTEND:20141102T020000Z
SUMMARY:Cross Month Event
DESCRIPTION:Fusce dapibus, tellus ac cursus commodo, tortor ma
END:VEVENT

BEGIN:VEVENT
DTSTART:20141209T060000Z
DTEND:20141212T000000Z
SUMMARY:A Food Festival
DESCRIPTION:Aenean eu leo quam. Pellentesque ornare sem lacini
END:VEVENT

BEGIN:VEVENT
DTSTART:20150102T000000Z
DTEND:20150102T020000Z
SUMMARY:Next Year Event
DESCRIPTION:Cras mattis consectetur purus sit amet fermentum. 
END:VEVENT
END:VCALENDAR

A huge thank you to Noel and the folks in the comments of part 3 for sharing this. It’s going to be a tremendous help in some upcoming event based projects!