import Foundation extension Date { // MARK: - Formatting var shortDateString: String { let formatter = DateFormatter() formatter.dateStyle = .short formatter.timeStyle = .none return formatter.string(from: self) } var mediumDateString: String { let formatter = DateFormatter() formatter.dateStyle = .medium formatter.timeStyle = .none return formatter.string(from: self) } var longDateString: String { let formatter = DateFormatter() formatter.dateStyle = .long formatter.timeStyle = .none return formatter.string(from: self) } var monthYearString: String { let formatter = DateFormatter() formatter.dateFormat = "MMM yyyy" return formatter.string(from: self) } var yearString: String { let formatter = DateFormatter() formatter.dateFormat = "yyyy" return formatter.string(from: self) } var iso8601String: String { ISO8601DateFormatter().string(from: self) } // MARK: - Components var startOfDay: Date { Calendar.current.startOfDay(for: self) } var startOfMonth: Date { let components = Calendar.current.dateComponents([.year, .month], from: self) return Calendar.current.date(from: components) ?? self } var startOfYear: Date { let components = Calendar.current.dateComponents([.year], from: self) return Calendar.current.date(from: components) ?? self } var endOfMonth: Date { let startOfNextMonth = Calendar.current.date( byAdding: .month, value: 1, to: startOfMonth ) ?? self return Calendar.current.date(byAdding: .day, value: -1, to: startOfNextMonth) ?? self } // MARK: - Comparisons func isSameDay(as other: Date) -> Bool { Calendar.current.isDate(self, inSameDayAs: other) } func isSameMonth(as other: Date) -> Bool { let selfComponents = Calendar.current.dateComponents([.year, .month], from: self) let otherComponents = Calendar.current.dateComponents([.year, .month], from: other) return selfComponents.year == otherComponents.year && selfComponents.month == otherComponents.month } func isSameYear(as other: Date) -> Bool { Calendar.current.component(.year, from: self) == Calendar.current.component(.year, from: other) } var isToday: Bool { Calendar.current.isDateInToday(self) } var isYesterday: Bool { Calendar.current.isDateInYesterday(self) } var isThisMonth: Bool { isSameMonth(as: Date()) } var isThisYear: Bool { isSameYear(as: Date()) } // MARK: - Calculations func adding(days: Int) -> Date { Calendar.current.date(byAdding: .day, value: days, to: self) ?? self } func adding(months: Int) -> Date { Calendar.current.date(byAdding: .month, value: months, to: self) ?? self } func adding(years: Int) -> Date { Calendar.current.date(byAdding: .year, value: years, to: self) ?? self } func monthsBetween(_ other: Date) -> Int { let components = Calendar.current.dateComponents([.month], from: self, to: other) return components.month ?? 0 } func daysBetween(_ other: Date) -> Int { let components = Calendar.current.dateComponents([.day], from: self, to: other) return components.day ?? 0 } func yearsBetween(_ other: Date) -> Double { let days = Double(daysBetween(other)) return days / 365.25 } // MARK: - Relative Description var relativeDescription: String { let formatter = RelativeDateTimeFormatter() formatter.unitsStyle = .abbreviated return formatter.localizedString(for: self, relativeTo: Date()) } var friendlyDescription: String { if isToday { return "Today" } else if isYesterday { return "Yesterday" } else if isThisMonth { let formatter = DateFormatter() formatter.dateFormat = "EEEE, d" return formatter.string(from: self) } else if isThisYear { let formatter = DateFormatter() formatter.dateFormat = "MMM d" return formatter.string(from: self) } else { return mediumDateString } } } // MARK: - Date Range struct DateRange { let start: Date let end: Date static var thisMonth: DateRange { let now = Date() return DateRange(start: now.startOfMonth, end: now) } static var lastMonth: DateRange { let now = Date() let lastMonth = now.adding(months: -1) return DateRange(start: lastMonth.startOfMonth, end: lastMonth.endOfMonth) } static var thisYear: DateRange { let now = Date() return DateRange(start: now.startOfYear, end: now) } static var lastYear: DateRange { let now = Date() let lastYear = now.adding(years: -1) return DateRange(start: lastYear.startOfYear, end: now.startOfYear.adding(days: -1)) } static func last(months: Int) -> DateRange { let now = Date() let start = now.adding(months: -months) return DateRange(start: start, end: now) } func contains(_ date: Date) -> Bool { date >= start && date <= end } }