import Foundation extension Date { // MARK: Methods /// How long until the date, in a few words: `47 h left`, `2 days left`, `under an hour left`. /// - Parameter now: the moment counted from. func countdown( from now: Date ) -> String { let hours = Int(timeIntervalSince(now) / 3_600) switch hours { case ..<1: return "under an hour left" case ..<48: return "\(hours) h left" default: return "\(hours / 24) days left" } } /// The days an event runs over, e.g. `Sat 26 – Sun 27 Sep`. static func span( from start: Date, to end: Date ) -> String { let first = start.formatted(style.weekday(.abbreviated).day()) let last = end.formatted(style.weekday(.abbreviated).day().month(.abbreviated)) return calendar.isDate(start, inSameDayAs: end) ? last : "\(first) – \(last)" } /// Whether the date falls within the week from the given moment: an event "this weekend". func isWithinAWeek( of now: Date ) -> Bool { (0..<7 * 86_400).contains(timeIntervalSince(now)) || Self.calendar.isDate(self, inSameDayAs: now) } /// The date as a day on a record, e.g. `20 Sep`. var dayStamp: String { formatted(Self.style.day().month(.abbreviated)) } /// The date as a stamp on a record: `today, 09:14`, `yesterday, 21:38`, `20 Sep, 14:05`, or with the year once it differs. /// /// Read on Amsterdam's clock: the marketplace is NL-first. /// - Parameter now: the moment "today" is judged from. func stamp( relativeTo now: Date ) -> String { let time = formatted(Self.style.hour(.twoDigits(amPM: .omitted)).minute(.twoDigits)) if Self.calendar.isDate(self, inSameDayAs: now) { return "today, \(time)" } if let yesterday = Self.calendar.date(byAdding: .day, value: -1, to: now), Self.calendar.isDate(self, inSameDayAs: yesterday) { return "yesterday, \(time)" } let isSameYear = Self.calendar.isDate(self, equalTo: now, toGranularity: .year) let day = formatted(isSameYear ? Self.style.day().month(.abbreviated) : Self.style.day().month(.abbreviated).year()) return "\(day), \(time)" } } // MARK: - Helpers private extension Date { // MARK: Constants /// The calendar days are told apart by, on Amsterdam's clock. static let calendar: Calendar = { var calendar = Calendar(identifier: .gregorian) calendar.timeZone = timeZone return calendar }() /// The base format style: British English, on Amsterdam's clock. static let style = Date.FormatStyle( locale: .init(identifier: "en_GB"), calendar: calendar, timeZone: timeZone ) static let timeZone = TimeZone(identifier: "Europe/Amsterdam") ?? .gmt }