using System.Text; using System.Globalization; namespace Socialize.Api.Modules.CalendarIntegrations.Services; internal sealed record CalendarExportFeedEvent( string Uid, string Title, DateTimeOffset StartsAt, DateTimeOffset EndsAt, bool IsAllDay, string? Description, string? Url); internal static class CalendarExportFeedBuilder { public static string Build(string calendarName, IReadOnlyCollection events) { StringBuilder builder = new(); builder.AppendLine("BEGIN:VCALENDAR"); builder.AppendLine("VERSION:2.0"); builder.AppendLine("PRODID:-//Socialize//User Work Calendar//EN"); builder.AppendLine("CALSCALE:GREGORIAN"); builder.AppendLine("METHOD:PUBLISH"); AppendLineInvariant(builder, $"X-WR-CALNAME:{EscapeText(calendarName)}"); foreach (CalendarExportFeedEvent feedEvent in events.OrderBy(calendarEvent => calendarEvent.StartsAt)) { builder.AppendLine("BEGIN:VEVENT"); AppendLineInvariant(builder, $"UID:{EscapeText(feedEvent.Uid)}"); AppendLineInvariant(builder, $"DTSTAMP:{FormatUtc(DateTimeOffset.UtcNow)}"); AppendLineInvariant(builder, $"SUMMARY:{EscapeText(feedEvent.Title)}"); if (feedEvent.IsAllDay) { AppendLineInvariant(builder, $"DTSTART;VALUE=DATE:{FormatDate(feedEvent.StartsAt)}"); AppendLineInvariant(builder, $"DTEND;VALUE=DATE:{FormatDate(feedEvent.EndsAt)}"); } else { AppendLineInvariant(builder, $"DTSTART:{FormatUtc(feedEvent.StartsAt)}"); AppendLineInvariant(builder, $"DTEND:{FormatUtc(feedEvent.EndsAt)}"); } if (!string.IsNullOrWhiteSpace(feedEvent.Description)) { AppendLineInvariant(builder, $"DESCRIPTION:{EscapeText(feedEvent.Description)}"); } if (!string.IsNullOrWhiteSpace(feedEvent.Url)) { AppendLineInvariant(builder, $"URL:{EscapeText(feedEvent.Url)}"); } builder.AppendLine("END:VEVENT"); } builder.AppendLine("END:VCALENDAR"); return builder.ToString(); } private static string FormatDate(DateTimeOffset value) { return value.ToString("yyyyMMdd", System.Globalization.CultureInfo.InvariantCulture); } private static string FormatUtc(DateTimeOffset value) { return value.UtcDateTime.ToString("yyyyMMdd'T'HHmmss'Z'", System.Globalization.CultureInfo.InvariantCulture); } private static string EscapeText(string value) { return value .Replace("\\", "\\\\", StringComparison.Ordinal) .Replace("\r\n", "\\n", StringComparison.Ordinal) .Replace("\n", "\\n", StringComparison.Ordinal) .Replace(";", "\\;", StringComparison.Ordinal) .Replace(",", "\\,", StringComparison.Ordinal); } private static void AppendLineInvariant(StringBuilder builder, FormattableString value) { builder.AppendLine(value.ToString(CultureInfo.InvariantCulture)); } }