宽窄优行-由【嘉易行】项目成品而来
younger_times
2023-04-06 a1ae6802080a22e6e6ce6d0935e95facb1daca5c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
//
//  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)
    }
 
}