// // TimePeriodCollection.swift // DateTools // // Created by Grayson Webster on 8/17/16. // Copyright © 2016 Grayson Webster. All rights reserved. // import Foundation /** * Time period collections serve as loose sets of time periods. They are * unorganized unless you decide to sort them, and have their own characteristics * like a `beginning` and `end` that are extrapolated from the time periods within. Time * period collections allow overlaps within their set of time periods. * * [Visit our github page](https://github.com/MatthewYork/DateTools#time-period-collections) for more information. */ open class TimePeriodCollection: TimePeriodGroup { // MARK: - Collection Manipulation /** * Append a TimePeriodProtocol to the periods array and check if the Collection's * beginning and end should change. * * - parameter period: TimePeriodProtocol to add to the collection */ public func append(_ period: TimePeriodProtocol) { periods.append(period) updateExtremes(period: period) } /** * Append a TimePeriodProtocol array to the periods array and check if the Collection's * beginning and end should change. * * - parameter periodArray: TimePeriodProtocol list to add to the collection */ public func append(_ periodArray: [TimePeriodProtocol]) { for period in periodArray { periods.append(period) updateExtremes(period: period) } } /** * Append a TimePeriodGroup's periods array to the periods array of self and check if the Collection's * beginning and end should change. * * - parameter newPeriods: TimePeriodGroup to merge periods arrays with */ public func append(contentsOf newPeriods: C) { for period in newPeriods as TimePeriodGroup { periods.append(period) updateExtremes(period: period) } } /** * Insert period into periods array at given index. * * - parameter newElement: The period to insert * - parameter index: Index to insert period at */ public func insert(_ newElement: TimePeriodProtocol, at index: Int) { periods.insert(newElement, at: index) updateExtremes(period: newElement) } /** * Remove from period array at the given index. * * - parameter at: The index in the collection to remove */ public func remove(at: Int) { periods.remove(at: at) updateExtremes() } /** * Remove all periods from period array. */ public func removeAll() { periods.removeAll() updateExtremes() } // MARK: - Sorting // In place /** * Sort periods array in place by beginning */ public func sortByBeginning() { sort { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in if period1.beginning == nil, period2.beginning == nil { return false } else if period1.beginning == nil { return true } else if period2.beginning == nil { return false } else { return period2.beginning! < period1.beginning! } } } /** * Sort periods array in place */ public func sort(by areInIncreasingOrder: (TimePeriodProtocol, TimePeriodProtocol) -> Bool) { periods.sort(by: areInIncreasingOrder) } // New collection /** * Return collection with sorted periods array by beginning * * - returns: Collection with sorted periods */ public func sortedByBeginning() -> TimePeriodCollection { let array = periods.sorted { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in if period1.beginning == nil, period2.beginning == nil { return false } else if period1.beginning == nil { return true } else if period2.beginning == nil { return false } else { return period2.beginning! < period1.beginning! } } let collection = TimePeriodCollection() collection.append(array) return collection } /** * Return collection with sorted periods array * * - returns: Collection with sorted periods */ public func sorted(by areInIncreasingOrder: (TimePeriodProtocol, TimePeriodProtocol) -> Bool) -> TimePeriodCollection { let collection = TimePeriodCollection() collection.append(periods.sorted(by: areInIncreasingOrder)) return collection } // MARK: - Collection Relationship // Potentially use .reduce() instead of these functions /** * Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s * whose start and end dates fall completely inside the interval of the given `TimePeriod`. * * - parameter period: The period to compare each other period against * * - returns: Collection of periods inside the given period */ public func allInside(in period: TimePeriodProtocol) -> TimePeriodCollection { let collection = TimePeriodCollection() // Filter by period collection.periods = periods.filter { (timePeriod: TimePeriodProtocol) -> Bool in timePeriod.isInside(of: period) } return collection } /** * Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s containing * the given date. * * - parameter date: The date to compare each period to * * - returns: Collection of periods intersected by the given date */ public func periodsIntersected(by date: Date) -> TimePeriodCollection { let collection = TimePeriodCollection() // Filter by period collection.periods = periods.filter { (timePeriod: TimePeriodProtocol) -> Bool in timePeriod.contains(date, interval: .closed) } return collection } /** * Returns from the `TimePeriodCollection` a sub-collection of `TimePeriod`s * containing either the start date or the end date--or both--of the given `TimePeriod`. * * - parameter period: The period to compare each other period to * * - returns: Collection of periods intersected by the given period */ public func periodsIntersected(by period: TimePeriodProtocol) -> TimePeriodCollection { let collection = TimePeriodCollection() // Filter by periop collection.periods = periods.filter { (timePeriod: TimePeriodProtocol) -> Bool in timePeriod.intersects(with: period) } return collection } // MARK: - Map public func map(_ transform: (TimePeriodProtocol) throws -> TimePeriodProtocol) rethrows -> TimePeriodCollection { var mappedArray = [TimePeriodProtocol]() mappedArray = try periods.map(transform) let mappedCollection = TimePeriodCollection() for period in mappedArray { mappedCollection.periods.append(period) mappedCollection.updateExtremes(period: period) } return mappedCollection } // MARK: - Operator Overloads /** * Operator overload for comparing `TimePeriodCollection`s to each other */ public static func == (left: TimePeriodCollection, right: TimePeriodCollection) -> Bool { return left.equals(right) } // MARK: - Helpers internal func updateExtremes(period: TimePeriodProtocol) { // Check incoming period against previous beginning and end date if count == 1 { _beginning = period.beginning _end = period.end } else { _beginning = nilOrEarlier(date1: _beginning, date2: period.beginning) _end = nilOrLater(date1: _end, date2: period.end) } } internal func updateExtremes() { if periods.isEmpty { _beginning = nil _end = nil } else { _beginning = periods[0].beginning _end = periods[0].end for i in 1 ..< periods.count { _beginning = nilOrEarlier(date1: _beginning, date2: periods[i].beginning) _end = nilOrEarlier(date1: _end, date2: periods[i].end) } } } internal func nilOrEarlier(date1: Date?, date2: Date?) -> Date? { if date1 == nil || date2 == nil { return nil } else { return date1!.earlierDate(date2!) } } internal func nilOrLater(date1: Date?, date2: Date?) -> Date? { if date1 == nil || date2 == nil { return nil } else { return date1!.laterDate(date2!) } } }