宽窄优行-由【嘉易行】项目成品而来
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
//
//  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
 
/// Time period chains serve as a tightly coupled set of time periods.
/// They are always organized by start and end date, and have their own characteristics like
/// a StartDate and EndDate that are extrapolated from the time periods within.
/// Time period chains do not allow overlaps within their set of time periods.
/// This type of group is ideal for modeling schedules like sequential meetings or appointments.
open class TimePeriodChain: TimePeriodGroup {
 
    // MARK: - Chain Existence Manipulation
 
    /**
    *  Append a TimePeriodProtocol to the periods array and update the Chain's
    *  beginning and end.
    *
    * - parameter period: TimePeriodProtocol to add to the collection
    */
    public func append(_ period: TimePeriodProtocol) {
        let beginning = (periods.count > 0) ? periods.last!.end! : period.start
 
        let newPeriod = TimePeriod(start: beginning!, duration: period.duration)
        periods.append(newPeriod)
 
        //Update updateExtremes
        if periods.count == 1 {
            start = period.start
            end = period.end
        } else {
            end = end?.addingTimeInterval(period.duration)
        }
    }
 
    /**
    *  Append a TimePeriodProtocol array to the periods array and update the Chain's
    *  beginning and end.
    *
    * - parameter periodArray: TimePeriodProtocol list to add to the collection
    */
    public func append<G: TimePeriodGroup>(contentsOf group: G) {
        for period in group.periods {
            let beginning = (periods.count > 0) ? periods.last!.end! : period.start
 
            let newPeriod = TimePeriod(start: beginning!, duration: period.duration)
            periods.append(newPeriod)
 
            //Update updateExtremes
            if periods.count == 1 {
                start = period.start
                end = period.end
            } else {
                end = end?.addingTimeInterval(period.duration)
            }
        }
    }
 
    /// Insert period into periods array at given index.
    ///
    /// - Parameters:
    ///   - period: The period to insert
    ///   - index: Index to insert period at
    public func insert(_ period: TimePeriodProtocol, at index: Int) {
        //Check for special zero case which takes the beginning date
        if index == 0 && period.start != nil && period.end != nil {
            //Insert new period
            periods.insert(period, at: index)
        } else if period.start != nil && period.end != nil {
            //Insert new period
            periods.insert(period, at: index)
        } else {
            print("All TimePeriods in a TimePeriodChain must contain a defined start and end date")
            return
        }
 
        //Shift all periods after inserted period
        for i in 0..<periods.count {
            if i > index && i > 0 {
                let currentPeriod = TimePeriod(start: period.start, end: period.end)
                periods[i].start = periods[i - 1].end
                periods[i].end = periods[i].start!.addingTimeInterval(currentPeriod.duration)
            }
        }
 
        updateExtremes()
    }
 
    /// Remove from period array at the given index.
    ///
    /// - Parameter index: The index in the collection to remove
    public func remove(at index: Int) {
        //Retrieve duration of period to be removed
        let duration = periods[index].duration
 
        //Remove period
        periods.remove(at: index)
 
        //Shift all periods after inserted period
        for i in index..<periods.count {
            periods[i].shift(by: -duration)
        }
        updateExtremes()
    }
 
    /// Remove all periods from period array.
    public func removeAll() {
        periods.removeAll()
        updateExtremes()
    }
 
    // MARK: - Chain Content Manipulation
 
    /// In place, shifts all chain time periods by a given time interval
    ///
    /// - Parameter duration: The time interval to shift the period by
    public func shift(by duration: TimeInterval) {
        for var period in periods {
            period.shift(by: duration)
        }
        start = start?.addingTimeInterval(duration)
        end = end?.addingTimeInterval(duration)
    }
 
    public override func map<T>(_ transform: (TimePeriodProtocol) throws -> T) rethrows -> [T] {
        return try periods.map(transform)
    }
 
    public override func filter(_ isIncluded: (TimePeriodProtocol) throws -> Bool) rethrows -> [TimePeriodProtocol] {
        return try periods.filter(isIncluded)
    }
 
    internal override func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, TimePeriodProtocol) throws -> Result) rethrows -> Result {
        return try periods.reduce(initialResult, nextPartialResult)
    }
 
    /// Removes the last object from the `TimePeriodChain` and returns it
    public func pop() -> TimePeriodProtocol? {
        let period = periods.popLast()
        updateExtremes()
 
        return period
    }
 
    internal func updateExtremes() {
        start = periods.first?.start
        end = periods.last?.end
    }
 
}