hCalendar items in C#

11/12/2008

If you've read any of the past topics, you'll note that I've been on a big microformat kick lately (similar to my extender kick, graphics, etc.). And sadly it probably wont stop today. Today I bring you the hCalendar format. The hCalendar format is simply a way of displaying calendar events in a human/computer readable format (and like other microformats, it's basically an HTML friendly version of another format which in this case is vCalendar). It's widely used on a number of websites including MapQuest Local, last.fm, Facebook, etc.

The concept is very simple, we simply start off with some entity (div, span, etc.) that has a class of vevent. Inside that we place the actual data. Dates plus a couple other items are placed within abbr items with the appropriate class name, everything else is placed in whatever node type you want. As far as what the class types are, it's a basic copy from vCalendar. So we have dtstart (start time), dtend (end time), location (location of the event), summary (a basic title for the event usually), url (points to a location where more info about the event can be found usually), description (actual description of the event), etc. So if we do the following:

   1: <div class="vevent">
   2: <span class="summary">An Event</span>
   3: <span class="location">Somewhere</span>
   4: <abbr class="dtstart" title="04/21/2009">April 21, 2009</abbr> to <abbr class="dtend" title="04/22/2009">April 22, 2009</abbr>
   5: <span class="description">This is the description of the event</span>
   6: </div>

We end up with an hCalendar event called An Event that is being held at Somewhere, on April 21 through 22 of 2009, with a basic description of the event. That's all there is to it really. But to help you out, I modified my vCalendar code a bit by adding a function to automatically export the event to an hCalendar item as well.

   1: /*
   2: Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>
   3: 
   4: Permission is hereby granted, free of charge, to any person obtaining a copy
   5: of this software and associated documentation files (the "Software"), to deal
   6: in the Software without restriction, including without limitation the rights
   7: to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   8: copies of the Software, and to permit persons to whom the Software is
   9: furnished to do so, subject to the following conditions:
  10: 
  11: The above copyright notice and this permission notice shall be included in
  12: all copies or substantial portions of the Software.
  13: 
  14: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20: THE SOFTWARE.*/
  21:  
  22: #region Usings
  23: using System;
  24: using System.Text;
  25: using System.Text.RegularExpressions;
  26: #endregion
  27:  
  28: namespace Utilities.FileFormats
  29: {
  30:     /// <summary>
  31:     /// Creates a VCalendar item
  32:     /// </summary>
  33:     public class VCalendar
  34:     {
  35:         #region Constructors
  36:  
  37:         /// <summary>
  38:         /// Constructor
  39:         /// </summary>
  40:         public VCalendar()
  41:         {
  42:         }
  43:  
  44:         #endregion
  45:  
  46:         #region Properties
  47:         /// <summary>
  48:         /// The time zone adjustment for the calendar event
  49:         /// </summary>
  50:         public int TimeZoneAdjustment { get; set; }
  51:  
  52:         /// <summary>
  53:         /// The start time
  54:         /// </summary>
  55:         public DateTime StartTime { get; set; }
  56:  
  57:         /// <summary>
  58:         /// The end time
  59:         /// </summary>
  60:         public DateTime EndTime { get; set; }
  61:  
  62:         /// <summary>
  63:         /// The location of the event
  64:         /// </summary>
  65:         public string Location { get; set; }
  66:  
  67:         /// <summary>
  68:         /// The subject of the item to send
  69:         /// </summary>
  70:         public string Subject { get; set; }
  71:  
  72:         /// <summary>
  73:         /// The description of the event
  74:         /// </summary>
  75:         public string Description { get; set; }
  76:  
  77:         private static readonly Regex STRIP_HTML_REGEX = new Regex("<[^>]*>", RegexOptions.Compiled);
  78:  
  79:         #endregion
  80:  
  81:         #region Private Functions
  82:  
  83:         private string StripHTML(string HTML)
  84:         {
  85:             if (string.IsNullOrEmpty(HTML))
  86:                 return string.Empty;
  87:  
  88:             HTML = STRIP_HTML_REGEX.Replace(HTML, string.Empty);
  89:             HTML = HTML.Replace("&nbsp;", " ");
  90:             return HTML.Replace("&#160;", string.Empty);
  91:         }
  92:  
  93:         private bool ContainsHTML(string Input)
  94:         {
  95:             if (string.IsNullOrEmpty(Input))
  96:                 return false;
  97:  
  98:             return STRIP_HTML_REGEX.IsMatch(Input);
  99:         }
 100:  
 101:         #endregion
 102:  
 103:         #region Public Functions
 104:  
 105:         /// <summary>
 106:         /// Returns the VCalendar item
 107:         /// </summary>
 108:         /// <returns>a string output of the VCalendar item</returns>
 109:         public string GetVCalendar()
 110:         {
 111:             StringBuilder FileOutput = new StringBuilder();
 112:             FileOutput.AppendLine("BEGIN:VCALENDAR");
 113:             FileOutput.AppendLine("VERSION:1.0");
 114:             FileOutput.AppendLine("BEGIN:VEVENT");
 115:             StartTime = StartTime.AddHours(-TimeZoneAdjustment);
 116:             EndTime = EndTime.AddHours(-TimeZoneAdjustment);
 117:  
 118:             string Start = StartTime.ToString("yyyyMMdd") + "T" + StartTime.ToString("HHmmss");
 119:             string End = EndTime.ToString("yyyyMMdd") + "T" + EndTime.ToString("HHmmss");
 120:             FileOutput.Append("DTStart:").AppendLine(Start);
 121:             FileOutput.Append("DTEnd:").AppendLine(End);
 122:             FileOutput.Append("Location;ENCODING=QUOTED-PRINTABLE:").AppendLine(Location);
 123:             FileOutput.Append("SUMMARY;ENCODING=QUOTED-PRINTABLE:").AppendLine(Subject);
 124:             FileOutput.Append("DESCRIPTION;ENCODING=QUOTED-PRINTABLE:").AppendLine(Description);
 125:             FileOutput.Append("UID:").Append(Start).Append(End).AppendLine(Subject);
 126:             FileOutput.AppendLine("PRIORITY:3");
 127:             FileOutput.AppendLine("End:VEVENT");
 128:             FileOutput.AppendLine("End:VCALENDAR");
 129:             return FileOutput.ToString();
 130:         }
 131:  
 132:         /// <summary>
 133:         /// Returns the ICalendar item
 134:         /// </summary>
 135:         /// <returns>a string output of the ICalendar item</returns>
 136:         public string GetICalendar()
 137:         {
 138:             StringBuilder FileOutput = new StringBuilder();
 139:             FileOutput.AppendLine("BEGIN:VCALENDAR");
 140:             FileOutput.AppendLine("PRODID:-//Craigs Utility Library//EN");
 141:             FileOutput.AppendLine("VERSION:2.0");
 142:             FileOutput.AppendLine("METHOD:PUBLISH");
 143:             StartTime = StartTime.AddHours(-TimeZoneAdjustment);
 144:             EndTime = EndTime.AddHours(-TimeZoneAdjustment);
 145:             string Start = StartTime.ToString("yyyyMMdd") + "T" + StartTime.ToString("HHmmss");
 146:             string End = EndTime.ToString("yyyyMMdd") + "T" + EndTime.ToString("HHmmss");
 147:             FileOutput.AppendLine("BEGIN:VEVENT");
 148:             FileOutput.AppendLine("CLASS:PUBLIC");
 149:             FileOutput.Append("CREATED:").AppendLine(DateTime.Now.ToString("yyyyMMddTHHmmssZ"));
 150:             FileOutput.AppendLine(StripHTML(Description.Replace("<br />", "\\n")));
 151:             FileOutput.Append("DTEND:").AppendLine(Start);
 152:             FileOutput.Append("DTSTART:").AppendLine(End);
 153:             FileOutput.Append("LOCATION:").AppendLine(Location);
 154:             FileOutput.Append("SUMMARY;LANGUAGE=en-us:").AppendLine(Subject);
 155:             FileOutput.Append("UID:").Append(Start).Append(End).AppendLine(Subject);
 156:             if (ContainsHTML(Description))
 157:             {
 158:                 FileOutput.Append("X-ALT-DESC;FMTTYPE=text/html:").AppendLine(Description.Replace("\n", ""));
 159:             }
 160:             else
 161:             {
 162:                 FileOutput.Append("DESCRIPTION:").AppendLine(Description);
 163:             }
 164:             FileOutput.AppendLine("BEGIN:VALARM");
 165:             FileOutput.AppendLine("TRIGGER:-PT15M");
 166:             FileOutput.AppendLine("ACTION:DISPLAY");
 167:             FileOutput.AppendLine("DESCRIPTION:Reminder");
 168:             FileOutput.AppendLine("END:VALARM");
 169:             FileOutput.AppendLine("END:VEVENT");
 170:             FileOutput.AppendLine("END:VCALENDAR");
 171:             return FileOutput.ToString();
 172:         }
 173:  
 174:         /// <summary>
 175:         /// Returns the HCalendar item
 176:         /// </summary>
 177:         /// <returns>A string output of the HCalendar item</returns>
 178:         public string GetHCalendar()
 179:         {
 180:             StringBuilder Output = new StringBuilder();
 181:             Output.Append("<div class=\"vevent\">")
 182:                 .Append("<div class=\"summary\">").Append(Subject).Append("</div>")
 183:                 .Append("<div>Date: <abbr class=\"dtstart\" title=\"")
 184:                 .Append(StartTime.ToString("MM-dd-yyyy hh:mm tt")).Append("\">")
 185:                 .Append(StartTime.ToString("MMMM dd, yyyy hh:mm tt")).Append("</abbr> to ")
 186:                 .Append("<abbr class=\"dtend\" title=\"").Append(EndTime.ToString("MM-dd-yyyy hh:mm tt"))
 187:                 .Append("\">");
 188:             if (EndTime.Year != StartTime.Year)
 189:             {
 190:                 Output.Append(EndTime.ToString("MMMM dd, yyyy hh:mm tt"));
 191:             }
 192:             else if (EndTime.Month != StartTime.Month)
 193:             {
 194:                 Output.Append(EndTime.ToString("MMMM dd hh:mm tt"));
 195:             }
 196:             else if (EndTime.Day != StartTime.Day)
 197:             {
 198:                 Output.Append(EndTime.ToString("dd hh:mm tt"));
 199:             }
 200:             else
 201:             {
 202:                 Output.Append(EndTime.ToString("hh:mm tt"));
 203:             }
 204:             Output.Append("</abbr></div>");
 205:             Output.Append("<div>Location: <span class=\"location\">").Append(Location).Append("</span></div>");
 206:             Output.Append("<div class=\"description\">").Append(Description).Append("</div>");
 207:             Output.Append("</div>");
 208:             return Output.ToString();
 209:         }
 210:  
 211:         #endregion
 212:     }
 213: }

The code is documented so it shouldn't be too difficult to figure out hopefully. You'll notice that it has a legal notice at the top of it as well. The reason for this is it's going into the Utility Library that I created (which I'll be moving to CodePlex some time this week). Anyway, download the code, leave feedback, and happy coding.



Comments