import Foundation import SwiftUI struct CalEvent: Identifiable, Hashable { let id: String let url: String var title: String var startDate: Date var endDate: Date var isAllDay: Bool var location: String var notes: String var color: String? var calendarId: String var calendarName: String var calendarColor: String var source: String var effectiveColor: String { color ?? calendarColor } static func from(json: [String: Any]) -> CalEvent? { guard let title = json["title"] as? String, let startStr = json["start"] as? String, let endStr = json["end"] as? String else { return nil } // id can be String (local UUID) or Int (CalDAV numeric) let id: String if let s = json["id"] as? String { id = s } else if let n = json["id"] as? Int { id = String(n) } else { return nil } let isAllDay = json["allDay"] as? Bool ?? false let startDate = parseDate(startStr, allDay: isAllDay) let endDate = parseDate(endStr, allDay: isAllDay) guard let s = startDate, let e = endDate else { return nil } return CalEvent( id: id, url: json["url"] as? String ?? "", title: title, startDate: s, endDate: e, isAllDay: isAllDay, location: json["location"] as? String ?? "", notes: json["description"] as? String ?? "", color: (json["color"] as? String).flatMap { $0.isEmpty ? nil : $0 }, calendarId: json["calendar_id"].map { "\($0)" } ?? "", calendarName: json["calendar_name"] as? String ?? "", calendarColor: json["calendarColor"] as? String ?? "#4285f4", source: json["source"] as? String ?? "local" ) } } private let isoFull: ISO8601DateFormatter = { let f = ISO8601DateFormatter() f.formatOptions = [.withInternetDateTime, .withFractionalSeconds] return f }() private let isoBasic: ISO8601DateFormatter = { let f = ISO8601DateFormatter() f.formatOptions = [.withInternetDateTime] return f }() private let dateOnly: DateFormatter = { let f = DateFormatter() f.dateFormat = "yyyy-MM-dd" f.timeZone = .current return f }() // Handles all date formats the backend may produce: // "2026-05-17" "2026-05-17T10:00:00Z" "2026-05-17T10:00:00+02:00" // "2026-05-17T10:00:00.000Z" "2026-05-17T10:00:00" "2026-05-17 10:00:00+00:00" private let noTZFormatter: DateFormatter = { let f = DateFormatter() f.locale = Locale(identifier: "en_US_POSIX") f.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" f.timeZone = TimeZone(abbreviation: "UTC") return f }() private let spaceSepFormatter: DateFormatter = { let f = DateFormatter() f.locale = Locale(identifier: "en_US_POSIX") f.dateFormat = "yyyy-MM-dd HH:mm:ssZ" return f }() func parseDate(_ s: String, allDay: Bool) -> Date? { let clean = s.trimmingCharacters(in: .whitespaces) if allDay || (clean.count == 10 && !clean.contains("T")) { return dateOnly.date(from: String(clean.prefix(10))) } // Try each formatter in order of likelihood if let d = isoFull.date(from: clean) { return d } if let d = isoBasic.date(from: clean) { return d } // Python isoformat uses space separator: "2026-05-17 10:00:00+00:00" if let d = spaceSepFormatter.date(from: clean) { return d } // No timezone → treat as UTC if let d = noTZFormatter.date(from: String(clean.prefix(19))) { return d } // Last resort: just parse the date part return dateOnly.date(from: String(clean.prefix(10))) } func formatISO(_ date: Date, allDay: Bool) -> String { if allDay { return dateOnly.string(from: date) } return isoBasic.string(from: date) }