97 lines
2.9 KiB
Swift
97 lines
2.9 KiB
Swift
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
|
||
|
||
}
|