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(); 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); } } }