Creating iCalendar Items in C#

10/15/2010

For a while now whenever I've needed to generate a calendar item, I've used my trusty vCalendar code. The vCalendar format was simple, rather straightforward and up until recently, without too many faults. However I ran into a project where they wanted to embed html within the calendar item... You see one of the things about the vCalendar format is it can support Base64, 8bit, etc. in the description field but as far as I can tell there isn't any way to tell the client that the description is HTML. Even exporting an item from outlook seems to strip out any HTML that it finds (bullet points become *, etc.). So looking around I found out that you can embed it in an iCalendar item... Which is pretty much just vCalendar 2.0... So in other words it was simple to add to my existing code base:

   1: /*
   2: 
   3: Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>
   4: 
   5: 
   6: 
   7: Permission is hereby granted, free of charge, to any person obtaining a copy
   8: 
   9: of this software and associated documentation files (the "Software"), to deal
  10: 
  11: in the Software without restriction, including without limitation the rights
  12: 
  13: to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  14: 
  15: copies of the Software, and to permit persons to whom the Software is
  16: 
  17: furnished to do so, subject to the following conditions:
  18: 
  19: 
  20: 
  21: The above copyright notice and this permission notice shall be included in
  22: 
  23: all copies or substantial portions of the Software.
  24: 
  25: 
  26: 
  27: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  28: 
  29: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  30: 
  31: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  32: 
  33: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  34: 
  35: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  36: 
  37: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  38: 
  39: THE SOFTWARE.*/
  40:  
  41:  
  42:  
  43: #region Usings
  44:  
  45: using System;
  46:  
  47: using System.Text;
  48:  
  49: using System.Text.RegularExpressions;
  50:  
  51: #endregion
  52:  
  53:  
  54:  
  55: namespace Utilities.FileFormats
  56:  
  57: {
  58:  
  59:     /// <summary>
  60:  
  61:     /// Creates a VCalendar item
  62:  
  63:     /// </summary>
  64:  
  65:     public class VCalendar
  66:  
  67:     {
  68:  
  69:         #region Constructors
  70:  
  71:  
  72:  
  73:         /// <summary>
  74:  
  75:         /// Constructor
  76:  
  77:         /// </summary>
  78:  
  79:         public VCalendar()
  80:  
  81:         {
  82:  
  83:         }
  84:  
  85:  
  86:  
  87:         #endregion
  88:  
  89:  
  90:  
  91:         #region Properties
  92:  
  93:         /// <summary>
  94:  
  95:         /// The time zone adjustment for the calendar event
  96:  
  97:         /// </summary>
  98:  
  99:         public int TimeZoneAdjustment{get;set;}
 100:  
 101:  
 102:  
 103:         /// <summary>
 104:  
 105:         /// The start time
 106:  
 107:         /// </summary>
 108:  
 109:         public DateTime StartTime{get;set;}
 110:  
 111:  
 112:  
 113:         /// <summary>
 114:  
 115:         /// The end time
 116:  
 117:         /// </summary>
 118:  
 119:         public DateTime EndTime{get;set;}
 120:  
 121:  
 122:  
 123:         /// <summary>
 124:  
 125:         /// The location of the event
 126:  
 127:         /// </summary>
 128:  
 129:         public string Location{get;set;}
 130:  
 131:  
 132:  
 133:         /// <summary>
 134:  
 135:         /// The subject of the item to send
 136:  
 137:         /// </summary>
 138:  
 139:         public string Subject{get;set;}
 140:  
 141:  
 142:  
 143:         /// <summary>
 144:  
 145:         /// The description of the event
 146:  
 147:         /// </summary>
 148:  
 149:         public string Description{get;set;}
 150:  
 151:  
 152:  
 153:         private static readonly Regex STRIP_HTML_REGEX = new Regex("<[^>]*>", RegexOptions.Compiled);
 154:  
 155:  
 156:  
 157:         #endregion
 158:  
 159:  
 160:  
 161:         #region Private Functions
 162:  
 163:  
 164:  
 165:         private string StripHTML(string HTML)
 166:  
 167:         {
 168:  
 169:             try
 170:  
 171:             {
 172:  
 173:                 if (string.IsNullOrEmpty(HTML))
 174:  
 175:                     return string.Empty;
 176:  
 177:  
 178:  
 179:                 HTML = STRIP_HTML_REGEX.Replace(HTML, string.Empty);
 180:  
 181:                 HTML = HTML.Replace("&nbsp;", " ");
 182:  
 183:                 return HTML.Replace("&#160;", string.Empty);
 184:  
 185:             }
 186:  
 187:             catch { throw; }
 188:  
 189:         }
 190:  
 191:  
 192:  
 193:         private bool ContainsHTML(string Input)
 194:  
 195:         {
 196:  
 197:             try
 198:  
 199:             {
 200:  
 201:                 if (string.IsNullOrEmpty(Input))
 202:  
 203:                     return false;
 204:  
 205:  
 206:  
 207:                 return STRIP_HTML_REGEX.IsMatch(Input);
 208:  
 209:             }
 210:  
 211:             catch { throw; }
 212:  
 213:         }
 214:  
 215:  
 216:  
 217:         #endregion
 218:  
 219:  
 220:  
 221:         #region Public Functions
 222:  
 223:  
 224:  
 225:         /// <summary>
 226:  
 227:         /// Returns the VCalendar item
 228:  
 229:         /// </summary>
 230:  
 231:         /// <returns>a string output of the VCalendar item</returns>
 232:  
 233:         public string GetVCalendar()
 234:  
 235:         {
 236:  
 237:             try
 238:  
 239:             {
 240:  
 241:                 StringBuilder FileOutput = new StringBuilder();
 242:  
 243:                 FileOutput.AppendLine("BEGIN:VCALENDAR");
 244:  
 245:                 FileOutput.AppendLine("VERSION:1.0");
 246:  
 247:                 FileOutput.AppendLine("BEGIN:VEVENT");
 248:  
 249:                 StartTime = StartTime.AddHours(-TimeZoneAdjustment);
 250:  
 251:                 EndTime = EndTime.AddHours(-TimeZoneAdjustment);
 252:  
 253:  
 254:  
 255:                 string Start = StartTime.ToString("yyyyMMdd") + "T" + StartTime.ToString("HHmmss");
 256:  
 257:                 string End = EndTime.ToString("yyyyMMdd") + "T" + EndTime.ToString("HHmmss");
 258:  
 259:                 FileOutput.Append("DTStart:").AppendLine(Start);
 260:  
 261:                 FileOutput.Append("DTEnd:").AppendLine(End);
 262:  
 263:                 FileOutput.Append("Location;ENCODING=QUOTED-PRINTABLE:").AppendLine(Location);
 264:  
 265:                 FileOutput.Append("SUMMARY;ENCODING=QUOTED-PRINTABLE:").AppendLine(Subject);
 266:  
 267:                 FileOutput.Append("DESCRIPTION;ENCODING=QUOTED-PRINTABLE:").AppendLine(Description);
 268:  
 269:                 FileOutput.Append("UID:").Append(Start).Append(End).AppendLine(Subject);
 270:  
 271:                 FileOutput.AppendLine("PRIORITY:3");
 272:  
 273:                 FileOutput.AppendLine("End:VEVENT");
 274:  
 275:                 FileOutput.AppendLine("End:VCALENDAR");
 276:  
 277:                 return FileOutput.ToString();
 278:  
 279:             }
 280:  
 281:             catch { throw; }
 282:  
 283:         }
 284:  
 285:  
 286:  
 287:         /// <summary>
 288:  
 289:         /// Returns the ICalendar item
 290:  
 291:         /// </summary>
 292:  
 293:         /// <returns>a string output of the ICalendar item</returns>
 294:  
 295:         public string GetICalendar()
 296:  
 297:         {
 298:  
 299:             try
 300:  
 301:             {
 302:  
 303:                 StringBuilder FileOutput = new StringBuilder();
 304:  
 305:                 FileOutput.AppendLine("BEGIN:VCALENDAR");
 306:  
 307:                 FileOutput.AppendLine("PRODID:-//Craigs Utility Library//EN");
 308:  
 309:                 FileOutput.AppendLine("VERSION:2.0");
 310:  
 311:                 FileOutput.AppendLine("METHOD:PUBLISH");
 312:  
 313:                 StartTime = StartTime.AddHours(-TimeZoneAdjustment);
 314:  
 315:                 EndTime = EndTime.AddHours(-TimeZoneAdjustment);
 316:  
 317:                 string Start = StartTime.ToString("yyyyMMdd") + "T" + StartTime.ToString("HHmmss");
 318:  
 319:                 string End = EndTime.ToString("yyyyMMdd") + "T" + EndTime.ToString("HHmmss");
 320:  
 321:                 FileOutput.AppendLine("BEGIN:VEVENT");
 322:  
 323:                 FileOutput.AppendLine("CLASS:PUBLIC");
 324:  
 325:                 FileOutput.Append("CREATED:").AppendLine(DateTime.Now.ToString("yyyyMMddTHHmmssZ"));
 326:  
 327:                 FileOutput.AppendLine(StripHTML(Description.Replace("<br />", "\\n")));
 328:  
 329:                 FileOutput.Append("DTEND:").AppendLine(Start);
 330:  
 331:                 FileOutput.Append("DTSTART:").AppendLine(End);
 332:  
 333:                 FileOutput.Append("LOCATION:").AppendLine(Location);
 334:  
 335:                 FileOutput.Append("SUMMARY;LANGUAGE=en-us:").AppendLine(Subject);
 336:  
 337:                 FileOutput.Append("UID:").Append(Start).Append(End).AppendLine(Subject);
 338:  
 339:                 if (ContainsHTML(Description))
 340:  
 341:                 {
 342:  
 343:                     FileOutput.Append("X-ALT-DESC;FMTTYPE=text/html:").AppendLine(Description.Replace("\n", ""));
 344:  
 345:                 }
 346:  
 347:                 else
 348:  
 349:                 {
 350:  
 351:                     FileOutput.Append("DESCRIPTION:").AppendLine(Description);
 352:  
 353:                 }
 354:  
 355:                 FileOutput.AppendLine("BEGIN:VALARM");
 356:  
 357:                 FileOutput.AppendLine("TRIGGER:-PT15M");
 358:  
 359:                 FileOutput.AppendLine("ACTION:DISPLAY");
 360:  
 361:                 FileOutput.AppendLine("DESCRIPTION:Reminder");
 362:  
 363:                 FileOutput.AppendLine("END:VALARM");
 364:  
 365:                 FileOutput.AppendLine("END:VEVENT");
 366:  
 367:                 FileOutput.AppendLine("END:VCALENDAR");
 368:  
 369:                 return FileOutput.ToString();
 370:  
 371:             }
 372:  
 373:             catch { throw; }
 374:  
 375:         }
 376:  
 377:  
 378:  
 379:         /// <summary>
 380:  
 381:         /// Returns the HCalendar item
 382:  
 383:         /// </summary>
 384:  
 385:         /// <returns>A string output of the HCalendar item</returns>
 386:  
 387:         public string GetHCalendar()
 388:  
 389:         {
 390:  
 391:             try
 392:  
 393:             {
 394:  
 395:                 StringBuilder Output = new StringBuilder();
 396:  
 397:                 Output.Append("<div class=\"vevent\">")
 398:  
 399:                     .Append("<div class=\"summary\">").Append(Subject).Append("</div>")
 400:  
 401:                     .Append("<div>Date: <abbr class=\"dtstart\" title=\"")
 402:  
 403:                     .Append(StartTime.ToString("MM-dd-yyyy hh:mm tt")).Append("\">")
 404:  
 405:                     .Append(StartTime.ToString("MMMM dd, yyyy hh:mm tt")).Append("</abbr> to ")
 406:  
 407:                     .Append("<abbr class=\"dtend\" title=\"").Append(EndTime.ToString("MM-dd-yyyy hh:mm tt"))
 408:  
 409:                     .Append("\">");
 410:  
 411:                 if (EndTime.Year != StartTime.Year)
 412:  
 413:                 {
 414:  
 415:                     Output.Append(EndTime.ToString("MMMM dd, yyyy hh:mm tt"));
 416:  
 417:                 }
 418:  
 419:                 else if (EndTime.Month != StartTime.Month)
 420:  
 421:                 {
 422:  
 423:                     Output.Append(EndTime.ToString("MMMM dd hh:mm tt"));
 424:  
 425:                 }
 426:  
 427:                 else if (EndTime.Day != StartTime.Day)
 428:  
 429:                 {
 430:  
 431:                     Output.Append(EndTime.ToString("dd hh:mm tt"));
 432:  
 433:                 }
 434:  
 435:                 else
 436:  
 437:                 {
 438:  
 439:                     Output.Append(EndTime.ToString("hh:mm tt"));
 440:  
 441:                 }
 442:  
 443:                 Output.Append("</abbr></div>");
 444:  
 445:                 Output.Append("<div>Location: <span class=\"location\">").Append(Location).Append("</span></div>");
 446:  
 447:                 Output.Append("<div class=\"description\">").Append(Description).Append("</div>");
 448:  
 449:                 Output.Append("</div>");
 450:  
 451:                 return Output.ToString();
 452:  
 453:             }
 454:  
 455:             catch { throw; }
 456:  
 457:         }
 458:  
 459:  
 460:  
 461:         #endregion
 462:  
 463:     }
 464:  
 465: }

The code above is actually the same as my previous vCalendar code with a couple of functions added. Not much needed to be changed (1 public function to generate an iCalendar item instead of a vCalendar item and 2 private functions to check if the description is HTML and strip it out in certain situations). But that's all there is to it. So take a look, try it out, leave feedback, and happy coding.



Comments