RSS 2.0 Feed
RSS 2.0


Atom 1.0 Feed
Atom 1.0

  Adding Calendar Items to Outlook or Other Calendar App from a Webpage via iCalendar 


If you have a website that maintains a list of events for users, it is a great idea to allow users to selectively add those events to their own calendar. Using automation from a website with something like Outlook is a bad idea. It would be blocked by the browser's security and your users might use something else for their calendar. Fortunately, many main-stream (most?) calendar applications, such as Outlook, Windows Calendar on Vista, and a whole lot more, support the iCalendar specification. This makes adding items from your applications a breeze.

The iCalendar Specification
The iCalendar spec (see RFC 2445) was created to extend an earlier created vCalendar spec. This spec basically provides a universally accepted way to represent a calendar item in a file, or really, as text. An iCalendar can represent some single generic calendar item, or an Event, To-Do, Journal item, and can even include free/busy schedule information. We are going to focus here on including only a single event in our iCalendar data, although it can also represent multiple items all within the same iCalendar. When expressed as a file, the iCalendar data will typically have an "ics" extension, but really it is just text. It also has a MIME type of "text/calendar"

Scenario
For our scenario, we'll assume we have a website with events on it. For any of these events, we want to allow the user to be able to selectively add an event to their own local calendar. On the display of an event on our website, we'll provide a link that the user can click to add the event to their calendar. Here's a sample of a website that I've used this on to give you an idea:



The user clicks that link and we get some jazz hands and it is added to the user's calendar. Let's take a look at how it all works.

The iCalendar Class
As I mentioned before, the iCalendar data is just text. The thing about it is, it is ugly text. I don't like ugly text. I want to write it once and then forget it ever existed. So, I created a class that does all the ugly text for me to use whenever I need. If you're really wanting to see more of this ugly text then you and the rest of your zombie friends can go nicely over to the RFC and read all you want.

Here's my class:

using System;
using System.Text;
using System.Collections.Generic;

namespace RF.Events
{
public enum PriorityLevel : int
  {
  Normal = 5,
High = 1,
Low = 9
}

public class Event
{
private const string _DATEFORMAT = "yyyyMMdd\\THHmmss\\Z";

public Event() { }

public DateTime StartTime;
public DateTime EndTime;
public string Location = string.Empty;
public string Title = string.Empty;
public string Description = string.Empty;
public bool UseAlarm = true;
public PriorityLevel Priority = PriorityLevel.Normal;
public string Category = string.Empty;

public override string ToString()
{
return Output;
}

public string Output
{
get
 {
StringBuilder sb = new StringBuilder();

sb.Append("BEGIN:VEVENT\n\n");
sb.Append("DTSTART:");
sb.Append(StartTime.ToUniversalTime().ToString(_DATEFORMAT));
sb.Append("\nDTEND:");
sb.Append(EndTime.ToUniversalTime().ToString(_DATEFORMAT));

sb.Append("\nLOCATION:");
sb.Append(Location);

sb.Append("\nCATEGORIES:");
sb.Append(Category);

sb.Append("\nTRANSP:OPAQUE\n");
sb.Append("SEQUENCE:0\n");
sb.AppendFormat("UID:RFCALITEM{0}\n", DateTime.Now.Ticks);
sb.Append("DTSTAMP:");
sb.Append(StartTime.ToUniversalTime().ToString(_DATEFORMAT));

sb.Append("\nDESCRIPTION:");
sb.Append(Description);
sb.Append("\nSUMMARY:");
sb.Append(Title);
sb.Append("\n\nPRIORITY:");
sb.Append((int)Priority);
sb.Append("\nCLASS:PUBLIC\n");

if (UseAlarm)
{
sb.Append("BEGIN:VALARM\n");
sb.Append("TRIGGER:PT15M\n");
sb.Append("ACTION:DISPLAY\n");
sb.Append("DESCRIPTION:Reminder\n");
sb.Append("PRIORITY:5\n");
sb.Append("END:VALARM\n");
}
sb.Append("END:VEVENT\n");

return sb.ToString();
}
}
}

public class iCalendar
{
public iCalendar()
{
this.Events = new List<Event>();
}

public List<Event> Events;

public override string ToString()
{
return this.Output;
}

public string Output
{
get
{
StringBuilder sb = new StringBuilder();

sb.Append("BEGIN:VCALENDAR\n");
sb.Append("PRODID:-//RyanFarley.com//iCalendar Sample MIMEDIR//EN\n");
sb.Append("VERSION:2.0\n");
sb.Append("METHOD:PUBLISH\n");

foreach (Event ev in Events)
sb.Append(ev.Output);

sb.Append("END:VCALENDAR");

return sb.ToString();
}
}
}
}

So, first question. Why the separation of the Event item from the rest of the iCalendar item? We'll get to that. For now, here's how we'll use that class. Let's add it to a handler to send back the iCalendar data in the response. We'll call this handler from the link we provide to the user (Typically we would do something like pass an "event ID" of some kind to it and grab the associated data from a database or something. We'll ignore all that stuff in this example to keep it simple).

Here's our handler code:

<%@ WebHandler Language="C#" Class="CalendarItem" %>

using System;
using System.Web;
using System.Text;
using RF.Events;

public class CalendarItem : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
// Set up the Event item first. This would typically come
// from a database or some object in the application
Event ev = new Event();
ev.Title = "Some Event";
ev.Description = "This is a test event. Yeye.\\nKTHXBYE";
ev.Location = "At ryanfarley.com";
ev.UseAlarm = true;
ev.StartTime = DateTime.Parse("11/1/2008 8:00:00 AM");
ev.EndTime = DateTime.Parse("11/1/2008 10:00:00 AM");
ev.Priority = PriorityLevel.Normal;
ev.Category = "Test Category";

// Now add the event to an iCalendar
iCalendar ical = new iCalendar();
ical.Events.Add(ev);

// Set the content-type of the response and file name
// so Windows knows how to handle the response.
context.Response.ContentType = "text/calendar";
context.Response.AddHeader("content-disposition", "inline;filename=CalendarEvent.ics");

// Write the bytes of the iCalendar item output to the resonse stream
context.Response.BinaryWrite(new System.Text.ASCIIEncoding().GetBytes(ical.Output));
context.Response.End();
}

public bool IsReusable
{
get { return false; }
}
}

If we add that as a hyperlink on a webpage, like this:



The user can click it (this is where we get the jazz hands) and they are prompted with this:




Of course, here they click OK and they are presented with some sort of dialog from whatever app is associated with ics files. On my machine that is Outlook, so I get this:



Cool. The user clicks the Save & Close and it now appears on their calendar. We're not just limited to do this from a webpage either. We could just as easily save the iCalendar.Output to a file and Process.Start it to open it for the user if we're in a Windows app. Or attach it to an e-mail to a user, similar to services like WebEx, GotoMeeting, and other services that send you an e-mail with an iCalendar file attached.

Adding Multiple Events to Create an iCalendar Feed
Now, back to that separation of the Event from the iCalendar class. Why is that? The iCalendar spec indicates that an iCalendar can have one, or multiple events in it. If you've ever added an iCalendar feed to Outlook from your Google Calendar or TripIt, etc, this feed is not an RSS feed or something like that. It is a big long iCalendar file containing multiple events in it. We can create the same with the following code:

        // Create the iCalendar
iCalendar ical = new iCalendar();

// Add the first event and add it to the iCalendar
Event ev = new Event();
ev.Title = "Some Event #1";
ev.Description = "This is a test event #1. Yeye.\\nKTHXBYE";
ev.Location = "At ryanfarley.com";
ev.UseAlarm = true;
ev.StartTime = DateTime.Parse("11/1/2008 8:00:00 AM");
ev.EndTime = DateTime.Parse("11/1/2008 10:00:00 AM");
ev.Priority = PriorityLevel.Normal;
ev.Category = "Test Category";
ical.Events.Add(ev);

// Add the second event and add it to the iCalendar
ev = new Event();
ev.Title = "Some Event #2";
ev.Description = "This is a test event #2. Yeye.\\nKTHXBYE";
ev.Location = "At ryanfarley.com";
ev.UseAlarm = true;
ev.StartTime = DateTime.Parse("11/5/2008 8:00:00 AM");
ev.EndTime = DateTime.Parse("11/5/2008 10:00:00 AM");
ev.Priority = PriorityLevel.High;
ev.Category = "Test Category";
ical.Events.Add(ev);

// ...you get the idea

// Now out iCalendar contains multiple events.
// We can output it all to the response stream.

// Set the content-type of the response and file name
// so Windows knows how to handle the response.
context.Response.ContentType = "text/calendar";
context.Response.AddHeader("content-disposition", "inline;filename=CalendarEvent.ics");

// Write the bytes of the iCalendar item output to the resonse stream
context.Response.BinaryWrite(new System.Text.ASCIIEncoding().GetBytes(ical.Output));
context.Response.End();

What happens now when the user clicks our link? They'll get prompted just as before, but when they click OK to add it, it will add the iCalendar feed as a second calendar in their calendar application. In Outlook it will show as a separate calendar which I can view side-by-side with my existing one or overlay on top of my existing one. In this case we'd also want to include the appropriate 304 response status in case the calendar item hasn't changed so we keep things nice.

So that is it. Not bad work and we've got a reusable class we can use for single item, or complete calendar feeds. Have fun.




                   



Leave a comment below.

Comments

  1. DotNetKicks.com 8/8/2008 1:05 AM
    Gravatar
    You've been kicked (a good thing) - Trackback from DotNetKicks.com
  2. dzone 8/8/2008 1:11 AM
    Gravatar
    Adding Calendar Items to Outlook or Other Calendar App from a Webpage via iCalendar
  3. Mark Dykun 8/8/2008 5:41 AM
    Gravatar
    Ryan, Very cool.

    Thanks for the code.

    I have already added it to my library. I altered it a little to include a bunch of event CTORs and moved the IHttpHandler into a abstact class so that in the future I can plugin in database access if I need.
  4. Otto 8/13/2008 8:31 PM
    Gravatar
    Ryan,

    Nicely done. I built something with this class tonight and ran into issues with calendar descriptions. It turns out that both iCal and vCards want fields that can contain carriage return and line feeds to be Quoted-Printable. The easiest way to get around this is to update your class with the following:

    sb.Append("\nDESCRIPTION;ENCODING=QUOTED-PRINTABLE:");
    if (!string.IsNullOrEmpty(Description))
    sb.Append(Description.Replace("\r\n", "=0D=0A"));
    else
    sb.Append(Description);

    Change the string builder's line for description, then replace the \r\n with =0D=0A and everything should look fine in the ics file.

    I wish I could find a proper method to encode the description field to quoted printable text.

    Either way, thought I'd let you know.
  5. VA Internet 9/11/2008 2:17 AM
    Gravatar
    For those of you who want a winforms version of this,

    FileStream fileStream = File.OpenWrite("CalendarEvent.ics");
    fileStream.Write(new System.Text.ASCIIEncoding().GetBytes(ical.Output), 0, new System.Text.ASCIIEncoding().GetByteCount(ical.Output));
    fileStream.Close();
  6. VA Internet 9/11/2008 2:35 PM
    Gravatar
    if you are adding multiple events from a database, make sure they have a unique ID

    public int iCount = 0;

    sb.AppendFormat("UID:RFCALITEM{0}\n", DateTime.Now.Ticks + iCount);

    Then increment icount on the event add
  7. Steve 1/5/2009 1:41 PM
    Gravatar
    Is it possible to add events to your main Outlook calendar (read, not a separate calendar)?
  8. Ryan Farley 1/5/2009 1:54 PM
    Gravatar
    @Steve,

    If you only include a single calendar event in the ICS file you push out then when the user opens it and then saves it will add to their main/default calendar in Outlook.

    Other than that, you'd have to use Outlook automation from client-side script to do it, but I'd only recommend something like that for internal or intranet sites where you have some level of control over user settings since they would need to add the site as a trusted site to do that (and have the proper settings in their virus scanner app do allow it - many virus scanners would block something like that)

    -Ryan
  9. Sam 1/21/2009 8:36 AM
    Gravatar
    Hi Ryan, thanks a lot for your post it's great. But I have one doubt what about duplicate events. I don't want duplicate events to be in my Outlook calendar. Now I know Outlook ignores UID in .ics file and I am not sure how I can get rid of duplicates when I first import my .ics file into my Outlook...

    If you have any answers to my question, please don't hesitate to drop me an email.

    I'll be looking forward to all your responses...

    Thanks in advance,

    Sam
  10. Ryan Farley 1/21/2009 4:10 PM
    Gravatar
    If you're actually "importing" the ICS into Outlook, you really don't have any control over what Outlook is doing with teh data. If you have the ability to examine the calendar when creating the ICS would be the only option.
  11. James 1/28/2009 4:26 PM
    Gravatar
    Thanks Ryan excellent article! I wondered how could one deal with updates? Rather than a person continually needing to download the ics it would be great to have a feed or icalendar subscription?

    Don't suppose you know how this is done or know of an example somewhere?

    Thanks in advance,

    James
  12. Johnny Tabales 2/6/2009 1:53 PM
    Gravatar
    Very nice. Is there a way to assign a specific color to the category during this process?
  13. PA 3/18/2009 5:01 AM
    Gravatar
    hi, Thanks for this code. gr8.
  14. Ritika 3/29/2009 3:51 PM
    Gravatar
    Hi, i have been trying to use this for adding an event which occurs on multiple days at different times. When i open the iCalendar file and then do a "Save and Close", it just adds the first day to my calendar and not the 2nd one. Please help.
    I am copying here the file that gets created. -


    BEGIN:VCALENDAR
    PRODID:-//RyanFarley.com//iCalendar Sample MIMEDIR//EN
    VERSION:2.0
    METHOD:PUBLISH
    BEGIN:VEVENT

    DTSTART:20090103T210000Z
    DTEND:20090103T230000Z
    LOCATION:test loc
    TRANSP:OPAQUE
    SEQUENCE:0
    UID:RFCALITEM633740031690382498
    DTSTAMP:20090103T210000Z
    DESCRIPTION:Test decs
    SUMMARY:test title

    PRIORITY:5
    CLASS:PUBLIC
    END:VEVENT
    BEGIN:VEVENT

    DTSTART:20090104T210000Z
    DTEND:20090104T230000Z
    LOCATION:test loc
    TRANSP:OPAQUE
    SEQUENCE:0
    UID:RFCALITEM633740031690382499
    DTSTAMP:20090104T210000Z
    DESCRIPTION:Test decs
    SUMMARY:test title

    PRIORITY:5
    CLASS:PUBLIC
    END:VEVENT
    END:VCALENDAR
  15. Rubinho 7/17/2009 12:35 PM
    Gravatar
    Hi,

    Thanks for the great article,
    When i use the UID with the value of my event ID, and the event changed in my website, and after that, i resend the event with the same uid. Will this event be automatically changed ?

    The same question with deleted events, can i resend the ics file to delete the event in the Outlook calendar ?

  16. Laurie 8/10/2009 12:46 PM
    Gravatar
    Thank you for your example code! It got me off the ground in a fraction of the time. I've been looking around and I can't find information on whether or not an event that was imported through an .ICS import can be modified through a subsequent .ICS Import. If you know, could you please let me know...
  17. Ryan Farley 8/10/2009 1:07 PM
    Gravatar
    Rubinho & Laurie,

    I've never seen anything that supports updating an event via an ICS with events using the same UID. AFAIK It's just not supported (although it would depend on the client and how it implements an ICS import). I don't recall ever seeing anything in the iCal RFC about it at all. Think "RSS" for events. Just as you can't modify an RSS post in this way, you can't in an ICS feed.

    -Ryan
  18. Laurie 8/12/2009 1:16 PM
    Gravatar
    This post seems to indicate it's possible. I saw a few other posts out there also where some are having success...

    http://stackoverflow.com/questions/45453/icalendar-and-event-updates-not-working-in-outlook
  19. Ryan Farley 8/12/2009 1:23 PM
    Gravatar
    Hi Laurie,

    Sure it is possible, however it would be up to the specific calendar client to support that since it is not part of the RFC you'll find it will not be standard for all calendar clients.

    -Ryan
  20. Chip 10/27/2009 11:05 AM
    Gravatar
    This may be very useful for our needs if I can make it work...

    However I'm finding that when importing multiple events in a single .ics (thus creating a new calendar) the reminder triggers do not get imported along with it. If I make a single event .ics, the reminder imports along with it.

    I have the following for each of the events:

    BEGIN:VALARM
    TRIGGER:-PT15M
    ACTION:DISPLAY
    DESCRIPTION:Reminder
    END:VALARM

    Any thoughts on that?
  21. Ian 1/20/2010 3:14 PM
    Gravatar
    what do i need to add to the form to make the event an all day event?
  22. GopalRaman 7/15/2010 3:16 AM
    Gravatar
    while downloading ical file in different languages like arabic or russian text. I am getting the following error.

    ???????????
  23. Andrew 9/27/2010 11:19 PM
    Gravatar
    Hi Ryan - great article, really helped me.

    I have one question: have you had any success in adding multiple events to Outlook 2007? Your article demonstrates the method that *should* work, but when I try something similar, only the first event is added. Interestingly, if the output is sent to plain old Windows Calendar (Vista) it works fine. Also if I save the .ics file to disk and Import into Outlook 2007 I get all events.

    A couple of other things that might be useful to others who land here via a search engine:
    1) An event CAN be updated (in Outlook 2007) by using the same UID as the original event and ensuring that the SEQUENCE is incremented. (I noticed a post asking about this.) The RFCs do talk aboout it and there is an article on MSDN as well.
    2) Another option for multiple events: send the .ics file in response to a request formatted such as webcal://SomeSite/SomCalendar.ics The result is that a new calendar is created (in Outlook) and all the events are placed there. Exactly how this appears in a particular client would be a bit variable I suppose, but it works ok in Outlook 2007. The link to the original file is maintained and any updates propogated to the subscribed clients - which may or may not be what you want! For anyone who wants to try this, be aware that I could not get it to work via a Response.Redirect; I ended up requesting the file from the browser - using a PageMethod to create the .ics, then sending the filename as a result, and finally using the javascript OnSuccess() function to call window.location(result); kinda ugly but it works. :)

    Cheers - Andrew
Comments have been closed on this topic.



 

News


Also see my CRM Developer blog

Connect:              


Sponsor

Sections