TeraCalendarPresentation.swift (3476B)
1 import Foundation 2 3 struct TeraCalendarPresentation { 4 var locale: Locale = .current 5 var timeZone: TimeZone = .current 6 7 func summary(_ timing: TeraCalendarTiming) -> String { 8 switch timing { 9 case let .dateBased(start, end): 10 let format = TeraCivilDateFormat(locale: locale) 11 guard let end else { return "All day, \(format.string(start))" } 12 guard end > start, let last = end.previousDay else { return "Date unavailable" } 13 let range = last == start ? format.string(start) : "\(format.string(start)) – \(format.string(last))" 14 return "All day, \(range)" 15 case let .timeBased(start, end, startZone, endZone): 16 guard end == nil || end.map({ $0 > start }) == true else { return "Date unavailable" } 17 var parts = [instant(start, zone: timeZone, label: "Starts")] 18 if let end { 19 parts.append(instant(end, zone: timeZone, label: "Ends")) 20 } 21 parts.append("Display time zone: \(timeZone.identifier)") 22 if let startZone { 23 parts.append(sourceInstant(start, zoneID: startZone, label: "Event start")) 24 } 25 if let end, let endZone { 26 parts.append(sourceInstant(end, zoneID: endZone, label: "Event end")) 27 } 28 return parts.joined(separator: "; ") 29 } 30 } 31 32 func authoredSummary(_ seconds: UInt64) -> String { 33 instant(seconds, zone: timeZone, label: "Posted") 34 } 35 36 func authoredDate(_ seconds: UInt64) -> Date? { 37 displayDate(seconds, zone: timeZone) 38 } 39 40 private func displayDate(_ seconds: UInt64, zone: TimeZone) -> Date? { 41 guard let date = TeraCalendarTiming.presentationInstant(seconds) else { return nil } 42 var calendar = Calendar(identifier: .gregorian) 43 calendar.timeZone = zone 44 return (1 ... 9999).contains(calendar.component(.year, from: date)) ? date : nil 45 } 46 47 private func sourceInstant(_ seconds: UInt64, zoneID: String, label: String) -> String { 48 guard zoneID.utf8.count <= 255, let zone = TimeZone(identifier: zoneID) else { return "Date unavailable" } 49 return "\(instant(seconds, zone: zone, label: label)) (\(zoneID))" 50 } 51 52 private func instant(_ seconds: UInt64, zone: TimeZone, label: String) -> String { 53 guard let date = displayDate(seconds, zone: zone) else { return "Date unavailable" } 54 var calendar = Calendar(identifier: .gregorian) 55 calendar.timeZone = zone 56 let format = DateFormatter() 57 format.locale = locale 58 format.calendar = calendar 59 format.timeZone = zone 60 format.dateStyle = .medium 61 format.timeStyle = .long 62 return "\(label): \(format.string(from: date))" 63 } 64 } 65 66 extension TeraTodayCard { 67 var accessibilitySummary: String { 68 accessibilitySummary(locale: .current, timeZone: .current) 69 } 70 71 func accessibilitySummary(locale: Locale, timeZone: TimeZone) -> String { 72 var parts = [type.label, "by \(authorName)"] 73 if let title { 74 parts.append(title) 75 } 76 if !content.isEmpty { 77 parts.append(content) 78 } 79 if let calendarTiming { 80 parts.append(TeraCalendarPresentation(locale: locale, timeZone: timeZone).summary(calendarTiming)) 81 } 82 let food = TeraFoodPresentation(locale: locale) 83 if let price = food.price(self) { 84 parts.append(price) 85 } 86 if let quantity = food.quantity(self) { 87 parts.append(quantity) 88 } 89 if lifecycle != .active { 90 parts.append(lifecycle.rawValue) 91 } 92 if let localOperationState { 93 parts.append(localOperationState) 94 } 95 return parts.joined(separator: ", ") 96 } 97 }