宽窄优行-由【嘉易行】项目成品而来
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
327
328
//
//  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
 
public protocol TimePeriodProtocol {
 
    /// The start date for a TimePeriod representing the starting boundary of the time period
    var start: DateInRegion? { get set }
 
    /// The end date for a TimePeriod representing the ending boundary of the time period
    var end: DateInRegion? { get set }
 
}
 
public extension TimePeriodProtocol {
 
    /// Return `true` if time period has both start and end dates
    var hasFiniteRange: Bool {
        guard start != nil && end != nil else { return false }
        return true
    }
 
    /// Return `true` if period has a start date
    var hasStart: Bool {
        return (start != nil)
    }
 
    /// Return `true` if period has a end date
    var hasEnd: Bool {
        return (end != nil)
    }
 
    /// Check if receiver is equal to given period (both start/end groups are equals)
    ///
    /// - Parameter period: period to compare against to.
    /// - Returns: true if are equals
    func equals(_ period: TimePeriodProtocol) -> Bool {
        return (start == period.start && end == period.end)
    }
 
    /// If the given `TimePeriod`'s beginning is before `beginning` and
    /// if the given 'TimePeriod`'s end is after `end`.
    ///
    /// - Parameter period: The time period to compare to self
    /// - Returns: True if self is inside of the given `TimePeriod`
    func isInside(_ period: TimePeriodProtocol) -> Bool {
        guard hasFiniteRange, period.hasFiniteRange else { return false }
        return (period.start! <= start! && period.end! >= end!)
    }
 
    /// If the given Date is after `beginning` and before `end`.
    ///
    /// - Parameters:
    ///   - date: The time period to compare to self
    ///   - interval: Whether the edge of the date is included in the calculation
    /// - Returns: True if the given `TimePeriod` is inside of self
    func contains(date: DateInRegion, interval: IntervalType = .closed) -> Bool {
        guard hasFiniteRange else { return false }
        switch interval {
        case .closed:    return (start! <= date && end! >= date)
        case .open:        return (start! < date && end! > date)
        }
    }
 
    /// If the given `TimePeriod`'s beginning is after `beginning` and
    /// if the given 'TimePeriod`'s after is after `end`.
    ///
    /// - Parameter period: The time period to compare to self
    /// - Returns: True if the given `TimePeriod` is inside of self
    func contains(_ period: TimePeriodProtocol) -> Bool {
        guard hasFiniteRange, period.hasFiniteRange else { return false }
        if period.start! < start! && period.end! > start! {
            return true // Outside -> Inside
        } else if period.start! >= start! && period.end! <= end! {
            return true // Enclosing
        } else if period.start! < end! && period.end! > end! {
            return true // Inside -> Out
        }
        return false
    }
 
    /// If self and the given `TimePeriod` share any sub-`TimePeriod`.
    ///
    /// - Parameter period: The time period to compare to self
    /// - Returns: True if there is a period of time that is shared by both `TimePeriod`s
    func overlaps(with period: TimePeriodProtocol) -> Bool {
        if period.start! < start! && period.end! > start! {
            return true // Outside -> Inside
        } else if period.start! >= start! && period.end! <= end! {
            return true // Enclosing
        } else if period.start! < end! && period.end! > end! {
            return true // Inside -> Out
        }
        return false
    }
 
    /// If self and the given `TimePeriod` overlap or the period's edges touch.
    ///
    /// - Parameter period: The time period to compare to self
    /// - Returns: True if there is a period of time or moment that is shared by both `TimePeriod`s
    func intersects(with period: TimePeriodProtocol) -> Bool {
        let relation = self.relation(to: period)
        return (relation != .after && relation != .before)
    }
 
    /// If self is before the given `TimePeriod` chronologically. (A gap must exist between the two).
    ///
    /// - Parameter period: The time period to compare to self
    /// - Returns: True if self is after the given `TimePeriod`
    func isBefore(_ period: TimePeriodProtocol) -> Bool {
        return (relation(to: period) == .before)
    }
 
    /// If self is after the given `TimePeriod` chronologically. (A gap must exist between the two).
    ///
    /// - Parameter period: The time period to compare to self
    /// - Returns: True if self is after the given `TimePeriod`
    func isAfter(_ period: TimePeriodProtocol) -> Bool {
        return (relation(to: period) == .after)
    }
 
    /// The period of time between self and the given `TimePeriod` not contained by either.
    ///
    /// - Parameter period: The time period to compare to self
    /// - Returns: The gap between the periods. Zero if there is no gap.
    func hasGap(between period: TimePeriodProtocol) -> Bool {
        return (isBefore(period) || isAfter(period))
    }
 
    /// The period of time between self and the given `TimePeriod` not contained by either.
    ///
    /// - Parameter period: The time period to compare to self
    /// - Returns: The gap between the periods. Zero if there is no gap.
    func gap(between period: TimePeriodProtocol) -> TimeInterval {
        guard hasFiniteRange, period.hasFiniteRange else { return TimeInterval.greatestFiniteMagnitude }
        if end! < period.start! {
            return abs(end!.timeIntervalSince(period.start!))
        } else if period.end! < start! {
            return abs(end!.timeIntervalSince(start!))
        }
        return 0
    }
 
    /// In place, shift the `TimePeriod` by a `TimeInterval`
    ///
    /// - Parameter timeInterval: The time interval to shift the period by
    mutating func shift(by timeInterval: TimeInterval) {
        start?.addTimeInterval(timeInterval)
        end?.addTimeInterval(timeInterval)
    }
 
    /// In place, lengthen the `TimePeriod`, anchored at the beginning, end or center
    ///
    /// - Parameters:
    ///   - timeInterval: The time interval to lengthen the period by
    ///   - anchor: The anchor point from which to make the change
    mutating func lengthen(by timeInterval: TimeInterval, at anchor: TimePeriodAnchor) {
        switch anchor {
        case .beginning:
            end?.addTimeInterval(timeInterval)
        case .end:
            start?.addTimeInterval(timeInterval)
        case .center:
            start = start?.addingTimeInterval(-timeInterval / 2.0)
            end = end?.addingTimeInterval(timeInterval / 2.0)
        }
    }
 
    /// In place, shorten the `TimePeriod`, anchored at the beginning, end or center
    ///
    /// - Parameters:
    ///   - timeInterval: The time interval to shorten the period by
    ///   - anchor: The anchor point from which to make the change
    mutating func shorten(by timeInterval: TimeInterval, at anchor: TimePeriodAnchor) {
        switch anchor {
        case .beginning:
            end?.addTimeInterval(-timeInterval)
        case .end:
            start?.addTimeInterval(timeInterval)
        case .center:
            start?.addTimeInterval(timeInterval / 2.0)
            end?.addTimeInterval(-timeInterval / 2.0)
        }
    }
 
    /// The relationship of the self `TimePeriod` to the given `TimePeriod`.
    /// Relations are stored in Enums.swift. Formal defnitions available in the provided
    /// links:
    /// [GitHub](https://github.com/MatthewYork/DateTools#relationships),
    /// [CodeProject](http://www.codeproject.com/Articles/168662/Time-Period-Library-for-NET)
    ///
    /// - Parameter period: The time period to compare to self
    /// - Returns: The relationship between self and the given time period
    func relation(to period: TimePeriodProtocol) -> TimePeriodRelation {
        //Make sure that all start and end points exist for comparison
        guard hasFiniteRange, period.hasFiniteRange else { return .none }
        //Make sure time periods are of positive durations
        guard start! < end! && period.start! < period.end! else { return .none }
        //Make comparisons
        if period.start! < start! {
            return .after
        } else if period.end! == start! {
            return .startTouching
        } else if period.start! < start! && period.end! < end! {
            return .startInside
        } else if period.start! == start! && period.end! > end! {
            return .insideStartTouching
        } else if period.start! == start! && period.end! < end! {
            return .enclosingStartTouching
        } else if period.start! > start! && period.end! < end! {
            return .enclosing
        } else if period.start! > start! && period.end! == end! {
            return .enclosingEndTouching
        } else if period.start == start! && period.end! == end! {
            return .exactMatch
        } else if period.start! < start! && period.end! > end! {
            return .inside
        } else if period.start! < start! && period.end! == end! {
            return .insideEndTouching
        } else if period.start! < end! && period.end! > end! {
            return .endInside
        } else if period.start! == end! && period.end! > end! {
            return .endTouching
        } else if period.start! > end! {
            return .before
        }
        return .none
    }
 
    /// Return `true` if period is zero-seconds long or less than specified precision.
    ///
    /// - Parameter precision: precision in seconds; by default is 0.
    /// - Returns: true if start/end has the same value or less than specified precision
    func isMoment(precision: TimeInterval = 0) -> Bool {
        guard hasFiniteRange else { return false }
        return (abs(start!.date.timeIntervalSince1970 - end!.date.timeIntervalSince1970) <= precision)
    }
 
    /// Returns the duration of the receiver expressed with given time unit.
    /// If time period has not a finite range it returns `nil`.
    ///
    /// - Parameter unit: unit of the duration
    /// - Returns: duration, `nil` if period has not a finite range
    func durationIn(_ units: Set<Calendar.Component>) -> DateComponents? {
        guard hasFiniteRange else { return nil }
        return start!.calendar.dateComponents(units, from: start!.date, to: end!.date)
    }
 
    /// Returns the duration of the receiver expressed with given time unit.
    /// If time period has not a finite range it returns `nil`.
    ///
    /// - Parameter unit: unit of the duration
    /// - Returns: duration, `nil` if period has not a finite range
    func durationIn(_ unit: Calendar.Component) -> Int? {
        guard hasFiniteRange else { return nil }
        return start!.calendar.dateComponents([unit], from: start!.date, to: end!.date).value(for: unit)
    }
 
    /// The duration of the `TimePeriod` in years.
    /// Returns the `Int.max` if beginning or end are `nil`.
    var years: Int {
        guard let b = start, let e = end else { return Int.max }
        return b.toUnit(.year, to: e)
    }
 
    /// The duration of the `TimePeriod` in months.
    /// Returns the `Int.max` if beginning or end are `nil`.
    var months: Int {
        guard let b = start, let e = end else { return Int.max }
        return b.toUnit(.month, to: e)
    }
 
    /// The duration of the `TimePeriod` in weeks.
    /// Returns the `Int.max` if beginning or end are `nil`.
    var weeks: Int {
        guard let b = start, let e = end else { return Int.max }
        return b.toUnit(.weekOfMonth, to: e)
    }
 
    /// The duration of the `TimePeriod` in days.
    /// Returns the `Int.max` if beginning or end are `nil`.
    var days: Int {
        guard let b = start, let e = end else { return Int.max }
        return b.toUnit(.day, to: e)
    }
 
    /// The duration of the `TimePeriod` in hours.
    /// Returns the `Int.max` if beginning or end are `nil`.
    var hours: Int {
        guard let b = start, let e = end else { return Int.max }
        return b.toUnit(.hour, to: e)
    }
 
    /// The duration of the `TimePeriod` in years.
    /// Returns the `Int.max` if beginning or end are `nil`.
    var minutes: Int {
        guard let b = start, let e = end else { return Int.max }
        return b.toUnit(.minute, to: e)
    }
 
    /// The duration of the `TimePeriod` in seconds.
    /// Returns the `Int.max` if beginning or end are `nil`.
    var seconds: Int {
        guard let b = start, let e = end else { return Int.max }
        return b.toUnit(.second, to: e)
    }
 
    /// The length of time between the beginning and end dates of the
    /// `TimePeriod` as a `TimeInterval`.
    /// If intervals are not nil returns `Double.greatestFiniteMagnitude`
    var duration: TimeInterval {
        guard let b = start, let e = end else {
            return TimeInterval(Double.greatestFiniteMagnitude)
        }
        return abs(b.date.timeIntervalSince(e.date))
    }
 
}