123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- //
- // 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: - Date Components Extensions
- public extension Calendar.Component {
- /// Return a description of the calendar component in seconds.
- /// Note: Values for `era`,`weekday`,`weekdayOrdinal`, `yearForWeekOfYear`, `calendar`, `timezone` are `nil`.
- /// For `weekOfYear` it return the same value of `weekOfMonth`.
- var timeInterval: Double? {
- switch self {
- case .era: return nil
- case .year: return (Calendar.Component.day.timeInterval! * 365.0)
- case .month: return (Calendar.Component.minute.timeInterval! * 43800)
- case .day: return 86400
- case .hour: return 3600
- case .minute: return 60
- case .second: return 1
- case .quarter: return (Calendar.Component.day.timeInterval! * 91.25)
- case .weekOfMonth, .weekOfYear: return (Calendar.Component.day.timeInterval! * 7)
- case .nanosecond: return 1e-9
- default: return nil
- }
- }
- /// Return the localized identifier of a calendar component
- ///
- /// - parameter unit: unit
- /// - parameter value: value
- ///
- /// - returns: return the plural or singular form of the time unit used to compose a valid identifier for search a localized
- /// string in resource bundle
- internal func localizedKey(forValue value: Int) -> String {
- let locKey = localizedKey
- let absValue = abs(value)
- switch absValue {
- case 0: // zero difference for this unit
- return "0\(locKey)"
- case 1: // one unit of difference
- return locKey
- default: // more than 1 unit of difference
- return "\(locKey)\(locKey)"
- }
- }
- internal var localizedKey: String {
- switch self {
- case .year: return "y"
- case .month: return "m"
- case .weekOfYear: return "w"
- case .day: return "d"
- case .hour: return "h"
- case .minute: return "M"
- case .second: return "s"
- default:
- return ""
- }
- }
- }
- public extension DateComponents {
- /// Shortcut for 'all calendar components'.
- static var allComponentsSet: Set<Calendar.Component> {
- return [.era, .year, .month, .day, .hour, .minute,
- .second, .weekday, .weekdayOrdinal, .quarter,
- .weekOfMonth, .weekOfYear, .yearForWeekOfYear,
- .nanosecond, .calendar, .timeZone]
- }
- internal static let allComponents: [Calendar.Component] = [.nanosecond, .second, .minute, .hour,
- .day, .month, .year, .yearForWeekOfYear,
- .weekOfYear, .weekday, .quarter, .weekdayOrdinal,
- .weekOfMonth]
- /// This function return the absolute amount of seconds described by the components of the receiver.
- /// Note: evaluated value maybe not strictly exact because it ignore the context (calendar/date) of
- /// the date components. In details:
- /// - The following keys are ignored: `era`,`weekday`,`weekdayOrdinal`,
- /// `weekOfYear`, `yearForWeekOfYear`, `calendar`, `timezone
- ///
- /// Some other values dependant from dates are fixed. This is a complete table:
- /// - `year` is 365.0 `days`
- /// - `month` is 30.4167 `days` (or 43800 minutes)
- /// - `quarter` is 91.25 `days`
- /// - `weekOfMonth` is 7 `days`
- /// - `day` is 86400 `seconds`
- /// - `hour` is 3600 `seconds`
- /// - `minute` is 60 `seconds`
- /// - `nanosecond` is 1e-9 `seconds`
- var timeInterval: TimeInterval {
- var totalAmount: TimeInterval = 0
- DateComponents.allComponents.forEach {
- if let multipler = $0.timeInterval, let value = value(for: $0), value != Int(NSDateComponentUndefined) {
- totalAmount += (TimeInterval(value) * multipler)
- }
- }
- return totalAmount
- }
- /// Create a new `DateComponents` instance with builder pattern.
- ///
- /// - Parameter builder: callback for builder
- /// - Returns: new instance
- static func create(_ builder: ((inout DateComponents) -> Void)) -> DateComponents {
- var components = DateComponents()
- builder(&components)
- return components
- }
- /// Return the current date plus the receive's interval
- /// The default calendar used is the `SwiftDate.defaultRegion`'s calendar.
- var fromNow: Date {
- return SwiftDate.defaultRegion.calendar.date(byAdding: (self as DateComponents) as DateComponents, to: Date() as Date)!
- }
- /// Returns the current date minus the receiver's interval
- /// The default calendar used is the `SwiftDate.defaultRegion`'s calendar.
- var ago: Date {
- return SwiftDate.defaultRegion.calendar.date(byAdding: -self as DateComponents, to: Date())!
- }
- /// - returns: the date that will occur once the receiver's components pass after the provide date.
- func from(_ date: DateRepresentable) -> Date? {
- return date.calendar.date(byAdding: self, to: date.date)
- }
- /// Return `true` if all interval components are zeroes
- var isZero: Bool {
- for component in DateComponents.allComponents {
- if let value = value(for: component), value != 0 {
- return false
- }
- }
- return true
- }
- /// Transform a `DateComponents` instance to a dictionary where key is the `Calendar.Component` and value is the
- /// value associated.
- ///
- /// - returns: a new `[Calendar.Component : Int]` dict representing source `DateComponents` instance
- internal func toDict() -> [Calendar.Component: Int] {
- var list: [Calendar.Component: Int] = [:]
- DateComponents.allComponents.forEach { component in
- let value = self.value(for: component)
- if value != nil && value != Int(NSDateComponentUndefined) {
- list[component] = value!
- }
- }
- return list
- }
- /// Alter date components specified into passed dictionary.
- ///
- /// - Parameter components: components dictionary with their values.
- internal mutating func alterComponents(_ components: [Calendar.Component: Int?]) {
- components.forEach {
- if let v = $0.value {
- setValue(v, for: $0.key)
- }
- }
- }
- /// Adds two NSDateComponents and returns their combined individual components.
- static func + (lhs: DateComponents, rhs: DateComponents) -> DateComponents {
- return combine(lhs, rhs: rhs, transform: +)
- }
- /// Subtracts two NSDateComponents and returns the relative difference between them.
- static func - (lhs: DateComponents, rhs: DateComponents) -> DateComponents {
- return lhs + (-rhs)
- }
- /// Applies the `transform` to the two `T` provided, defaulting either of them if it's
- /// `nil`
- internal static func bimap<T>(_ a: T?, _ b: T?, default: T, _ transform: (T, T) -> T) -> T? {
- if a == nil && b == nil { return nil }
- return transform(a ?? `default`, b ?? `default`)
- }
- /// - returns: a new `NSDateComponents` that represents the negative of all values within the
- /// components that are not `NSDateComponentUndefined`.
- static prefix func - (rhs: DateComponents) -> DateComponents {
- var components = DateComponents()
- components.era = rhs.era.map(-)
- components.year = rhs.year.map(-)
- components.month = rhs.month.map(-)
- components.day = rhs.day.map(-)
- components.hour = rhs.hour.map(-)
- components.minute = rhs.minute.map(-)
- components.second = rhs.second.map(-)
- components.nanosecond = rhs.nanosecond.map(-)
- components.weekday = rhs.weekday.map(-)
- components.weekdayOrdinal = rhs.weekdayOrdinal.map(-)
- components.quarter = rhs.quarter.map(-)
- components.weekOfMonth = rhs.weekOfMonth.map(-)
- components.weekOfYear = rhs.weekOfYear.map(-)
- components.yearForWeekOfYear = rhs.yearForWeekOfYear.map(-)
- return components
- }
- /// Combines two date components using the provided `transform` on all
- /// values within the components that are not `NSDateComponentUndefined`.
- private static func combine(_ lhs: DateComponents, rhs: DateComponents, transform: (Int, Int) -> Int) -> DateComponents {
- var components = DateComponents()
- components.era = bimap(lhs.era, rhs.era, default: 0, transform)
- components.year = bimap(lhs.year, rhs.year, default: 0, transform)
- components.month = bimap(lhs.month, rhs.month, default: 0, transform)
- components.day = bimap(lhs.day, rhs.day, default: 0, transform)
- components.hour = bimap(lhs.hour, rhs.hour, default: 0, transform)
- components.minute = bimap(lhs.minute, rhs.minute, default: 0, transform)
- components.second = bimap(lhs.second, rhs.second, default: 0, transform)
- components.nanosecond = bimap(lhs.nanosecond, rhs.nanosecond, default: 0, transform)
- components.weekday = bimap(lhs.weekday, rhs.weekday, default: 0, transform)
- components.weekdayOrdinal = bimap(lhs.weekdayOrdinal, rhs.weekdayOrdinal, default: 0, transform)
- components.quarter = bimap(lhs.quarter, rhs.quarter, default: 0, transform)
- components.weekOfMonth = bimap(lhs.weekOfMonth, rhs.weekOfMonth, default: 0, transform)
- components.weekOfYear = bimap(lhs.weekOfYear, rhs.weekOfYear, default: 0, transform)
- components.yearForWeekOfYear = bimap(lhs.yearForWeekOfYear, rhs.yearForWeekOfYear, default: 0, transform)
- return components
- }
- /// Subscription support for `DateComponents` instances.
- /// ie. `cmps[.day] = 5`
- ///
- /// Note: This does not take into account any built-in errors, `Int.max` returned instead of `nil`.
- ///
- /// - Parameter component: component to get
- subscript(component: Calendar.Component) -> Int? {
- switch component {
- case .era: return era
- case .year: return year
- case .month: return month
- case .day: return day
- case .hour: return hour
- case .minute: return minute
- case .second: return second
- case .weekday: return weekday
- case .weekdayOrdinal: return weekdayOrdinal
- case .quarter: return quarter
- case .weekOfMonth: return weekOfMonth
- case .weekOfYear: return weekOfYear
- case .yearForWeekOfYear: return yearForWeekOfYear
- case .nanosecond: return nanosecond
- default: return nil // `calendar` and `timezone` are ignored in this context
- }
- }
- /// Express a `DateComponents` instance in another time unit you choose.
- ///
- /// - parameter component: time component
- /// - parameter calendar: context calendar to use
- ///
- /// - returns: the value of interval expressed in selected `Calendar.Component`
- func `in`(_ component: Calendar.Component, of calendar: CalendarConvertible? = nil) -> Int? {
- let cal = (calendar?.toCalendar() ?? SwiftDate.defaultRegion.calendar)
- let dateFrom = Date()
- let dateTo = (dateFrom + self)
- let components: Set<Calendar.Component> = [component]
- let value = cal.dateComponents(components, from: dateFrom, to: dateTo).value(for: component)
- return value
- }
- /// Express a `DateComponents` instance in a set of time units you choose.
- ///
- /// - Parameters:
- /// - component: time component
- /// - calendar: context calendar to use
- /// - Returns: a dictionary of extract values.
- func `in`(_ components: Set<Calendar.Component>, of calendar: CalendarConvertible? = nil) -> [Calendar.Component: Int] {
- let cal = (calendar?.toCalendar() ?? SwiftDate.defaultRegion.calendar)
- let dateFrom = Date()
- let dateTo = (dateFrom + self)
- let extractedCmps = cal.dateComponents(components, from: dateFrom, to: dateTo)
- return extractedCmps.toDict()
- }
- }
|