import Foundation extension Date { // Cached formatters — DateFormatter init is expensive and these are called // in list rows on every render pass. private static let shortDateTime: DateFormatter = { let f = DateFormatter(); f.dateStyle = .short; f.timeStyle = .short; return f }() private static let timeOnly: DateFormatter = { let f = DateFormatter(); f.dateStyle = .none; f.timeStyle = .short; return f }() private static let mediumDate: DateFormatter = { let f = DateFormatter(); f.dateStyle = .medium; f.timeStyle = .none; return f }() private static let monthAbbrev: DateFormatter = { let f = DateFormatter(); f.dateFormat = "MMM"; return f }() private static let weekdayAbbrev: DateFormatter = { let f = DateFormatter(); f.dateFormat = "EEE"; return f }() func formattedDate() -> String { Self.shortDateTime.string(from: self) } func formattedTime() -> String { Self.timeOnly.string(from: self) } func formatDate() -> String { Self.mediumDate.string(from: self) } func isSameDay(as other: Date) -> Bool { Calendar.current.isDate(self, inSameDayAs: other) } var abbreviatedMonth: String { Self.monthAbbrev.string(from: self) } var abbreviatedWeekday: String { Self.weekdayAbbrev.string(from: self) } var dayOfMonth: Int { Calendar.current.component(.day, from: self) } func humanTimeInterval(to other: Date) -> String { let interval = other.timeIntervalSince(self) let hours = Int(interval) / 3600 let minutes = (Int(interval) % 3600) / 60 return hours > 0 ? "\(hours)h \(minutes)m" : "\(minutes)m" } }