1.直接编码方式
遵循rfc5545x协议标准开发,参考链接 https://tools.ietf.org/html/rfc5545,正常遵循此标准开发的Calendar都是兼容的,比如Gmail。
优点:不依赖第三方组件
缺点:实现相对复杂些
2.可以实现功能
发送 修改 取消(取消之后,支持修改,修改之后,状态是未取消状态) 支持添加附件 内容支持html格式 循环功能,支持Daily、Weekly、Monthly、Yearly
3.与Outlook发送的区别:
Organizer暂时在Calendar看不到自己的Appointment,所以本文所介绍的方案比较适合作为系统发送的Appointment的场景。
4.实现代码

1 public static void SendMeeting(ref string appointmentID, MailAddress from, MailAddressCollection to, MailAddressCollection cc, MailAddressCollection bcc, string subject, string body, DateTimeOffset dtStart, DateTimeOffset dtEnd, string location, Collection<Attachment> attachments, MailAddress sender, string smtp, int port, string account, string password, bool async, METHOD method, int utcOffset, RecurType recurType, DateTimeOffset recurEndByDate,bool isRecurEndbyDate,int recurEndAfterOccurences, RecurDaily recurDaily, RecurWeekly recurWeekly,RecurMonthly recurMonthly,RecurYearly recurYearly, string originalSenderAuto = "", string originalMessageAutoID = "", bool isLocalTimeZone = true,bool isAllDay=false) 2 { 3 // CIPPlanner.CIPAce.Configuration.Configuration syscfg = new CIPPlanner.CIPAce.Configuration.Configuration(); 4 if (from == null) from = new MailAddress(ApplicationSetting.SmtpFrom); 5 if (smtp == null) smtp = ApplicationSetting.SmtpServer; 6 if (port <= 0) port = ApplicationSetting.SmtpPort; 7 if (sender == null) sender = new MailAddress(ApplicationSetting.SmtpSender); 8 if (account == null) account = ApplicationSetting.SmtpAccount; 9 if (password == null) password = ApplicationSetting.SmtpPassword; 10 11 MailMessage mm = new MailMessage(); 12 mm.IsBodyHtml = true; 13 mm.Subject = (method==METHOD.Cancel? "Canceled: ":"")+subject; 14 mm.Sender = sender; 15 mm.From = from; 16 // 邮件标头 17 mm.Headers.Add("Content-class", "urn:content-classes:calendarmessage"); 18 19 //add hidden info in mailmessage 20 string hiddenInfo = ""; 21 if (originalSenderAuto != "" && originalMessageAutoID != "") 22 { 23 hiddenInfo = HiddenInfoConsts.KEY + HiddenInfoConsts.SPLIT_FLAG.ToString() + originalSenderAuto + HiddenInfoConsts.SPLIT_FLAG.ToString() + originalMessageAutoID + HiddenInfoConsts.SPLIT_FLAG.ToString() + HiddenInfoConsts.KEY;// HiddenInfoConsts.KEY与HiddenInfoKey.Second为拼接格式,便于后续解析; 24 body += string.Format("<!-- {0} -->", hiddenInfo); 25 mm.Headers.Add(HiddenInfoConsts.KEY, hiddenInfo); 26 mm.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(hiddenInfo, null, "text/plain")); 27 } 28 else 29 { 30 mm.Body = body; 31 } 32 ContentType typeHtml = new ContentType("text/html"); 33 typeHtml.Parameters.Add("charset", "utf-8"); 34 AlternateView viewHtml = AlternateView.CreateAlternateViewFromString(body, typeHtml); 35 mm.AlternateViews.Add(viewHtml); 36 foreach (var item in attachments) 37 { 38 LinkedResource resource = new LinkedResource(item.ContentStream); 39 resource.ContentId = Guid.NewGuid().ToString().Replace("-", "") + DateTime.Now.Ticks.ToString(); 40 resource.ContentType.Name = item.Name;//Name of file 41 resource.TransferEncoding = System.Net.Mime.TransferEncoding.Base64; 42 viewHtml.LinkedResources.Add(resource); 43 } 44 45 ContentType typeCalendar = new ContentType("text/calendar"); 46 //向calendar header添加参数 47 typeCalendar.Parameters.Add("method", method == METHOD.Cancel ? "CANCEL" : "REQUEST");//REQUEST 48 typeCalendar.Parameters.Add("charset", "utf-8"); 49 // typeCalendar.Parameters.Add("name", "meeting.ics"); 50 // 使用vcalendar格式创建邮件的body部分 51 //获取 reccuringParam部分 52 string reccuringParam = ""; 53 if(recurType!=RecurType.NoRecurrence) 54 { 55 reccuringParam = GetRecurringParam(recurType, isRecurEndbyDate, recurEndByDate, recurEndAfterOccurences, recurDaily, recurWeekly, recurMonthly, recurYearly); 56 } 57 if (string.IsNullOrEmpty(ApplicationSetting.emailParam)) 58 { 59 AlternateView viewCalendar = AlternateView.CreateAlternateViewFromString(CalendarGenerate(ref appointmentID, from, to, cc, bcc, subject, body, dtStart, dtEnd, location, method, reccuringParam, isAllDay), typeCalendar); 60 viewCalendar.TransferEncoding = TransferEncoding.SevenBit; 61 mm.AlternateViews.Add(viewCalendar); 62 63 } 64 else 65 { 66 AlternateView viewCalendar = AlternateView.CreateAlternateViewFromString(ApplicationSetting.emailParam, typeCalendar); 67 viewCalendar.TransferEncoding = TransferEncoding.SevenBit; 68 mm.AlternateViews.Add(viewCalendar); 69 } 70 71 72 if (to != null) 73 { 74 foreach (MailAddress ma in to) 75 { 76 mm.To.Add(ma); 77 } 78 } 79 if (cc != null) 80 { 81 foreach (MailAddress ma in cc) 82 { 83 mm.CC.Add(ma); 84 } 85 } 86 if (bcc != null) 87 { 88 foreach (MailAddress ma in bcc) 89 { 90 mm.Bcc.Add(ma); 91 } 92 } 93 //if (attachments != null) 94 //{ 95 // foreach (Attachment a in attachments) 96 // { 97 // mm.Attachments.Add(a); 98 // } 99 //} 100 101 SmtpClient sc = new SmtpClient(smtp, port); 102 sc.UseDefaultCredentials = false; 103 sc.Credentials = new NetworkCredential(account, password); 104 sc.DeliveryMethod = SmtpDeliveryMethod.Network; 105 sc.EnableSsl = ApplicationSetting.SmtpEnableSsl == "true" || ApplicationSetting.SmtpEnableSsl == "1"; 106 if (!async) 107 { 108 try 109 { 110 sc.Send(mm); 111 } 112 finally 113 { 114 mm.Dispose(); 115 } 116 return; 117 } 118 119 System.Threading.SynchronizationContext context = System.ComponentModel.AsyncOperationManager.SynchronizationContext; 120 try 121 { 122 System.ComponentModel.AsyncOperationManager.SynchronizationContext = new System.Threading.SynchronizationContext(); 123 sc.SendCompleted += new SendCompletedEventHandler(sc_SendCompleted); 124 sc.SendAsync(mm, mm); 125 } 126 finally 127 { 128 System.ComponentModel.AsyncOperationManager.SynchronizationContext = context; 129 //mm.Dispose(); 130 } 131 132 } 133 static string calDateFormat = "yyyyMMddTHHmmssZ"; 134 /// <summary> 135 /// 生成ics文件(为以Outlook主要参考) 136 /// </summary> 137 /// <returns></returns> 138 private static string CalendarGenerate(ref string appointmentID,MailAddress from, MailAddressCollection to, MailAddressCollection cc, MailAddressCollection bcc, string subject, string body, DateTimeOffset dtStart, DateTimeOffset dtEnd, string location, METHOD method,string reccurringParam,bool isAllDay=false) 139 { 140 141 StringBuilder sb = new StringBuilder(); 142 sb.Append("BEGIN:VCALENDAR").Append("\r\n"); 143 //sb.Append("PRODID:").Append("-//Microsoft Corporation//Outlook 12.0 //EN").Append("\r\n"); 144 sb.Append("PRODID:").Append("-//Microsoft Corporation//Outlook 12.0 MIMEDIR//EN").Append("\r\n"); 145 sb.Append("VERSION:2.0").Append("\r\n"); 146 if (method == METHOD.Create || method == METHOD.Update) 147 sb.Append("METHOD:REQUEST").Append("\r\n"); 148 else 149 sb.Append("METHOD:CANCEL").Append("\r\n"); 150 //sb.Append("X-MS-OLK-FORCEINSPECTOROPEN:TRUE").Append("\r\n"); 151 sb.Append("BEGIN:VEVENT").Append("\r\n"); 152 153 if (!string.IsNullOrEmpty(from.Address)) 154 { 155 /// ORGANIZER; CN = "John Smith":mailto: jsmith @example.com 156 sb.Append("ORGANIZER;CN=\"").Append(from.DisplayName).Append("\":mailto" + ":").Append(from.Address).Append("\r\n"); 157 } 158 foreach (var recipient in to) 159 { 160 sb.Append("ATTENDEE;CN=\"" + recipient.DisplayName + "\";ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:" + recipient.Address).Append("\r\n"); 161 } 162 foreach (var recipient in cc) 163 { 164 sb.Append("ATTENDEE;CN=\"" + recipient.DisplayName + "\";ROLE=OPT-PARTICIPANT;RSVP=TRUE:mailto:" + recipient.Address).Append("\r\n"); 165 } 166 foreach (var recipient in bcc) 167 { 168 sb.Append("ATTENDEE;CN=\"" + recipient.DisplayName + "\";CUTYPE=RESOURCE;ROLE=NON-PARTICIPANT:mailto:" + recipient.Address).Append("\r\n"); 169 } 170 171 sb.Append("CLASS:PUBLIC").Append("\r\n"); 172 sb.Append("CREATED:").Append(DateTime.Now.ToUniversalTime().ToString(calDateFormat)).Append("\r\n"); 173 sb.Append("DESCRIPTION:").Append("DESCRIPTION").Append("\r\n"); 174 // string timeZoneId = isLocalTimezone ? GetCurrentTimeZoneName() : GetTimezoneIdbyUTCOffset(utcOffset); 175 // sb.Append("DTEND;TZID="+ timeZoneId + ":").Append(dtEnd.ToString(calDateFormat)).Append("\r\n"); 176 sb.Append("DTSTART:").Append(dtStart.ToUniversalTime().ToString(calDateFormat)).Append("\r\n"); 177 sb.Append("DTEND:").Append(dtEnd.ToUniversalTime().ToString(calDateFormat)).Append("\r\n"); 178 sb.Append("DTSTAMP:").Append(DateTime.Now.ToUniversalTime().ToString(calDateFormat)).Append("\r\n"); 179 //sb.Append("DTSTART;TZID=" + timeZoneId + ":").Append(dtStart.ToString(calDateFormat)).Append("\r\n"); 180 // if() 181 // sb.Append("RRULE:FREQ=DAILY;COUNT=10").Append("\r\n");//recurring WKST=weekday 182 if (!string.IsNullOrEmpty(reccurringParam)) 183 sb.Append(reccurringParam).Append("\r\n"); 184 // sb.Append("RRULE:FREQ=MONTHLY;UNTIL=20191209T020000Z;BYDAY=2MO").Append("\r\n");//recurring BYMONTHDAY=3 不支持-2MO 185 // sb.Append("RRULE:FREQ=WEEKLY;UNTIL=20191209T020000Z;BYDAY=MO").Append("\r\n"); 186 // sb.Append("RRULE:FREQ=MONTHLY;COUNT=10;BYDAY=2MO;BYMONTH=3").Append("\r\n");//recurring BYMONTHDAY=3 不支持-2MO 187 188 // sb.Append("RECURRENCE-ID;VALUE=DATE:"+ DateTime.Now.ToString("yyyyMMdd")).Append("\r\n"); 189 sb.Append("LAST-MODIFIED:").Append(DateTime.Now.ToUniversalTime().ToString(calDateFormat)).Append("\r\n"); 190 sb.Append("LOCATION:").Append(NotNull(location)).Append("\r\n"); 191 sb.Append("PRIORITY:1").Append("\r\n"); 192 //sb.Append("SEQUENCE:0").Append("\r\n"); 193 if(method==METHOD.Cancel) 194 { 195 sb.Append("SUMMARY:").Append("Canceled: " + NotNull(subject)).Append("\r\n"); 196 } 197 else 198 { 199 sb.Append("SUMMARY:").Append(NotNull(subject)).Append("\r\n"); 200 } 201 if (method == METHOD.Create) 202 { 203 ApplicationSetting.Uid = Guid.NewGuid().ToString().Replace("-", "") + Guid.NewGuid().ToString().Replace("-", ""); 204 appointmentID = ApplicationSetting.Uid; 205 } 206 else 207 { 208 ApplicationSetting.Uid = appointmentID; 209 if(string.IsNullOrEmpty(ApplicationSetting.Uid)) 210 { 211 throw new Exception("appointmentID can not be empty when update or cancel"); 212 } 213 } 214 sb.Append("SEQUENCE:0").Append("\r\n"); 215 sb.Append("UID:").Append(ApplicationSetting.Uid).Append("\r\n"); 216 sb.Append("X-ALT-DESC;FMTTYPE=text/html:").Append(body).Append("\r\n"); 217 if (method == METHOD.Cancel) 218 { sb.Append("STATUS:CANCELLED").Append("\r\n"); } 219 else 220 { sb.Append("STATUS:CONFIRMED").Append("\r\n"); } 221 sb.Append("TRANSP:OPAQUE").Append("\r\n"); 222 sb.Append("X-MICROSOFT-CDO-BUSYSTATUS:BUSY").Append("\r\n"); 223 sb.Append("X-MICROSOFT-CDO-IMPORTANCE:1").Append("\r\n"); 224 sb.Append("X-MICROSOFT-CDO-INSTTYPE:0").Append("\r\n"); 225 sb.Append("X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY").Append("\r\n"); 226 sb.Append("X-MICROSOFT-CDO-ALLDAYEVENT:"+ isAllDay.ToString().ToUpper()).Append("\r\n"); 227 sb.Append("X-MICROSOFT-CDO-OWNERAPPTID:-611620904").Append("\r\n"); 228 sb.Append("X-MICROSOFT-CDO-APPT-SEQUENCE:0").Append("\r\n"); 229 //sb.Append("X-MICROSOFT-CDO-ATTENDEE-CRITICAL-CHANGE:").Append(DateTime.Now.ToUniversalTime().ToString(calDateFormat)).Append("\r\n"); 230 //sb.Append("X-MICROSOFT-CDO-OWNER-CRITICAL-CHANGE:").Append(DateTime.Now.ToUniversalTime().ToString(calDateFormat)).Append("\r\n"); 231 sb.Append("BEGIN:VALARM").Append("\r\n"); 232 sb.Append("ACTION:DISPLAY").Append("\r\n"); 233 sb.Append("DESCRIPTION:REMINDER").Append("\r\n"); 234 sb.Append("TRIGGER;RELATED=START:-PT00H15M00S").Append("\r\n"); 235 sb.Append("END:VALARM").Append("\r\n"); 236 237 sb.Append("END:VEVENT").Append("\r\n"); 238 sb.Append("END:VCALENDAR").Append("\r\n"); 239 return sb.ToString(); 240 } 241 242 static string GetRecurringParam(RecurType freq, bool isEndbyDate, DateTimeOffset endby, int endafterOccurences, RecurDaily dailyEntiy, RecurWeekly weeklyEntity, RecurMonthly monthlyEntiy, RecurYearly yearlyEntity) 243 { 244 if(freq!=RecurType.NoRecurrence) 245 { 246 StringBuilder sb = new StringBuilder(); 247 if(freq==RecurType.DAILY) 248 { 249 sb.Append(GetDailyParam(dailyEntiy)); 250 } 251 else if (freq == RecurType.WEEKLY) 252 { 253 sb.Append(GetWeeklyParam(weeklyEntity)); 254 } 255 else if (freq == RecurType.MONTHLY) 256 { 257 sb.Append(GetMonthlyParam(monthlyEntiy)); 258 } 259 else if (freq == RecurType.YEARLY) 260 { 261 sb.Append(GetYearlyParam(yearlyEntity)); 262 } 263 264 //end by datetime or count 265 if(isEndbyDate) 266 { 267 string utcDateFormat = "yyyyMMddTHHmmssZ"; 268 sb.Append("UNTIL=").Append(endby.ToUniversalTime().ToString(utcDateFormat));// must be utc time 269 } 270 else 271 { 272 sb.Append("COUNT="+ endafterOccurences + ";"); 273 } 274 if(sb[sb.Length-1]==';') 275 { 276 sb.Remove(sb.Length - 1, 1); 277 } 278 return sb.ToString(); 279 } 280 return ""; 281 282 } 283 static StringBuilder GetDailyParam(RecurDaily recurDaily) 284 { 285 StringBuilder sb = new StringBuilder(); 286 sb.Append("RRULE:FREQ=DAILY;"); 287 if (recurDaily.IsEveryWeekday == false) 288 { 289 if (recurDaily.RecurIntervalDays <= 0) 290 { 291 throw new Exception("RecurIntervalDays must more than Zero"); 292 } 293 sb.Append("INTERVAL=" + (recurDaily.RecurIntervalDays) + ";"); 294 } 295 else 296 { 297 sb.Append("BYDAY=MO,TU,WE,TH,FR;"); 298 } 299 return sb; 300 } 301 302 static StringBuilder GetWeeklyParam(RecurWeekly recurWeekly) 303 { 304 StringBuilder sb = new StringBuilder(); 305 sb.Append("RRULE:FREQ=WEEKLY;"); 306 if (recurWeekly.RecurIntervalWeeks > 0) 307 sb.Append("INTERVAL=" + (recurWeekly.RecurIntervalWeeks) + ";"); 308 sb.Append("BYDAY="); 309 List<string> listWeek = new List<string>(); 310 if (recurWeekly.IsSunday) 311 { 312 listWeek.Add("SU"); 313 } 314 if (recurWeekly.IsMonday) 315 { 316 listWeek.Add("MO"); 317 } 318 if (recurWeekly.IsTuesday) 319 { 320 listWeek.Add("TU"); 321 } 322 if (recurWeekly.IsWednesday) 323 { 324 listWeek.Add("WE"); 325 } 326 if (recurWeekly.IsThursday) 327 { 328 listWeek.Add("TH"); 329 } 330 if (recurWeekly.IsFriday) 331 { 332 listWeek.Add("FR"); 333 } 334 if (recurWeekly.IsSaturday) 335 { 336 listWeek.Add("SA"); 337 338 } 339 if (listWeek.Count == 0) 340 { 341 throw new Exception("Must check one day of week"); 342 } 343 sb.Append(string.Join(",", listWeek) + ";"); 344 return sb; 345 } 346 347 static StringBuilder GetMonthlyParam(RecurMonthly recurMonthly) 348 { 349 StringBuilder sb = new StringBuilder(); 350 sb.Append("RRULE:FREQ=MONTHLY;"); 351 if (recurMonthly.IsDayofEveryMonth) 352 { 353 sb.Append("BYMONTHDAY=" + recurMonthly.DayOfMonth + ";"); 354 if (recurMonthly.RecurIntervalMonths > 1) 355 sb.Append("INTERVAL=" + (recurMonthly.RecurIntervalMonths) + ";"); 356 } 357 else 358 { 359 if (recurMonthly.DayOfWeek == RecurDayOfWeek.Day || recurMonthly.DayOfWeek == RecurDayOfWeek.WeekDay || recurMonthly.DayOfWeek == RecurDayOfWeek.WeekEndDay) 360 { 361 sb.Append("BYDAY=" + GetDaysOfWeek(recurMonthly.DayOfWeek) + ";BYSETPOS=" + GetOrdinal(recurMonthly.RecurOrdinal) + ";"); 362 } 363 else 364 { 365 string byday = GetOrdinal(recurMonthly.RecurOrdinal) + GetDaysOfWeek(recurMonthly.DayOfWeek); 366 sb.Append("BYDAY=" + byday + ";"); 367 } 368 369 if (recurMonthly.RecurIntervalMonths > 0) 370 sb.Append("INTERVAL=" + (recurMonthly.RecurIntervalMonths) + ";"); 371 372 } 373 return sb; 374 } 375 376 static StringBuilder GetYearlyParam(RecurYearly recurYearly) 377 { 378 StringBuilder sb = new StringBuilder(); 379 sb.Append("RRULE:FREQ=YEARLY;"); 380 if (recurYearly.IsDayofMonth) 381 { 382 sb.Append("BYMONTH=" + (int)recurYearly.RecurMonthDay + ";"); 383 sb.Append("BYMONTHDAY=" + recurYearly.DayOfMonth + ";"); 384 385 } 386 else 387 { 388 if (recurYearly.DayOfWeek == RecurDayOfWeek.Day || recurYearly.DayOfWeek == RecurDayOfWeek.WeekDay || recurYearly.DayOfWeek == RecurDayOfWeek.WeekEndDay) 389 { 390 sb.Append("BYDAY=" + GetDaysOfWeek(recurYearly.DayOfWeek) + ";BYSETPOS=" + GetOrdinal(recurYearly.RecurOrdinal) + ";"); 391 } 392 else 393 { 394 string byday = GetOrdinal(recurYearly.RecurOrdinal) + GetDaysOfWeek(recurYearly.DayOfWeek); 395 sb.Append("BYDAY=" + byday + ";"); 396 } 397 sb.Append("BYMONTH=" + (int)recurYearly.RecurMonthDay2 + ";"); 398 } 399 400 if (recurYearly.recurIntervalYears > 0) 401 { 402 sb.Append("INTERVAL=" + (recurYearly.recurIntervalYears) + ";"); 403 } 404 return sb; 405 } 406 static string GetOrdinal(RecurOrdinal ordinal) 407 { 408 string prexByday = ""; 409 if (ordinal == RecurOrdinal.First) 410 { 411 prexByday = "1"; 412 } 413 else if (ordinal == RecurOrdinal.Second) 414 { 415 prexByday = "2"; 416 } 417 else if (ordinal == RecurOrdinal.Third) 418 { 419 prexByday = "3"; 420 } 421 else if (ordinal == RecurOrdinal.Fourth) 422 { 423 prexByday = "4"; 424 } 425 else if(ordinal==RecurOrdinal.Last) 426 { 427 prexByday = "-1"; 428 } 429 return prexByday; 430 } 431 432 static string GetDaysOfWeek(RecurDayOfWeek dayofweek) 433 { 434 string suffixByday = ""; 435 switch (dayofweek) 436 { 437 case RecurDayOfWeek.Monday: 438 suffixByday= "MO"; 439 break; 440 case RecurDayOfWeek.Tuesday: 441 suffixByday = "TU"; 442 break; 443 case RecurDayOfWeek.Wednesday: 444 suffixByday = "WE"; 445 break; 446 case RecurDayOfWeek.Thursday: 447 suffixByday = "TH"; 448 break; 449 case RecurDayOfWeek.Friday: 450 suffixByday = "FR"; 451 break; 452 case RecurDayOfWeek.Saturday: 453 suffixByday = "SA"; 454 break; 455 case RecurDayOfWeek.Sunday: 456 suffixByday = "SU"; 457 break; 458 case RecurDayOfWeek.Day: 459 suffixByday = "MO,TU,WE,TH,FR,SU,SA"; 460 break; 461 case RecurDayOfWeek.WeekDay: 462 suffixByday = "MO,TU,WE,TH,FR"; 463 break; 464 case RecurDayOfWeek.WeekEndDay: 465 suffixByday = "SA,SU"; 466 break; 467 468 } 469 return suffixByday; 470 } 471 472 private static string NotNull(string str) 473 { 474 return str ?? String.Empty; 475 } 476 477 478 479 public class RecurDaily 480 { 481 public bool IsEveryWeekday { get; set; } 482 public int RecurIntervalDays { get; set; } 483 484 } 485 486 public class RecurWeekly 487 { 488 public int RecurIntervalWeeks { get; set; } 489 public bool IsSunday { get; set; } 490 public bool IsMonday { get; set; } 491 public bool IsTuesday { get; set; } 492 public bool IsWednesday { get; set; } 493 public bool IsThursday { get; set; } 494 public bool IsFriday { get; set; } 495 public bool IsSaturday { get; set; } 496 497 } 498 public class RecurMonthly 499 { 500 public bool IsDayofEveryMonth { get; set; } 501 public int DayOfMonth { get; set; } 502 public int RecurIntervalMonths { get; set; } 503 public RecurOrdinal RecurOrdinal { get; set; } 504 public RecurDayOfWeek DayOfWeek { get; set; } 505 506 507 } 508 public class RecurYearly 509 { 510 public bool IsDayofMonth { get; set; } 511 public int recurIntervalYears { get; set; } 512 public RecurMonth RecurMonthDay { get; set; } 513 public int DayOfMonth{ get; set; } 514 515 public RecurOrdinal RecurOrdinal { get; set; } 516 public RecurDayOfWeek DayOfWeek { get; set; } 517 518 public RecurMonth RecurMonthDay2 { get; set; } 519 520 } 521 public enum RecurOrdinal 522 { 523 First, 524 Second, 525 Third, 526 Fourth, 527 Last 528 } 529 public enum RecurDayOfWeek 530 { 531 // 532 // Summary: 533 // Indicates Sunday. 534 Sunday = 0, 535 // 536 // Summary: 537 // Indicates Monday. 538 Monday = 1, 539 // 540 // Summary: 541 // Indicates Tuesday. 542 Tuesday = 2, 543 // 544 // Summary: 545 // Indicates Wednesday. 546 Wednesday = 3, 547 // 548 // Summary: 549 // Indicates Thursday. 550 Thursday = 4, 551 // 552 // Summary: 553 // Indicates Friday. 554 Friday = 5, 555 // 556 // Summary: 557 // Indicates Saturday. 558 Saturday = 6, 559 560 Day=7, 561 562 WeekDay=8, 563 564 WeekEndDay=9 565 } 566 public enum RecurMonth 567 { 568 569 January = 1, 570 571 February = 2, 572 573 March= 3, 574 575 April= 4, 576 577 May= 5, 578 579 June= 6, 580 581 July= 7, 582 583 August= 8, 584 585 September= 9, 586 587 October= 10, 588 589 November= 11, 590 591 December= 12 592 593 }
5.参考资料
Rfc5545规范
https://tools.ietf.org/html/rfc5545#ref-TZDB
Organizer在Outlook的Calendar看不到自己创建的event
时区问题解决
https://stackoverflow.com/questions/7626114/ics-timezone-not-working
其它问题
https://blog.csdn.net/hppgjx/article/details/77896101