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.

268 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
5 years ago
/**
* Append a TimePeriodProtocol to the periods array and check if the Collection's
* beginning and end should change.
5 years ago
*
* - parameter period: TimePeriodProtocol to add to the collection
*/
public func append(_ period: TimePeriodProtocol) {
periods.append(period)
updateExtremes(period: period)
}
5 years ago
/**
* 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)
}
}
5 years ago
/**
* 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)
}
}
5 years ago
/**
* 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)
}
5 years ago
/**
* 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()
}
5 years ago
/**
* Remove all periods from period array.
*/
public func removeAll() {
periods.removeAll()
updateExtremes()
}
5 years ago
// MARK: - Sorting
5 years ago
// In place
/**
* Sort periods array in place by beginning
*/
public func sortByBeginning() {
5 years ago
sort { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in
if period1.beginning == nil, period2.beginning == nil {
return false
5 years ago
} else if period1.beginning == nil {
return true
5 years ago
} else if period2.beginning == nil {
return false
} else {
return period2.beginning! < period1.beginning!
}
}
}
5 years ago
/**
* Sort periods array in place
*/
public func sort(by areInIncreasingOrder: (TimePeriodProtocol, TimePeriodProtocol) -> Bool) {
5 years ago
periods.sort(by: areInIncreasingOrder)
}
5 years ago
// New collection
/**
* Return collection with sorted periods array by beginning
*
* - returns: Collection with sorted periods
*/
public func sortedByBeginning() -> TimePeriodCollection {
5 years ago
let array = periods.sorted { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in
if period1.beginning == nil, period2.beginning == nil {
return false
5 years ago
} else if period1.beginning == nil {
return true
5 years ago
} else if period2.beginning == nil {
return false
} else {
return period2.beginning! < period1.beginning!
}
}
let collection = TimePeriodCollection()
collection.append(array)
return collection
}
5 years ago
/**
* Return collection with sorted periods array
*
* - returns: Collection with sorted periods
*/
public func sorted(by areInIncreasingOrder: (TimePeriodProtocol, TimePeriodProtocol) -> Bool) -> TimePeriodCollection {
let collection = TimePeriodCollection()
5 years ago
collection.append(periods.sorted(by: areInIncreasingOrder))
return collection
}
5 years ago
// MARK: - Collection Relationship
5 years ago
// 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
5 years ago
collection.periods = periods.filter { (timePeriod: TimePeriodProtocol) -> Bool in
timePeriod.isInside(of: period)
}
return collection
}
5 years ago
/**
5 years ago
* 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
5 years ago
collection.periods = periods.filter { (timePeriod: TimePeriodProtocol) -> Bool in
timePeriod.contains(date, interval: .closed)
}
return collection
}
5 years ago
/**
5 years ago
* 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()
5 years ago
// Filter by periop
collection.periods = periods.filter { (timePeriod: TimePeriodProtocol) -> Bool in
timePeriod.intersects(with: period)
}
return collection
}
5 years ago
// MARK: - Map
5 years ago
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
}
5 years ago
// MARK: - Operator Overloads
5 years ago
/**
* Operator overload for comparing `TimePeriodCollection`s to each other
*/
5 years ago
public static func == (left: TimePeriodCollection, right: TimePeriodCollection) -> Bool {
return left.equals(right)
}
5 years ago
// MARK: - Helpers
internal func updateExtremes(period: TimePeriodProtocol) {
5 years ago
// 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)
}
}
5 years ago
internal func updateExtremes() {
5 years ago
if periods.isEmpty {
_beginning = nil
_end = nil
} else {
_beginning = periods[0].beginning
_end = periods[0].end
5 years ago
for i in 1 ..< periods.count {
_beginning = nilOrEarlier(date1: _beginning, date2: periods[i].beginning)
_end = nilOrEarlier(date1: _end, date2: periods[i].end)
}
}
}
5 years ago
internal func nilOrEarlier(date1: Date?, date2: Date?) -> Date? {
if date1 == nil || date2 == nil {
return nil
} else {
return date1!.earlierDate(date2!)
}
}
5 years ago
internal func nilOrLater(date1: Date?, date2: Date?) -> Date? {
if date1 == nil || date2 == nil {
return nil
} else {
return date1!.laterDate(date2!)
}
}
}