using Elternbeirat.Contracts; using Elternbeirat.Web.Shared; namespace Elternbeirat.Web.Features.Events; /// /// Formats the date range of an for display in German. /// /// /// Shared by the full event list, the home-page events embed and the next-event /// card, so all render dates the same way: as the full date, /// the Sheet… methods and for the calendar sheet and the /// time line beside it, and for the list of past events. /// /// This is the event counterpart to /// , which formats a plain /// day instead of a point-in-time range. /// /// public static class EventDateFormat { /// /// Date format for the start, and for a single-day event. /// private const string LongDate = "dddd, d. MMMM yyyy"; /// /// Date format for the end of a multi-day event; the weekday is omitted. /// private const string ShortDate = "d. MMMM yyyy"; /// /// Date format for the list of past events, which is grouped by year. /// private const string DayMonth = "d. MMMM"; /// /// Formats the date range of an event. /// /// /// The event to format. /// /// /// The formatted range, depending on and : /// /// /// No end: Donnerstag, 8. Oktober 2026, 19:30 Uhr /// /// /// Same day: Donnerstag, 8. Oktober 2026, 19:30–21:00 Uhr /// /// /// Several days: Freitag, 9. Oktober 2026 – 11. Oktober 2026 /// /// /// Without a time, the time parts are omitted. /// public static string Range(Event @event) => (@event.Start, @event.End, @event.HasTime) switch { (var start, null, var hasTime) => Stamp(start, LongDate, hasTime), (var start, { } end, true) when end.Date == start.Date => $"{start.ToString(LongDate, Cultures.German)}, {Clock(start)}–{Clock(end)} Uhr", (var start, { } end, false) when end.Date == start.Date => Stamp(start, LongDate, withTime: false), (var start, { } end, var hasTime) => $"{Stamp(start, LongDate, hasTime)} – {Stamp(end, ShortDate, hasTime)}", }; /// /// The short month for the top band of a calendar sheet. /// /// The event's start. /// The German short month without its period, e.g. Okt or März. /// /// Returned in normal case; the sheet's CSS sets it in capitals. The period of the /// abbreviation (Okt.) is dropped because on a sheet it reads as a stray dot. /// /// public static string SheetMonth(DateTime value) => value.ToString("MMM", Cultures.German).TrimEnd('.'); /// /// The day of the month for the large number on a calendar sheet. /// /// The event's start. /// The day without a leading zero, e.g. 8. public static string SheetDay(DateTime value) => value.Day.ToString(Cultures.German); /// /// The short weekday for the bottom of a calendar sheet. /// /// The event's start. /// The German short weekday without its period, e.g. Do. public static string SheetWeekday(DateTime value) => value.ToString("ddd", Cultures.German).TrimEnd('.'); /// /// The time line beside a calendar sheet: what the sheet itself cannot show. /// /// The event to format. /// /// Depending on and : /// /// No end: 19:30 Uhr /// Same day: 19:30–21:00 Uhr /// Several days: 09:00 Uhr bis Sonntag, 11. Oktober 2026, 13:00 Uhr /// Several days, no time: bis Sonntag, 11. Oktober 2026 /// One day, no time: an empty string; the sheet says it all. /// /// /// /// The sheet shows the start day only, so a multi-day event must name its last /// day here, with the weekday, as the sheet does for the first one. /// public static string Span(Event @event) => (@event.Start, @event.End, @event.HasTime) switch { (var start, { } end, var hasTime) when end.Date != start.Date => (hasTime ? Clock(start) + " Uhr " : "") + "bis " + Stamp(end, LongDate, hasTime), (var start, null, true) => $"{Clock(start)} Uhr", (var start, { } end, true) => $"{Clock(start)}–{Clock(end)} Uhr", _ => "", }; /// /// The day column of a past event in the folded-up list on the events page. /// /// The event to format. /// /// Day and full month, depending on : /// /// One day: 13. November /// Several days in one month: 8.–9. Februar /// Across a month: 30. Januar – 2. Februar /// Across a year: 30. Dezember – 2. Januar 2027 /// /// /// /// The list is grouped by the start year under its own small heading, so the /// year is only named where the end leaves that year. The weekday is left out /// to keep the column short; the time follows the title (). /// public static string PastDays(Event @event) => (@event.Start, @event.End) switch { (var start, { } end) when end.Year != start.Year => $"{start.ToString(DayMonth, Cultures.German)} – {end.ToString(ShortDate, Cultures.German)}", (var start, { } end) when end.Month != start.Month => $"{start.ToString(DayMonth, Cultures.German)} – {end.ToString(DayMonth, Cultures.German)}", (var start, { } end) when end.Day != start.Day => $"{start.Day.ToString(Cultures.German)}.–{end.ToString(DayMonth, Cultures.German)}", (var start, _) => start.ToString(DayMonth, Cultures.German), }; /// /// The time after the title of a past event in the folded-up list. /// /// The event to format. /// /// Depending on and : /// /// No time: an empty string; nothing is shown. /// No end: 19:00 Uhr /// Same day: 19:00–21:00 Uhr /// Several days: the start time only, 09:00 Uhr /// /// /// /// For several days the day column already shows the span; a time range beside /// it would read as the daily hours, so only the start time is given. /// public static string PastTime(Event @event) => (@event.HasTime, @event.End) switch { (false, _) => "", (true, { } end) when end.Date != @event.Start.Date => $"{Clock(@event.Start)} Uhr", _ => Span(@event), }; /// /// Formats the clock time of a point in time. /// /// The date and time. /// The time as e.g. 19:30. private static string Clock(DateTime value) => value.ToString("HH:mm", Cultures.German); /// /// Formats a single point in time as a date with an optional time. /// /// /// The date and time to format. /// /// /// The date format, e.g. . /// /// /// to append the time, e.g. , 19:30 Uhr. /// /// /// The formatted date, with the time if requested. /// private static string Stamp(DateTime value, string dateFormat, bool withTime) => withTime ? value.ToString(dateFormat + ", HH:mm", Cultures.German) + " Uhr" : value.ToString(dateFormat, Cultures.German); }