import Foundation /// Was über einen Tag bekannt ist. public enum DayStatus: Equatable, Sendable { /// Keine Termine — und das ist gesicherte Information. case free /// Mindestens ein Termin. case busy /// Die Quelle deckt diesen Tag nicht ab. **Nicht dasselbe wie frei.** case unknown } /// Ein Tag im Monatsraster. public struct MonthDay: Identifiable, Equatable, Sendable { public let date: Date /// Gehört der Tag zum dargestellten Monat oder füllt er nur die Woche auf? public let isInMonth: Bool public var id: Date { date } } /// Das Raster eines Monats, immer aus vollen Wochen. /// /// Die Wochen füllen sich mit Tagen aus den Nachbarmonaten auf. Eine /// angeschnittene erste Zeile würde die Spaltenausrichtung brechen, und dann /// steht der Dienstag nicht mehr unter „Di". public struct MonthGrid: Equatable, Sendable { public let month: Date public let days: [MonthDay] private let calendar: Calendar public init(containing date: Date, calendar: Calendar) { self.calendar = calendar let startOfMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: date))! self.month = startOfMonth let dayCount = calendar.range(of: .day, in: .month, for: startOfMonth)!.count // Wie viele Tage aus dem Vormonat vorne stehen müssen, damit die erste // Zeile am eingestellten Wochenanfang beginnt. let weekdayOfFirst = calendar.component(.weekday, from: startOfMonth) let leading = (weekdayOfFirst - calendar.firstWeekday + 7) % 7 var days: [MonthDay] = [] for offset in 0..) -> DayStatus { let day = calendar.startOfDay(for: date) if let coverage { // Halboffenes Intervall [start, end) — so legt es die // Calendarr-Anleitung fest, und `DateInterval.contains` schließt // das Ende ein, deshalb der eigene Vergleich. guard day >= calendar.startOfDay(for: coverage.start), day < calendar.startOfDay(for: coverage.end) else { return .unknown } } return busyDays.contains(day) ? .busy : .free } public static func == (lhs: MonthGrid, rhs: MonthGrid) -> Bool { lhs.month == rhs.month && lhs.days == rhs.days } } public extension Collection where Element == OnyxEvent { /// Die Tage, an denen etwas stattfindet — für das Monatsraster. /// Mehrtägige Termine markieren jeden berührten Tag. func busyDays(calendar: Calendar) -> Set { var days = Set() for event in self { var day = calendar.startOfDay(for: event.start) let last = calendar.startOfDay(for: event.end) while day <= last { days.insert(day) guard let next = calendar.date(byAdding: .day, value: 1, to: day) else { break } day = next } } return days } }