blazor-calendar/BlazorApp/Data/Event.cs

123 lines
4.2 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using NodaTime;
namespace BlazorApp.Data
{
public class Event
{
public string Uid { get; set; }
public string Summary { get; set; }
public DateTime? DtStart { get; set; }
public DateTime? DtEnd { get; set; }
public string Description { get; set; }
public TimeSpan? Duration { get; set; }
public List<(string, string, string)> RawProperties { get; } = new();
public string ETag { get; set; }
public string Href { get; set; }
public DateTime? CalculatedEnd
{
get => DtEnd ?? DtStart + Duration;
set
{
if (Duration != null)
Duration = DtStart - value;
else
DtEnd = value;
}
}
private static string _escape(string s)
{
if (s == null)
return null;
return s
.Replace(@"\", @"\\")
.Replace(",", @"\,")
.Replace(";", @"\;")
.Replace("\n", @"\n");
}
public string ToICal()
{
var sb = new StringBuilder();
sb.Append("BEGIN:VCALENDAR\r\n");
sb.Append("VERSION:2.0\r\n");
sb.Append("PRODID:-//Sijmen//blazor-calendar//EN\r\n");
sb.Append("BEGIN:VEVENT\r\n");
var seenNames = new HashSet<string>();
foreach (var (name, parameters, oldValue) in RawProperties)
{
var value = name switch
{
"SUMMARY" => _escape(Summary),
"DESCRIPTION" => _escape(Description),
"DTSTART" => FormatDate(parameters, DtStart),
"DTEND" => FormatDate(parameters, DtEnd),
_ => oldValue
};
if (value != null)
{
sb.Append(name);
if (parameters != null)
sb.Append($";{parameters}");
sb.Append($":{value}\r\n");
}
seenNames.Add(name);
}
// If one of the event's properties did not exist yet, add it to the end.
if (!seenNames.Contains("SUMMARY") && !string.IsNullOrWhiteSpace(Summary))
sb.Append($"SUMMARY:{_escape(Summary)}\r\n");
if (!seenNames.Contains("DESCRIPTION") && !string.IsNullOrWhiteSpace(Description))
sb.Append($"DESCRIPTION:{_escape(Description)}\r\n");
if (!seenNames.Contains("DTSTART") && DtStart != null)
sb.Append($"DTSTART:{DtStart?.ToUniversalTime().ToString(Calendar.DateTimeFormat)}\r\n");
if (!seenNames.Contains("DTEND") && !seenNames.Contains("DURATION") && CalculatedEnd != null)
sb.Append($"DTEND:{DtStart?.ToUniversalTime().ToString(Calendar.DateTimeFormat)}\r\n");
sb.Append("END:VEVENT\r\n");
sb.Append("END:VCALENDAR\r\n");
return sb.ToString();
}
private static string FormatDate(string parameters, DateTime? dateTime)
{
if (dateTime == null)
return null;
DateTimeZone timezone;
if (parameters != null)
{
// If there's parameters, see if we can find a timezone, or default to UTC.
var parameterList = parameters.Split('=');
var timezoneIana =
Array.Find(parameterList, s => s.StartsWith("TZID="))?[5..] ?? "UTC";
timezone = DateTimeZoneProviders.Tzdb[timezoneIana];
}
else
{
// If there's no parameters, default to UTC.
timezone = DateTimeZone.Utc;
}
// Convert the datetime to the used timezone, to avoid changing it.
return Instant
.FromDateTimeUtc(((DateTime) dateTime).ToUniversalTime())
.InZone(timezone)
.ToString(Calendar.DateTimeFormat, CultureInfo.InvariantCulture);
}
}
}