//
|
// SwiftDate
|
// Parse, validate, manipulate, and display dates, time and timezones in Swift
|
//
|
// Created by Daniele Margutti
|
// - Web: https://www.danielemargutti.com
|
// - Twitter: https://twitter.com/danielemargutti
|
// - Mail: hello@danielemargutti.com
|
//
|
// Copyright © 2019 Daniele Margutti. Licensed under MIT License.
|
//
|
|
import Foundation
|
|
// MARK: - Comparing DateInRegion
|
|
public func == (lhs: DateInRegion, rhs: DateInRegion) -> Bool {
|
return (lhs.date.timeIntervalSince1970 == rhs.date.timeIntervalSince1970)
|
}
|
|
public func <= (lhs: DateInRegion, rhs: DateInRegion) -> Bool {
|
let result = lhs.date.compare(rhs.date)
|
return (result == .orderedAscending || result == .orderedSame)
|
}
|
|
public func >= (lhs: DateInRegion, rhs: DateInRegion) -> Bool {
|
let result = lhs.date.compare(rhs.date)
|
return (result == .orderedDescending || result == .orderedSame)
|
}
|
|
public func < (lhs: DateInRegion, rhs: DateInRegion) -> Bool {
|
return lhs.date.compare(rhs.date) == .orderedAscending
|
}
|
|
public func > (lhs: DateInRegion, rhs: DateInRegion) -> Bool {
|
return lhs.date.compare(rhs.date) == .orderedDescending
|
}
|
|
// The type of comparison to do against today's date or with the suplied date.
|
///
|
/// - isToday: hecks if date today.
|
/// - isTomorrow: Checks if date is tomorrow.
|
/// - isYesterday: Checks if date is yesterday.
|
/// - isSameDay: Compares date days
|
/// - isThisWeek: Checks if date is in this week.
|
/// - isNextWeek: Checks if date is in next week.
|
/// - isLastWeek: Checks if date is in last week.
|
/// - isSameWeek: Compares date weeks
|
/// - isThisMonth: Checks if date is in this month.
|
/// - isNextMonth: Checks if date is in next month.
|
/// - isLastMonth: Checks if date is in last month.
|
/// - isSameMonth: Compares date months
|
/// - isThisYear: Checks if date is in this year.
|
/// - isNextYear: Checks if date is in next year.
|
/// - isLastYear: Checks if date is in last year.
|
/// - isSameYear: Compare date years
|
/// - isInTheFuture: Checks if it's a future date
|
/// - isInThePast: Checks if the date has passed
|
/// - isEarlier: Checks if earlier than date
|
/// - isLater: Checks if later than date
|
/// - isWeekday: Checks if it's a weekday
|
/// - isWeekend: Checks if it's a weekend
|
/// - isInDST: Indicates whether the represented date uses daylight saving time.
|
/// - isMorning: Return true if date is in the morning (>=5 - <12)
|
/// - isAfternoon: Return true if date is in the afternoon (>=12 - <17)
|
/// - isEvening: Return true if date is in the morning (>=17 - <21)
|
/// - isNight: Return true if date is in the morning (>=21 - <5)
|
public enum DateComparisonType {
|
|
// Days
|
case isToday
|
case isTomorrow
|
case isYesterday
|
case isSameDay(_ : DateRepresentable)
|
|
// Weeks
|
case isThisWeek
|
case isNextWeek
|
case isLastWeek
|
case isSameWeek(_: DateRepresentable)
|
|
// Months
|
case isThisMonth
|
case isNextMonth
|
case isLastMonth
|
case isSameMonth(_: DateRepresentable)
|
|
// Years
|
case isThisYear
|
case isNextYear
|
case isLastYear
|
case isSameYear(_: DateRepresentable)
|
|
// Relative Time
|
case isInTheFuture
|
case isInThePast
|
case isEarlier(than: DateRepresentable)
|
case isLater(than: DateRepresentable)
|
case isWeekday
|
case isWeekend
|
|
// Day time
|
case isMorning
|
case isAfternoon
|
case isEvening
|
case isNight
|
|
// TZ
|
case isInDST
|
}
|
|
public extension DateInRegion {
|
|
/// Decides whether a DATE is "close by" another one passed in parameter,
|
/// where "Being close" is measured using a precision argument
|
/// which is initialized a 300 seconds, or 5 minutes.
|
///
|
/// - Parameters:
|
/// - refDate: reference date compare against to.
|
/// - precision: The precision of the comparison (default is 5 minutes, or 300 seconds).
|
/// - Returns: A boolean; true if close by, false otherwise.
|
func compareCloseTo(_ refDate: DateInRegion, precision: TimeInterval = 300) -> Bool {
|
return (abs(date.timeIntervalSince(refDate.date)) <= precision)
|
}
|
|
/// Compare the date with the rule specified in the `compareType` parameter.
|
///
|
/// - Parameter compareType: comparison type.
|
/// - Returns: `true` if comparison succeded, `false` otherwise
|
func compare(_ compareType: DateComparisonType) -> Bool {
|
switch compareType {
|
case .isToday:
|
return compare(.isSameDay(region.nowInThisRegion()))
|
|
case .isTomorrow:
|
let tomorrow = DateInRegion(region: region).dateByAdding(1, .day)
|
return compare(.isSameDay(tomorrow))
|
|
case .isYesterday:
|
let yesterday = DateInRegion(region: region).dateByAdding(-1, .day)
|
return compare(.isSameDay(yesterday))
|
|
case .isSameDay(let refDate):
|
return calendar.isDate(date, inSameDayAs: refDate.date)
|
|
case .isThisWeek:
|
return compare(.isSameWeek(region.nowInThisRegion()))
|
|
case .isNextWeek:
|
let nextWeek = region.nowInThisRegion().dateByAdding(1, .weekOfYear)
|
return compare(.isSameWeek(nextWeek))
|
|
case .isLastWeek:
|
let lastWeek = region.nowInThisRegion().dateByAdding(-1, .weekOfYear)
|
return compare(.isSameWeek(lastWeek))
|
|
case .isSameWeek(let refDate):
|
guard weekOfYear == refDate.weekOfYear else {
|
return false
|
}
|
// Ensure time interval is under 1 week
|
return (abs(date.timeIntervalSince(refDate.date)) < 1.weeks.timeInterval)
|
|
case .isThisMonth:
|
return compare(.isSameMonth(region.nowInThisRegion()))
|
|
case .isNextMonth:
|
let nextMonth = region.nowInThisRegion().dateByAdding(1, .month)
|
return compare(.isSameMonth(nextMonth))
|
|
case .isLastMonth:
|
let lastMonth = region.nowInThisRegion().dateByAdding(-1, .month)
|
return compare(.isSameMonth(lastMonth))
|
|
case .isSameMonth(let refDate):
|
return (date.year == refDate.date.year) && (date.month == refDate.date.month)
|
|
case .isThisYear:
|
return compare(.isSameYear(region.nowInThisRegion()))
|
|
case .isNextYear:
|
let nextYear = region.nowInThisRegion().dateByAdding(1, .year)
|
return compare(.isSameYear(nextYear))
|
|
case .isLastYear:
|
let lastYear = region.nowInThisRegion().dateByAdding(-1, .year)
|
return compare(.isSameYear(lastYear))
|
|
case .isSameYear(let refDate):
|
return (date.year == refDate.date.year)
|
|
case .isInTheFuture:
|
return compare(.isLater(than: region.nowInThisRegion()))
|
|
case .isInThePast:
|
return compare(.isEarlier(than: region.nowInThisRegion()))
|
|
case .isEarlier(let refDate):
|
return ((date as NSDate).earlierDate(refDate.date) == date)
|
|
case .isLater(let refDate):
|
return ((date as NSDate).laterDate(refDate.date) == date)
|
|
case .isWeekday:
|
return !compare(.isWeekend)
|
|
case .isWeekend:
|
let range = calendar.maximumRange(of: Calendar.Component.weekday)!
|
return (weekday == range.lowerBound || weekday == range.upperBound - range.lowerBound)
|
|
case .isInDST:
|
return region.timeZone.isDaylightSavingTime(for: date)
|
|
case .isMorning:
|
return (hour >= 5 && hour < 12)
|
|
case .isAfternoon:
|
return (hour >= 12 && hour < 17)
|
|
case .isEvening:
|
return (hour >= 17 && hour < 21)
|
|
case .isNight:
|
return (hour >= 21 || hour < 5)
|
|
}
|
}
|
|
/// Returns a ComparisonResult value that indicates the ordering of two given dates based on
|
/// their components down to a given unit granularity.
|
///
|
/// - parameter date: date to compare.
|
/// - parameter granularity: The smallest unit that must, along with all larger units
|
/// - returns: `ComparisonResult`
|
func compare(toDate refDate: DateInRegion, granularity: Calendar.Component) -> ComparisonResult {
|
switch granularity {
|
case .nanosecond:
|
// There is a possible rounding error using Calendar to compare two dates below the minute granularity
|
// So we've added this trick and use standard Date compare which return correct results in this case
|
// https://github.com/malcommac/SwiftDate/issues/346
|
return date.compare(refDate.date)
|
default:
|
return region.calendar.compare(date, to: refDate.date, toGranularity: granularity)
|
}
|
}
|
|
/// Compares whether the receiver is before/before equal `date` based on their components down to a given unit granularity.
|
///
|
/// - Parameters:
|
/// - refDate: reference date
|
/// - orEqual: `true` to also check for equality
|
/// - granularity: smallest unit that must, along with all larger units, be less for the given dates
|
/// - Returns: Boolean
|
func isBeforeDate(_ date: DateInRegion, orEqual: Bool = false, granularity: Calendar.Component) -> Bool {
|
let result = compare(toDate: date, granularity: granularity)
|
return (orEqual ? (result == .orderedSame || result == .orderedAscending) : result == .orderedAscending)
|
}
|
|
/// Compares whether the receiver is after `date` based on their components down to a given unit granularity.
|
///
|
/// - Parameters:
|
/// - refDate: reference date
|
/// - orEqual: `true` to also check for equality
|
/// - granularity: Smallest unit that must, along with all larger units, be greater for the given dates.
|
/// - Returns: Boolean
|
func isAfterDate(_ refDate: DateInRegion, orEqual: Bool = false, granularity: Calendar.Component) -> Bool {
|
let result = compare(toDate: refDate, granularity: granularity)
|
return (orEqual ? (result == .orderedSame || result == .orderedDescending) : result == .orderedDescending)
|
}
|
|
/// Compares equality of two given dates based on their components down to a given unit
|
/// granularity.
|
///
|
/// - parameter date: date to compare
|
/// - parameter granularity: The smallest unit that must, along with all larger units, be equal for the given
|
/// dates to be considered the same.
|
///
|
/// - returns: `true` if the dates are the same down to the given granularity, otherwise `false`
|
func isInside(date: DateInRegion, granularity: Calendar.Component) -> Bool {
|
return (compare(toDate: date, granularity: granularity) == .orderedSame)
|
}
|
|
/// Return `true` if receiver data is contained in the range specified by two dates.
|
///
|
/// - Parameters:
|
/// - startDate: range upper bound date
|
/// - endDate: range lower bound date
|
/// - orEqual: `true` to also check for equality on date and date2, default is `true`
|
/// - granularity: smallest unit that must, along with all larger units, be greater
|
/// - Returns: Boolean
|
func isInRange(date startDate: DateInRegion, and endDate: DateInRegion, orEqual: Bool = true, granularity: Calendar.Component = .nanosecond) -> Bool {
|
return isAfterDate(startDate, orEqual: orEqual, granularity: granularity) && isBeforeDate(endDate, orEqual: orEqual, granularity: granularity)
|
}
|
|
// MARK: - Date Earlier/Later
|
|
/// Return the earlier of two dates, between self and a given date.
|
///
|
/// - Parameter date: The date to compare to self
|
/// - Returns: The date that is earlier
|
func earlierDate(_ date: DateInRegion) -> DateInRegion {
|
return self.date.timeIntervalSince(date.date) <= 0 ? self : date
|
}
|
|
/// Return the later of two dates, between self and a given date.
|
///
|
/// - Parameter date: The date to compare to self
|
/// - Returns: The date that is later
|
func laterDate(_ date: DateInRegion) -> DateInRegion {
|
return self.date.timeIntervalSince(date.date) >= 0 ? self : date
|
}
|
|
/// Returns the difference in the calendar component given (like day, month or year)
|
/// with respect to the other date as a positive integer
|
func difference(in component: Calendar.Component, from other: DateInRegion) -> Int? {
|
return self.date.difference(in: component, from: other.date)
|
}
|
|
/// Returns the differences in the calendar components given (like day, month and year)
|
/// with respect to the other date as dictionary with the calendar component as the key
|
/// and the diffrence as a positive integer as the value
|
func differences(in components: Set<Calendar.Component>, from other: DateInRegion) -> [Calendar.Component: Int] {
|
return self.date.differences(in: components, from: other.date)
|
}
|
|
}
|