// // Date+Comparators.swift // DateToolsTests // // Created by Matthew York on 8/26/16. // Copyright © 2016 Matthew York. All rights reserved. // import Foundation /** * Extends the Date class by adding methods for calculating the chunk * of time between two dates and providing many variables and functions * that compare the ordinality of two dates and the space between two dates * for a given unit of time. */ public extension Date { // MARK: - Comparisons /** * Given a date, returns a `TimeChunk` with components in their most natural form. Example: * * ``` * let formatter = DateFormatter() * formatter.dateFormat = "yyyy MM dd HH:mm:ss.SSS" * let birthday = formatter.date(from: "2015 11 24 14:50:12.000")! * let age = birthday.chunkBetween(date: formatter.date(from: "2016 10 07 15:27:12.000")!) * ``` * * The age variable will have a chunk of time with year, month, day, hour, minute, * and second components (note that we do not use weeks since they are not components * of `Calendar`). So if you just wanted the age in years, you could then say: age.years. * * The chunk is calculated exactly as you'd say it in real life, always converting up * when a lower unit equals 1 of the unit above it. The above example returns * `TimeChunk(seconds: 0, minutes: 37, hours: 0, days: 13, weeks: 0, months: 10, years: 0)`. * * Passing a future date returns a TimeChunk with all positive components and passing * a date in the past returns one with all negative components. * * - parameter date: The date of reference from the date called on * * - returns: A TimeChunk representing the time between the dates, in natural form */ func chunkBetween(date: Date) -> TimeChunk { let compenentsBetween = Calendar.autoupdatingCurrent.dateComponents([.year, .month, .day, .hour, .minute, .second], from: self, to: date) return TimeChunk(seconds: compenentsBetween.second!, minutes: compenentsBetween.minute!, hours: compenentsBetween.hour!, days: compenentsBetween.day!, weeks: 0, months: compenentsBetween.month!, years: compenentsBetween.year!) // TimeChunk(seconds: secondDelta, minutes: minuteDelta, hours: hourDelta, days: dayDelta, weeks: 0, months: monthDelta, years: yearDelta) } /** * Returns a true if receiver is equal to provided comparison date, otherwise returns false * * - parameter date: Provided date for comparison * * - returns: Bool representing comparison result */ func equals(_ date: Date) -> Bool { return compare(date) == .orderedSame } /** * Returns a true if receiver is later than provided comparison date, otherwise * returns false * * - parameter date: Provided date for comparison * * - returns: Bool representing comparison result */ func isLater(than date: Date) -> Bool { return compare(date) == .orderedDescending } /** * Returns a true if receiver is later than or equal to provided comparison date, * otherwise returns false * * - parameter date: Provided date for comparison * * - returns: Bool representing comparison result */ func isLaterThanOrEqual(to date: Date) -> Bool { return compare(date) == .orderedDescending || compare(date) == .orderedSame } /** * Returns a true if receiver is earlier than provided comparison date, otherwise * returns false * * - parameter date: Provided date for comparison * * - returns: Bool representing comparison result */ func isEarlier(than date: Date) -> Bool { return compare(date) == .orderedAscending } /** * Returns a true if receiver is earlier than or equal to the provided comparison date, * otherwise returns false * * - parameter date: Provided date for comparison * * - returns: Bool representing comparison result */ func isEarlierThanOrEqual(to date: Date) -> Bool { return compare(date) == .orderedAscending || compare(date) == .orderedSame } /** * Returns whether two dates fall on the same day. * * - parameter date: Date to compare with sender * * - returns: True if both paramter dates fall on the same day, false otherwise */ func isSameDay(date: Date) -> Bool { return Date.isSameDay(date: self, as: date) } /** * Returns whether two dates fall on the same day. * * - parameter date: First date to compare * - parameter compareDate: Second date to compare * * - returns: True if both paramter dates fall on the same day, false otherwise */ static func isSameDay(date: Date, as compareDate: Date) -> Bool { let calendar = Calendar.autoupdatingCurrent var components = calendar.dateComponents([.era, .year, .month, .day], from: date) let dateOne = calendar.date(from: components) components = calendar.dateComponents([.era, .year, .month, .day], from: compareDate) let dateTwo = calendar.date(from: components) return (dateOne?.equals(dateTwo!))! } // MARK: - Date Comparison // MARK: Time From /** * Returns an Int representing the amount of time in years between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * Uses the default Gregorian calendar * * - parameter date: The provided date for comparison * * - returns: The years between receiver and provided date */ func years(from date: Date) -> Int { return years(from: date, calendar: nil) } /** * Returns an Int representing the amount of time in months between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * Uses the default Gregorian calendar * * - parameter date: The provided date for comparison * * - returns: The years between receiver and provided date */ func months(from date: Date) -> Int { return months(from: date, calendar: nil) } /** * Returns an Int representing the amount of time in weeks between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * Uses the default Gregorian calendar * * - parameter date: The provided date for comparison * * - returns: The weeks between receiver and provided date */ func weeks(from date: Date) -> Int { return weeks(from: date, calendar: nil) } /** * Returns an Int representing the amount of time in days between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * Uses the default Gregorian calendar * * - parameter date: The provided date for comparison * * - returns: The days between receiver and provided date */ func days(from date: Date) -> Int { return days(from: date, calendar: nil) } /** * Returns an Int representing the amount of time in hours between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * * - parameter date: The provided date for comparison * * - returns: The hours between receiver and provided date */ func hours(from date: Date) -> Int { return Int(timeIntervalSince(date) / Constants.SecondsInHour) } /** * Returns an Int representing the amount of time in minutes between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * * - parameter date: The provided date for comparison * * - returns: The minutes between receiver and provided date */ func minutes(from date: Date) -> Int { return Int(timeIntervalSince(date) / Constants.SecondsInMinute) } /** * Returns an Int representing the amount of time in seconds between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * * - parameter date: The provided date for comparison * * - returns: The seconds between receiver and provided date */ func seconds(from date: Date) -> Int { return Int(timeIntervalSince(date)) } // MARK: Time From With Calendar /** * Returns an Int representing the amount of time in years between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * * - parameter date: The provided date for comparison * - parameter calendar: The calendar to be used in the calculation * * - returns: The years between receiver and provided date */ func years(from date: Date, calendar: Calendar?) -> Int { var calendarCopy = calendar if calendar == nil { calendarCopy = Calendar.autoupdatingCurrent } let earliest = earlierDate(date) let latest = (earliest == self) ? date : self let multiplier = (earliest == self) ? -1 : 1 let components = calendarCopy!.dateComponents([.year], from: earliest, to: latest) return multiplier * components.year! } /** * Returns an Int representing the amount of time in months between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * * - parameter date: The provided date for comparison * - parameter calendar: The calendar to be used in the calculation * * - returns: The months between receiver and provided date */ func months(from date: Date, calendar: Calendar?) -> Int { var calendarCopy = calendar if calendar == nil { calendarCopy = Calendar.autoupdatingCurrent } let earliest = earlierDate(date) let latest = (earliest == self) ? date : self let multiplier = (earliest == self) ? -1 : 1 let components = calendarCopy!.dateComponents(Constants.AllCalendarUnitFlags, from: earliest, to: latest) return multiplier * (components.month! + 12 * components.year!) } /** * Returns an Int representing the amount of time in weeks between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * * - parameter date: The provided date for comparison * - parameter calendar: The calendar to be used in the calculation * * - returns: The weeks between receiver and provided date */ func weeks(from date: Date, calendar: Calendar?) -> Int { var calendarCopy = calendar if calendar == nil { calendarCopy = Calendar.autoupdatingCurrent } let earliest = earlierDate(date) let latest = (earliest == self) ? date : self let multiplier = (earliest == self) ? -1 : 1 let components = calendarCopy!.dateComponents([.weekOfYear], from: earliest, to: latest) return multiplier * components.weekOfYear! } /** * Returns an Int representing the amount of time in days between the receiver and * the provided date. * * If the receiver is earlier than the provided date, the returned value will be negative. * * - parameter date: The provided date for comparison * - parameter calendar: The calendar to be used in the calculation * * - returns: The days between receiver and provided date */ func days(from date: Date, calendar: Calendar?) -> Int { var calendarCopy = calendar if calendar == nil { calendarCopy = Calendar.autoupdatingCurrent } let earliest = earlierDate(date) let latest = (earliest == self) ? date : self let multiplier = (earliest == self) ? -1 : 1 let components = calendarCopy!.dateComponents([.day], from: earliest, to: latest) return multiplier * components.day! } // MARK: Time Until /** * The number of years until the receiver's date (0 if the receiver is the same or * earlier than now). */ var yearsUntil: Int { return yearsLater(than: Date()) } /** * The number of months until the receiver's date (0 if the receiver is the same or * earlier than now). */ var monthsUntil: Int { return monthsLater(than: Date()) } /** * The number of weeks until the receiver's date (0 if the receiver is the same or * earlier than now). */ var weeksUntil: Int { return weeksLater(than: Date()) } /** * The number of days until the receiver's date (0 if the receiver is the same or * earlier than now). */ var daysUntil: Int { return daysLater(than: Date()) } /** * The number of hours until the receiver's date (0 if the receiver is the same or * earlier than now). */ var hoursUntil: Int { return hoursLater(than: Date()) } /** * The number of minutes until the receiver's date (0 if the receiver is the same or * earlier than now). */ var minutesUntil: Int { return minutesLater(than: Date()) } /** * The number of seconds until the receiver's date (0 if the receiver is the same or * earlier than now). */ var secondsUntil: Int { return secondsLater(than: Date()) } // MARK: Time Ago /** * The number of years the receiver's date is earlier than now (0 if the receiver is * the same or earlier than now). */ var yearsAgo: Int { return yearsEarlier(than: Date()) } /** * The number of months the receiver's date is earlier than now (0 if the receiver is * the same or earlier than now). */ var monthsAgo: Int { return monthsEarlier(than: Date()) } /** * The number of weeks the receiver's date is earlier than now (0 if the receiver is * the same or earlier than now). */ var weeksAgo: Int { return weeksEarlier(than: Date()) } /** * The number of days the receiver's date is earlier than now (0 if the receiver is * the same or earlier than now). */ var daysAgo: Int { return daysEarlier(than: Date()) } /** * The number of hours the receiver's date is earlier than now (0 if the receiver is * the same or earlier than now). */ var hoursAgo: Int { return hoursEarlier(than: Date()) } /** * The number of minutes the receiver's date is earlier than now (0 if the receiver is * the same or earlier than now). */ var minutesAgo: Int { return minutesEarlier(than: Date()) } /** * The number of seconds the receiver's date is earlier than now (0 if the receiver is * the same or earlier than now). */ var secondsAgo: Int { return secondsEarlier(than: Date()) } // MARK: Earlier Than /** * Returns the number of years the receiver's date is earlier than the provided * comparison date, 0 if the receiver's date is later than or equal to the provided comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of years */ func yearsEarlier(than date: Date) -> Int { return abs(min(years(from: date), 0)) } /** * Returns the number of months the receiver's date is earlier than the provided * comparison date, 0 if the receiver's date is later than or equal to the provided comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of months */ func monthsEarlier(than date: Date) -> Int { return abs(min(months(from: date), 0)) } /** * Returns the number of weeks the receiver's date is earlier than the provided * comparison date, 0 if the receiver's date is later than or equal to the provided comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of weeks */ func weeksEarlier(than date: Date) -> Int { return abs(min(weeks(from: date), 0)) } /** * Returns the number of days the receiver's date is earlier than the provided * comparison date, 0 if the receiver's date is later than or equal to the provided comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of days */ func daysEarlier(than date: Date) -> Int { return abs(min(days(from: date), 0)) } /** * Returns the number of hours the receiver's date is earlier than the provided * comparison date, 0 if the receiver's date is later than or equal to the provided comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of hours */ func hoursEarlier(than date: Date) -> Int { return abs(min(hours(from: date), 0)) } /** * Returns the number of minutes the receiver's date is earlier than the provided * comparison date, 0 if the receiver's date is later than or equal to the provided comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of minutes */ func minutesEarlier(than date: Date) -> Int { return abs(min(minutes(from: date), 0)) } /** * Returns the number of seconds the receiver's date is earlier than the provided * comparison date, 0 if the receiver's date is later than or equal to the provided comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of seconds */ func secondsEarlier(than date: Date) -> Int { return abs(min(seconds(from: date), 0)) } // MARK: Later Than /** * Returns the number of years the receiver's date is later than the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of years */ func yearsLater(than date: Date) -> Int { return max(years(from: date), 0) } /** * Returns the number of months the receiver's date is later than the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of months */ func monthsLater(than date: Date) -> Int { return max(months(from: date), 0) } /** * Returns the number of weeks the receiver's date is later than the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of weeks */ func weeksLater(than date: Date) -> Int { return max(weeks(from: date), 0) } /** * Returns the number of days the receiver's date is later than the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of days */ func daysLater(than date: Date) -> Int { return max(days(from: date), 0) } /** * Returns the number of hours the receiver's date is later than the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of hours */ func hoursLater(than date: Date) -> Int { return max(hours(from: date), 0) } /** * Returns the number of minutes the receiver's date is later than the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of minutes */ func minutesLater(than date: Date) -> Int { return max(minutes(from: date), 0) } /** * Returns the number of seconds the receiver's date is later than the provided * comparison date, 0 if the receiver's date is earlier than or equal to the provided * comparison date. * * - parameter date: Provided date for comparison * * - returns: The number of seconds */ func secondsLater(than date: Date) -> Int { return max(seconds(from: date), 0) } }