You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
267 lines
8.7 KiB
267 lines
8.7 KiB
// |
|
// 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<C: TimePeriodGroup>(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!) |
|
} |
|
} |
|
}
|
|
|