ICalendarNet is an iCalendar (RFC 5545) class library for .NET aimed at providing RFC 5545 compliance, while providing full compatibility with popular calendaring applications and libraries. Credits go to Rianjs for providing a ton of info and the baseline of this project.
- Net Standard 2.1
- Net 8.0
- Serialization
- Deserialization
- RFC 5545 compliancy
- Make it easier to create/edit occurency
- Make it easier to create/edit alarms
dotnet add packages ICalendarNet
How to deserialize an get events
using var httpClient = new HttpClient();
string icalvar = await httpClient.GetStringAsync("https://www.webcal.guru/en-US/download_calendar?calendar_instance_id=10");
Calendar? calendar = calSerializor.DeserializeCalendar(icalvar);
CalendarEvent calEvent = calendar.GetEvents().First();
Hot to create a new calendar and serialize
private static string SimpleCalendar()
{
Calendar calendar = new Calendar();
using CalSerializor serializor = new CalSerializor()
//Add an event
CalendarEvent calendarEvent = new CalendarEvent()
{
DTSTART = DateTimeOffset.UtcNow,
DTEND = DateTimeOffset.UtcNow.AddHours(1),
Location = "The Exceptionally Long Named Meeting Room",
Priority = 0
};
calendarEvent.SetAttachments(new List<CalendarAttachment>()
{
//Add url attachment
new CalendarAttachment(new Uri("ldap://example.com:3333/o=eExample Industries,c=3DUS??(cn=3DBJohn Smith)"), ""),
//Add byte attachment
new CalendarAttachment(Encoding.UTF8.GetBytes(""), "application/msword")
});
calendar.SubComponents.Add(calendarEvent);
//Add an alarm
calendar.SubComponents.Add(
//Display info when triggered
new CalendarAlarm(trigger: new CalendarTrigger(TimeSpan.FromMinutes(-108)),
notification: "Reminder water plants"));
calendar.SubComponents.Add(
//Send an email when triggered
new CalendarAlarm(trigger: new CalendarTrigger(DateTime.Now.AddYears(1)),
emailAdresses: ["test@gmail.com"],
subject: "Test email subject",
body: @"Dear John,
Please water the plants.
Regards
Mr Smith"));
//Add a t_odo
calendar.SubComponents.Add(
new CalendarTodo()
{
DTSTART = DateTimeOffset.UtcNow,
Completed = DateTimeOffset.UtcNow.AddHours(1),
Location = "The Exceptionally Long Named Meeting Room"
});
//Add a freebusy
calendar.SubComponents.Add(
new CalendarFreeBusy()
{
DTSTART = DateTimeOffset.UtcNow,
DTEND = DateTimeOffset.UtcNow.AddHours(1),
});
return serializor.SerializeCalendar(calendar);
}
BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.5011/22H2/2022Update) 12th Gen Intel Core i7-1265U, 1 CPU, 12 logical and 10 physical cores .NET SDK 9.0.100-rc.1.24452.12
| ICalendarNet | Mean | Error | StdDev | Median | Gen0 | Gen1 | Gen2 | Allocated |
|----------------------------------|--------------:|------------:|------------:|--------------:|----------:|----------:|---------:|------------:|
| DeserializeCalendar | 13.668 μs | 0.2459 μs | 0.4797 μs | 13.491 μs | 3.3875 | 0.1221 | - | 20.89 KB |
| SerializeCalendar | 5.697 μs | 0.1136 μs | 0.2849 μs | 5.666 μs | 2.8381 | 0.0534 | - | 17.39 KB |
| Deser_And_Ser_allotof_Calendars | 7,384.326 μs | 143.6299 μs | 176.3904 μs | 7,337.092 μs | 531.2500 | 429.6875 | 125.0000 | 2986.76 KB |
| Deser_And_Ser_verybig_Calendar | 24,471.842 μs | 488.4794 μs | 986.7525 μs | 24,261.273 μs | 3406.2500 | 1656.2500 | 562.5000 | 20489.48 KB |
When using ICal.Net
| ICal.Net | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated |
|----------------------------------|--------------:|-------------:|-------------:|-----------:|----------:|----------:|-------------:|
| DeserializeCalendar | 79.87 μs | 0.707 μs | 0.626 μs | 30.7617 | 5.3711 | - | 191.42 KB |
| SerializeCalendar | 22.93 μs | 0.428 μs | 0.783 μs | 9.2773 | 0.7324 | - | 57.4 KB |
| Deser_And_Ser_allotof_Calendars | NA | NA | NA | NA | NA | NA | NA |
| Deser_And_Ser_verybig_Calendar | 174,481.37 μs | 3,461.040 μs | 6,831.756 μs | 20500.0000 | 6500.0000 | 1500.0000 | 121901.07 KB |
Benchmarks with issues:
OtherToolsTests.ICal_Net_Deserialize_And_Serialize_all_Calendars: DefaultJob
We can see in .net9 ICalendarNet is +- 4 times faster and uses 4-6 times fewer allocated memory.
I replaced Regex functions, and started using ReadonlySpan<char> instead of strings when possible. When using .net8 we can use the performant SearchValues<T> to search for values.