Abhishek Banthia
3 years ago
72 changed files with 661 additions and 1681 deletions
@ -0,0 +1,139 @@
|
||||
// Copyright © 2015 Abhishek Banthia |
||||
|
||||
import CoreModelKit |
||||
import XCTest |
||||
|
||||
@testable import Clocker |
||||
|
||||
class SearchDataSourceTests: XCTestCase { |
||||
private var subject: SearchDataSource! |
||||
|
||||
private func setupSubject(searchText: String = "") { |
||||
let mockSearchField = NSSearchField() |
||||
mockSearchField.stringValue = searchText |
||||
subject = SearchDataSource(with: mockSearchField, location: .preferences) |
||||
} |
||||
|
||||
private func setupMockData() { |
||||
subject.searchTimezones("los") |
||||
XCTAssertTrue(subject.calculateChangesets()) |
||||
|
||||
let mockTimezone = TimezoneData() |
||||
mockTimezone.timezoneID = "PST" |
||||
mockTimezone.formattedAddress = "Los Angeles" |
||||
subject.setFilteredArrayValue([mockTimezone]) |
||||
|
||||
subject.searchTimezones("los") |
||||
XCTAssertTrue(subject.calculateChangesets()) |
||||
} |
||||
|
||||
override func tearDownWithError() throws { |
||||
subject = nil |
||||
try super.tearDownWithError() |
||||
} |
||||
|
||||
func testSearchTimezones() { |
||||
setupSubject(searchText: "") |
||||
// Test capitalized string |
||||
subject.searchTimezones("MUMBAI") |
||||
XCTAssert(subject.timezoneFilteredArray.isEmpty == false) |
||||
|
||||
// Test sentence-cased string |
||||
subject.searchTimezones("Delhi") |
||||
XCTAssert(subject.timezoneFilteredArray.isEmpty == false) |
||||
|
||||
// Test lower-cased string |
||||
subject.searchTimezones("california") |
||||
XCTAssert(subject.timezoneFilteredArray.isEmpty == false) |
||||
} |
||||
|
||||
func testCalculateChangesets() { |
||||
setupSubject(searchText: "los") |
||||
setupMockData() |
||||
|
||||
subject.cleanupFilterArray() |
||||
subject.searchTimezones("los") |
||||
XCTAssertTrue(subject.calculateChangesets()) |
||||
} |
||||
|
||||
func testRetrieveResult() throws { |
||||
setupSubject(searchText: "los") |
||||
setupMockData() |
||||
|
||||
// 0 will translate to a city search result |
||||
let result1 = subject.retrieveResult(0) |
||||
let unwrap = try XCTUnwrap(result1) |
||||
if let metadata = unwrap as? CoreModelKit.TimezoneData { |
||||
XCTAssert(metadata.timezoneID == "PST") |
||||
} |
||||
|
||||
// 1 will translate to a timezone search result |
||||
let result2 = subject.retrieveResult(1) |
||||
let unwrap2 = try XCTUnwrap(result2) |
||||
if let metadata = unwrap2 as? TimezoneMetadata { |
||||
XCTAssert(metadata.timezone.name == "America/Tijuana") |
||||
} |
||||
|
||||
// Test placeForRow |
||||
let rowType = subject.placeForRow(0) |
||||
XCTAssert(rowType == .city) |
||||
|
||||
let rowType1 = subject.placeForRow(1) |
||||
XCTAssert(rowType1 == .timezone) |
||||
|
||||
// Test count |
||||
XCTAssertEqual(subject.resultsCount(), 4) |
||||
|
||||
// Test retrieveFilteredResultFromGoogleAPI |
||||
let firstResult = try XCTUnwrap(subject.retrieveFilteredResultFromGoogleAPI(0)) |
||||
XCTAssert(firstResult.timezoneID == "PST") |
||||
// filteredArray should only have a count of 1 |
||||
XCTAssertNil(subject.retrieveFilteredResultFromGoogleAPI(1)) |
||||
} |
||||
|
||||
func testTableViewDataSourceMethods() { |
||||
let mockTableView = NSTableView(frame: CGRect.zero) |
||||
setupSubject(searchText: "los") |
||||
setupMockData() |
||||
|
||||
let resultsCount = subject.numberOfRows(in: mockTableView) |
||||
XCTAssert(resultsCount == 4) |
||||
XCTAssert(subject.tableView(mockTableView, heightOfRow: 0) == 30) |
||||
} |
||||
|
||||
func testRetrieveSelectedTimezone() { |
||||
setupSubject(searchText: "los") |
||||
setupMockData() |
||||
|
||||
let result = subject.retrieveSelectedTimezone(0) |
||||
XCTAssert(result.timezone.abbreviation == "PDT") |
||||
} |
||||
|
||||
func testRetrieveSelectedTimezoneWithEmptySearchField() { |
||||
// Setup subject with an empty search field |
||||
setupSubject(searchText: CLEmptyString) |
||||
subject.searchTimezones("los") |
||||
XCTAssertFalse(subject.calculateChangesets()) |
||||
|
||||
let mockTimezone = TimezoneData() |
||||
mockTimezone.timezoneID = "PST" |
||||
mockTimezone.formattedAddress = "Los Angeles" |
||||
subject.setFilteredArrayValue([mockTimezone]) |
||||
|
||||
subject.searchTimezones("los") |
||||
XCTAssertFalse(subject.calculateChangesets()) |
||||
|
||||
let result = subject.retrieveSelectedTimezone(1) |
||||
XCTAssert(result.timezone.abbreviation == "GMT") |
||||
} |
||||
|
||||
func testRetrieveSelectedTimezoneWithEmptySearchFieldWithoutSearchResults() { |
||||
// Setup subject with an empty search field |
||||
setupSubject(searchText: "los") |
||||
setupMockData() |
||||
subject.cleanupFilterArray() |
||||
|
||||
let result = subject.retrieveResult(0) |
||||
XCTAssertNil(result) |
||||
} |
||||
} |
@ -1,667 +0,0 @@
|
||||
// |
||||
// TimePeriod.swift |
||||
// DateTools |
||||
// |
||||
// Created by Grayson Webster on 8/17/16. |
||||
// Copyright © 2016 Grayson Webster. All rights reserved. |
||||
// |
||||
|
||||
import Foundation |
||||
|
||||
/** |
||||
* In DateTools, time periods are represented by the TimePeriod protocol. |
||||
* Required variables and method impleementations are bound below. An inheritable |
||||
* implementation of the TimePeriodProtocol is available through the TimePeriodClass |
||||
* |
||||
* [Visit our github page](https://github.com/MatthewYork/DateTools#time-periods) for more information. |
||||
*/ |
||||
public protocol TimePeriodProtocol { |
||||
// MARK: - Variables |
||||
|
||||
/** |
||||
* The start date for a TimePeriod representing the starting boundary of the time period |
||||
*/ |
||||
var beginning: Date? { get set } |
||||
|
||||
/** |
||||
* The end date for a TimePeriod representing the ending boundary of the time period |
||||
*/ |
||||
var end: Date? { get set } |
||||
} |
||||
|
||||
public extension TimePeriodProtocol { |
||||
// MARK: - Information |
||||
|
||||
/** |
||||
* True if the `TimePeriod`'s duration is zero |
||||
*/ |
||||
var isMoment: Bool { |
||||
return beginning == end |
||||
} |
||||
|
||||
/** |
||||
* The duration of the `TimePeriod` in years. |
||||
* Returns the max int if beginning or end are nil. |
||||
*/ |
||||
var years: Int { |
||||
if beginning != nil, end != nil { |
||||
return beginning!.yearsEarlier(than: end!) |
||||
} |
||||
return Int.max |
||||
} |
||||
|
||||
/** |
||||
* The duration of the `TimePeriod` in weeks. |
||||
* Returns the max int if beginning or end are nil. |
||||
*/ |
||||
var weeks: Int { |
||||
if beginning != nil, end != nil { |
||||
return beginning!.weeksEarlier(than: end!) |
||||
} |
||||
return Int.max |
||||
} |
||||
|
||||
/** |
||||
* The duration of the `TimePeriod` in days. |
||||
* Returns the max int if beginning or end are nil. |
||||
*/ |
||||
var days: Int { |
||||
if beginning != nil, end != nil { |
||||
return beginning!.daysEarlier(than: end!) |
||||
} |
||||
return Int.max |
||||
} |
||||
|
||||
/** |
||||
* The duration of the `TimePeriod` in hours. |
||||
* Returns the max int if beginning or end are nil. |
||||
*/ |
||||
var hours: Int { |
||||
if beginning != nil, end != nil { |
||||
return beginning!.hoursEarlier(than: end!) |
||||
} |
||||
return Int.max |
||||
} |
||||
|
||||
/** |
||||
* The duration of the `TimePeriod` in minutes. |
||||
* Returns the max int if beginning or end are nil. |
||||
*/ |
||||
var minutes: Int { |
||||
if beginning != nil, end != nil { |
||||
return beginning!.minutesEarlier(than: end!) |
||||
} |
||||
return Int.max |
||||
} |
||||
|
||||
/** |
||||
* The duration of the `TimePeriod` in seconds. |
||||
* Returns the max int if beginning or end are nil. |
||||
*/ |
||||
var seconds: Int { |
||||
if beginning != nil, end != nil { |
||||
return beginning!.secondsEarlier(than: end!) |
||||
} |
||||
return Int.max |
||||
} |
||||
|
||||
/** |
||||
* The duration of the `TimePeriod` in a time chunk. |
||||
* Returns a time chunk with all zeroes if beginning or end are nil. |
||||
*/ |
||||
var chunk: TimeChunk { |
||||
if beginning != nil, end != nil { |
||||
return beginning!.chunkBetween(date: end!) |
||||
} |
||||
return TimeChunk(seconds: 0, minutes: 0, hours: 0, days: 0, weeks: 0, months: 0, years: 0) |
||||
} |
||||
|
||||
/** |
||||
* The length of time between the beginning and end dates of the |
||||
* `TimePeriod` as a `TimeInterval`. |
||||
*/ |
||||
var duration: TimeInterval { |
||||
if beginning != nil, end != nil { |
||||
return abs(beginning!.timeIntervalSince(end!)) |
||||
} |
||||
|
||||
return TimeInterval(Double.greatestFiniteMagnitude) |
||||
} |
||||
|
||||
// MARK: - Time Period Relationships |
||||
|
||||
/** |
||||
* 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) -> Relation { |
||||
// Make sure that all start and end points exist for comparison |
||||
if beginning != nil, end != nil, period.beginning != nil, period.end != nil { |
||||
// Make sure time periods are of positive durations |
||||
if beginning!.isEarlier(than: end!), period.beginning!.isEarlier(than: period.end!) { |
||||
// Make comparisons |
||||
if period.end!.isEarlier(than: beginning!) { |
||||
return .after |
||||
} else if period.end!.equals(beginning!) { |
||||
return .startTouching |
||||
} else if period.beginning!.isEarlier(than: beginning!), period.end!.isEarlier(than: end!) { |
||||
return .startInside |
||||
} else if period.beginning!.equals(beginning!), period.end!.isLater(than: end!) { |
||||
return .insideStartTouching |
||||
} else if period.beginning!.equals(beginning!), period.end!.isEarlier(than: end!) { |
||||
return .enclosingStartTouching |
||||
} else if period.beginning!.isLater(than: beginning!), period.end!.isEarlier(than: end!) { |
||||
return .enclosing |
||||
} else if period.beginning!.isLater(than: beginning!), period.end!.equals(end!) { |
||||
return .enclosingEndTouching |
||||
} else if period.beginning!.equals(beginning!), period.end!.equals(end!) { |
||||
return .exactMatch |
||||
} else if period.beginning!.isEarlier(than: beginning!), period.end!.isLater(than: end!) { |
||||
return .inside |
||||
} else if period.beginning!.isEarlier(than: beginning!), period.end!.equals(end!) { |
||||
return .insideEndTouching |
||||
} else if period.beginning!.isEarlier(than: end!), period.end!.isLater(than: end!) { |
||||
return .endInside |
||||
} else if period.beginning!.equals(end!), period.end!.isLater(than: end!) { |
||||
return .endTouching |
||||
} else if period.beginning!.isLater(than: end!) { |
||||
return .before |
||||
} |
||||
} |
||||
} |
||||
|
||||
return .none |
||||
} |
||||
|
||||
/** |
||||
* If `self.beginning` and `self.end` are equal to the beginning and end of the |
||||
* given `TimePeriod`. |
||||
* |
||||
* - parameter period: The time period to compare to self |
||||
* |
||||
* - returns: True if the periods are the same |
||||
*/ |
||||
func equals(_ period: TimePeriodProtocol) -> Bool { |
||||
return beginning == period.beginning && end == period.end |
||||
} |
||||
|
||||
/** |
||||
* If the given `TimePeriod`'s beginning is before `self.beginning` and |
||||
* if the given 'TimePeriod`'s end is after `self.end`. |
||||
* |
||||
* - parameter period: The time period to compare to self |
||||
* |
||||
* - returns: True if self is inside of the given `TimePeriod` |
||||
*/ |
||||
func isInside(of period: TimePeriodProtocol) -> Bool { |
||||
return period.beginning!.isEarlierThanOrEqual(to: beginning!) && period.end!.isLaterThanOrEqual(to: end!) |
||||
} |
||||
|
||||
/** |
||||
* If the given Date is after `self.beginning` and before `self.end`. |
||||
* |
||||
* - parameter period: The time period to compare to self |
||||
* - parameter 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: Date, interval: Interval) -> Bool { |
||||
if interval == .open { |
||||
return beginning!.isEarlier(than: date) && end!.isLater(than: date) |
||||
} else if interval == .closed { |
||||
return (beginning!.isEarlierThanOrEqual(to: date) && end!.isLaterThanOrEqual(to: date)) |
||||
} |
||||
|
||||
return false |
||||
} |
||||
|
||||
/** |
||||
* If the given `TimePeriod`'s beginning is after `self.beginning` and |
||||
* if the given 'TimePeriod`'s after is after `self.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 { |
||||
return beginning!.isEarlierThanOrEqual(to: period.beginning!) && end!.isLaterThanOrEqual(to: period.end!) |
||||
} |
||||
|
||||
/** |
||||
* 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 { |
||||
// Outside -> Inside |
||||
if period.beginning!.isEarlier(than: beginning!), period.end!.isLater(than: beginning!) { |
||||
return true |
||||
} |
||||
// Enclosing |
||||
else if period.beginning!.isLaterThanOrEqual(to: beginning!), period.end!.isEarlierThanOrEqual(to: end!) { |
||||
return true |
||||
} |
||||
// Inside -> Out |
||||
else if period.beginning!.isEarlier(than: end!), period.end!.isLater(than: end!) { |
||||
return true |
||||
} |
||||
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 { |
||||
return relation(to: period) != .after && relation(to: period) != .before |
||||
} |
||||
|
||||
/** |
||||
* If self and the given `TimePeriod` have no overlap or touching edges. |
||||
* |
||||
* - parameter period: The time period to compare to self |
||||
* |
||||
* - returns: True if there is a period of time between self and the given `TimePeriod` not contained by either period |
||||
*/ |
||||
func hasGap(between period: TimePeriodProtocol) -> Bool { |
||||
return isBefore(period: period) || isAfter(period: 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 { |
||||
if end!.isEarlier(than: period.beginning!) { |
||||
return abs(end!.timeIntervalSince(period.beginning!)) |
||||
} else if period.end!.isEarlier(than: beginning!) { |
||||
return abs(period.end!.timeIntervalSince(beginning!)) |
||||
} |
||||
return 0 |
||||
} |
||||
|
||||
/** |
||||
* The period of time between self and the given `TimePeriod` not contained by either |
||||
* as a `TimeChunk`. |
||||
* |
||||
* - 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) -> TimeChunk? { |
||||
if end != nil, period.beginning != nil { |
||||
return (end?.chunkBetween(date: period.beginning!))! |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
/** |
||||
* 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 |
||||
} |
||||
|
||||
/** |
||||
* 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 |
||||
} |
||||
|
||||
// MARK: - Shifts |
||||
|
||||
// MARK: In Place |
||||
|
||||
/** |
||||
* In place, shift the `TimePeriod` by a `TimeInterval` |
||||
* |
||||
* - parameter timeInterval: The time interval to shift the period by |
||||
*/ |
||||
mutating func shift(by timeInterval: TimeInterval) { |
||||
beginning?.addTimeInterval(timeInterval) |
||||
end?.addTimeInterval(timeInterval) |
||||
} |
||||
|
||||
/** |
||||
* In place, shift the `TimePeriod` by a `TimeChunk` |
||||
* |
||||
* - parameter chunk: The time chunk to shift the period by |
||||
*/ |
||||
mutating func shift(by chunk: TimeChunk) { |
||||
beginning = beginning?.add(chunk) |
||||
end = end?.add(chunk) |
||||
} |
||||
|
||||
// MARK: - Lengthen / Shorten |
||||
|
||||
// MARK: In Place |
||||
|
||||
/** |
||||
* In place, lengthen the `TimePeriod`, anchored at the beginning, end or center |
||||
* |
||||
* - parameter timeInterval: The time interval to lengthen the period by |
||||
* - parameter anchor: The anchor point from which to make the change |
||||
*/ |
||||
mutating func lengthen(by timeInterval: TimeInterval, at anchor: Anchor) { |
||||
switch anchor { |
||||
case .beginning: |
||||
end = end?.addingTimeInterval(timeInterval) |
||||
case .center: |
||||
beginning = beginning?.addingTimeInterval(-timeInterval / 2.0) |
||||
end = end?.addingTimeInterval(timeInterval / 2.0) |
||||
case .end: |
||||
beginning = beginning?.addingTimeInterval(-timeInterval) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* In place, lengthen the `TimePeriod`, anchored at the beginning or end |
||||
* |
||||
* - parameter chunk: The time chunk to lengthen the period by |
||||
* - parameter anchor: The anchor point from which to make the change |
||||
*/ |
||||
mutating func lengthen(by chunk: TimeChunk, at anchor: Anchor) { |
||||
switch anchor { |
||||
case .beginning: |
||||
end = end?.add(chunk) |
||||
case .center: |
||||
// Do not lengthen by TimeChunk at center |
||||
Swift.print("Mutation via chunk from center anchor is not supported.") |
||||
case .end: |
||||
beginning = beginning?.subtract(chunk) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* In place, shorten the `TimePeriod`, anchored at the beginning, end or center |
||||
* |
||||
* - parameter timeInterval: The time interval to shorten the period by |
||||
* - parameter anchor: The anchor point from which to make the change |
||||
*/ |
||||
mutating func shorten(by timeInterval: TimeInterval, at anchor: Anchor) { |
||||
switch anchor { |
||||
case .beginning: |
||||
end = end?.addingTimeInterval(-timeInterval) |
||||
case .center: |
||||
beginning = beginning?.addingTimeInterval(timeInterval / 2.0) |
||||
end = end?.addingTimeInterval(-timeInterval / 2.0) |
||||
case .end: |
||||
beginning = beginning?.addingTimeInterval(timeInterval) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* In place, shorten the `TimePeriod`, anchored at the beginning or end |
||||
* |
||||
* - parameter chunk: The time chunk to shorten the period by |
||||
* - parameter anchor: The anchor point from which to make the change |
||||
*/ |
||||
mutating func shorten(by chunk: TimeChunk, at anchor: Anchor) { |
||||
switch anchor { |
||||
case .beginning: |
||||
end = end?.subtract(chunk) |
||||
case .center: |
||||
// Do not shorten by TimeChunk at center |
||||
Swift.print("Mutation via chunk from center anchor is not supported.") |
||||
case .end: |
||||
beginning = beginning?.add(chunk) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* In DateTools, time periods are represented by the case TimePeriod class |
||||
* and come with a suite of initializaiton, manipulation, and comparison methods |
||||
* to make working with them a breeze. |
||||
* |
||||
* [Visit our github page](https://github.com/MatthewYork/DateTools#time-periods) for more information. |
||||
*/ |
||||
open class TimePeriod: TimePeriodProtocol { |
||||
// MARK: - Variables |
||||
|
||||
/** |
||||
* The start date for a TimePeriod representing the starting boundary of the time period |
||||
*/ |
||||
public var beginning: Date? |
||||
|
||||
/** |
||||
* The end date for a TimePeriod representing the ending boundary of the time period |
||||
*/ |
||||
public var end: Date? |
||||
|
||||
// MARK: - Initializers |
||||
|
||||
init() {} |
||||
|
||||
init(beginning: Date?, end: Date?) { |
||||
self.beginning = beginning |
||||
self.end = end |
||||
} |
||||
|
||||
init(beginning: Date, duration: TimeInterval) { |
||||
self.beginning = beginning |
||||
end = beginning + duration |
||||
} |
||||
|
||||
init(end: Date, duration: TimeInterval) { |
||||
self.end = end |
||||
beginning = end.addingTimeInterval(-duration) |
||||
} |
||||
|
||||
init(beginning: Date, chunk: TimeChunk) { |
||||
self.beginning = beginning |
||||
end = beginning + chunk |
||||
} |
||||
|
||||
init(end: Date, chunk: TimeChunk) { |
||||
self.end = end |
||||
beginning = end - chunk |
||||
} |
||||
|
||||
init(chunk: TimeChunk) { |
||||
beginning = Date() |
||||
end = beginning?.add(chunk) |
||||
} |
||||
|
||||
// MARK: - Shifted |
||||
|
||||
/** |
||||
* Shift the `TimePeriod` by a `TimeInterval` |
||||
* |
||||
* - parameter timeInterval: The time interval to shift the period by |
||||
* |
||||
* - returns: The new, shifted `TimePeriod` |
||||
*/ |
||||
func shifted(by timeInterval: TimeInterval) -> TimePeriod { |
||||
let timePeriod = TimePeriod() |
||||
timePeriod.beginning = beginning?.addingTimeInterval(timeInterval) |
||||
timePeriod.end = end?.addingTimeInterval(timeInterval) |
||||
return timePeriod |
||||
} |
||||
|
||||
/** |
||||
* Shift the `TimePeriod` by a `TimeChunk` |
||||
* |
||||
* - parameter chunk: The time chunk to shift the period by |
||||
* |
||||
* - returns: The new, shifted `TimePeriod` |
||||
*/ |
||||
func shifted(by chunk: TimeChunk) -> TimePeriod { |
||||
let timePeriod = TimePeriod() |
||||
timePeriod.beginning = beginning?.add(chunk) |
||||
timePeriod.end = end?.add(chunk) |
||||
return timePeriod |
||||
} |
||||
|
||||
// MARK: - Lengthen / Shorten |
||||
|
||||
// MARK: New |
||||
|
||||
/** |
||||
* Lengthen the `TimePeriod` by a `TimeInterval` |
||||
* |
||||
* - parameter timeInterval: The time interval to lengthen the period by |
||||
* - parameter anchor: The anchor point from which to make the change |
||||
* |
||||
* - returns: The new, lengthened `TimePeriod` |
||||
*/ |
||||
func lengthened(by timeInterval: TimeInterval, at anchor: Anchor) -> TimePeriod { |
||||
let timePeriod = TimePeriod() |
||||
switch anchor { |
||||
case .beginning: |
||||
timePeriod.beginning = beginning |
||||
timePeriod.end = end?.addingTimeInterval(timeInterval) |
||||
case .center: |
||||
timePeriod.beginning = beginning?.addingTimeInterval(-timeInterval) |
||||
timePeriod.end = end?.addingTimeInterval(timeInterval) |
||||
case .end: |
||||
timePeriod.beginning = beginning?.addingTimeInterval(-timeInterval) |
||||
timePeriod.end = end |
||||
} |
||||
|
||||
return timePeriod |
||||
} |
||||
|
||||
/** |
||||
* Lengthen the `TimePeriod` by a `TimeChunk` |
||||
* |
||||
* - parameter chunk: The time chunk to lengthen the period by |
||||
* - parameter anchor: The anchor point from which to make the change |
||||
* |
||||
* - returns: The new, lengthened `TimePeriod` |
||||
*/ |
||||
func lengthened(by chunk: TimeChunk, at anchor: Anchor) -> TimePeriod { |
||||
let timePeriod = TimePeriod() |
||||
switch anchor { |
||||
case .beginning: |
||||
timePeriod.beginning = beginning |
||||
timePeriod.end = end?.add(chunk) |
||||
case .center: |
||||
Swift.print("Mutation via chunk from center anchor is not supported.") |
||||
case .end: |
||||
timePeriod.beginning = beginning?.add(-chunk) |
||||
timePeriod.end = end |
||||
} |
||||
|
||||
return timePeriod |
||||
} |
||||
|
||||
/** |
||||
* Shorten the `TimePeriod` by a `TimeInterval` |
||||
* |
||||
* - parameter timeInterval: The time interval to shorten the period by |
||||
* - parameter anchor: The anchor point from which to make the change |
||||
* |
||||
* - returns: The new, shortened `TimePeriod` |
||||
*/ |
||||
func shortened(by timeInterval: TimeInterval, at anchor: Anchor) -> TimePeriod { |
||||
let timePeriod = TimePeriod() |
||||
switch anchor { |
||||
case .beginning: |
||||
timePeriod.beginning = beginning |
||||
timePeriod.end = end?.addingTimeInterval(-timeInterval) |
||||
case .center: |
||||
timePeriod.beginning = beginning?.addingTimeInterval(-timeInterval / 2) |
||||
timePeriod.end = end?.addingTimeInterval(timeInterval / 2) |
||||
case .end: |
||||
timePeriod.beginning = beginning?.addingTimeInterval(timeInterval) |
||||
timePeriod.end = end |
||||
} |
||||
|
||||
return timePeriod |
||||
} |
||||
|
||||
/** |
||||
* Shorten the `TimePeriod` by a `TimeChunk` |
||||
* |
||||
* - parameter chunk: The time chunk to shorten the period by |
||||
* - parameter anchor: The anchor point from which to make the change |
||||
* |
||||
* - returns: The new, shortened `TimePeriod` |
||||
*/ |
||||
func shortened(by chunk: TimeChunk, at anchor: Anchor) -> TimePeriod { |
||||
let timePeriod = TimePeriod() |
||||
switch anchor { |
||||
case .beginning: |
||||
timePeriod.beginning = beginning |
||||
timePeriod.end = end?.subtract(chunk) |
||||
case .center: |
||||
Swift.print("Mutation via chunk from center anchor is not supported.") |
||||
case .end: |
||||
timePeriod.beginning = beginning?.add(-chunk) |
||||
timePeriod.end = end |
||||
} |
||||
|
||||
return timePeriod |
||||
} |
||||
|
||||
// MARK: - Operator Overloads |
||||
|
||||
/** |
||||
* Operator overload for checking if two `TimePeriod`s are equal |
||||
*/ |
||||
static func == (leftAddend: TimePeriod, rightAddend: TimePeriod) -> Bool { |
||||
return leftAddend.equals(rightAddend) |
||||
} |
||||
|
||||
// Default anchor = beginning |
||||
/** |
||||
* Operator overload for lengthening a `TimePeriod` by a `TimeInterval` |
||||
*/ |
||||
static func + (leftAddend: TimePeriod, rightAddend: TimeInterval) -> TimePeriod { |
||||
return leftAddend.lengthened(by: rightAddend, at: .beginning) |
||||
} |
||||
|
||||
/** |
||||
* Operator overload for lengthening a `TimePeriod` by a `TimeChunk` |
||||
*/ |
||||
static func + (leftAddend: TimePeriod, rightAddend: TimeChunk) -> TimePeriod { |
||||
return leftAddend.lengthened(by: rightAddend, at: .beginning) |
||||
} |
||||
|
||||
// Default anchor = beginning |
||||
/** |
||||
* Operator overload for shortening a `TimePeriod` by a `TimeInterval` |
||||
*/ |
||||
static func - (minuend: TimePeriod, subtrahend: TimeInterval) -> TimePeriod { |
||||
return minuend.shortened(by: subtrahend, at: .beginning) |
||||
} |
||||
|
||||
/** |
||||
* Operator overload for shortening a `TimePeriod` by a `TimeChunk` |
||||
*/ |
||||
static func - (minuend: TimePeriod, subtrahend: TimeChunk) -> TimePeriod { |
||||
return minuend.shortened(by: subtrahend, at: .beginning) |
||||
} |
||||
|
||||
/** |
||||
* Operator overload for checking if a `TimePeriod` is equal to a `TimePeriodProtocol` |
||||
*/ |
||||
static func == (left: TimePeriod, right: TimePeriodProtocol) -> Bool { |
||||
return left.equals(right) |
||||
} |
||||
} |
@ -1,177 +0,0 @@
|
||||
// |
||||
// TimePeriodChain.swift |
||||
// DateTools |
||||
// |
||||
// Created by Grayson Webster on 8/17/16. |
||||
// Copyright © 2016 Grayson Webster. All rights reserved. |
||||
// |
||||
|
||||
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. |
||||
* |
||||
* [Visit our github page](https://github.com/MatthewYork/DateTools#time-period-chains) for more information. |
||||
*/ |
||||
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.isEmpty == false) ? periods.last!.end! : period.beginning |
||||
|
||||
let newPeriod = TimePeriod(beginning: beginning!, duration: period.duration) |
||||
periods.append(newPeriod) |
||||
|
||||
// Update updateExtremes |
||||
if periods.count == 1 { |
||||
_beginning = period.beginning |
||||
_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.isEmpty == false) ? periods.last!.end! : period.beginning |
||||
|
||||
let newPeriod = TimePeriod(beginning: beginning!, duration: period.duration) |
||||
periods.append(newPeriod) |
||||
|
||||
// Update updateExtremes |
||||
if periods.count == 1 { |
||||
_beginning = period.beginning |
||||
_end = period.end |
||||
} else { |
||||
_end = _end?.addingTimeInterval(period.duration) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Insert period into periods array at given index. |
||||
* |
||||
* - parameter newElement: The period to insert |
||||
* - parameter 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.beginning != nil, period.end != nil { |
||||
// Insert new period |
||||
periods.insert(period, at: index) |
||||
} else if period.beginning != nil, period.end != nil { |
||||
// Insert new period |
||||
periods.insert(period, at: index) |
||||
} else { |
||||
Swift.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(beginning: period.beginning, end: period.end) |
||||
periods[i].beginning = periods[i - 1].end |
||||
periods[i].end = periods[i].beginning!.addingTimeInterval(currentPeriod.duration) |
||||
} |
||||
} |
||||
|
||||
updateExtremes() |
||||
} |
||||
|
||||
/** |
||||
* Remove from period array at the given index. |
||||
* |
||||
* - parameter at: 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) |
||||
} |
||||
|
||||
_beginning = _beginning?.addingTimeInterval(duration) |
||||
_end = _end?.addingTimeInterval(duration) |
||||
} |
||||
|
||||
override public func map<T>(_ transform: (TimePeriodProtocol) throws -> T) rethrows -> [T] { |
||||
return try periods.map(transform) |
||||
} |
||||
|
||||
override public func filter(_ isIncluded: (TimePeriodProtocol) throws -> Bool) rethrows -> [TimePeriodProtocol] { |
||||
return try periods.filter(isIncluded) |
||||
} |
||||
|
||||
override internal 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() { |
||||
_beginning = periods.first?.beginning |
||||
_end = periods.last?.end |
||||
} |
||||
|
||||
// MARK: - Operator Overloads |
||||
|
||||
/** |
||||
* Operator overload for comparing `TimePeriodChain`s to each other |
||||
*/ |
||||
public static func == (left: TimePeriodChain, right: TimePeriodChain) -> Bool { |
||||
return left.equals(right) |
||||
} |
||||
} |
@ -1,267 +0,0 @@
|
||||
// |
||||
// 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!) |
||||
} |
||||
} |
||||
} |
@ -1,146 +0,0 @@
|
||||
// |
||||
// TimePeriodGroup.swift |
||||
// DateTools |
||||
// |
||||
// Created by Grayson Webster on 8/17/16. |
||||
// Copyright © 2016 Grayson Webster. All rights reserved. |
||||
// |
||||
|
||||
import Foundation |
||||
|
||||
/** |
||||
* Time period groups are the final abstraction of date and time in DateTools. Here, time |
||||
* periods are gathered and organized into something useful. There are two main types of time |
||||
* period groups, `TimePeriodCollection` and `TimePeriodChain`. |
||||
* |
||||
* [Visit our github page](https://github.com/MatthewYork/DateTools#time-period-groups) for more information. |
||||
*/ |
||||
open class TimePeriodGroup: Sequence { |
||||
// MARK: - Variables |
||||
|
||||
/** |
||||
* The array of periods that define the group. |
||||
*/ |
||||
internal var periods: [TimePeriodProtocol] = [] |
||||
|
||||
internal var _beginning: Date? |
||||
internal var _end: Date? |
||||
|
||||
/** |
||||
* The earliest beginning date of a `TimePeriod` in the group. |
||||
* Nil if any `TimePeriod` in group has a nil beginning date (indefinite). |
||||
* (Read Only) |
||||
*/ |
||||
public var beginning: Date? { |
||||
return _beginning |
||||
} |
||||
|
||||
/** |
||||
* The latest end date of a `TimePeriod` in the group. |
||||
* Nil if any `TimePeriod` in group has a nil end date (indefinite). |
||||
* (Read Only) |
||||
*/ |
||||
public var end: Date? { |
||||
return _end |
||||
} |
||||
|
||||
/** |
||||
* The number of periods in the periods array. |
||||
*/ |
||||
public var count: Int { |
||||
return periods.count |
||||
} |
||||
|
||||
/** |
||||
* The total amount of time between the earliest and latest dates stored in the |
||||
* periods array. Nil if any beginning or end date in any contained period is nil. |
||||
*/ |
||||
public var duration: TimeInterval? { |
||||
if beginning != nil, end != nil { |
||||
return end!.timeIntervalSince(beginning!) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// MARK: - Initializers |
||||
|
||||
public init() {} |
||||
|
||||
// MARK: - Comparisons |
||||
|
||||
/** |
||||
* If `self.periods` contains the exact elements as the given group's periods array. |
||||
* |
||||
* - parameter group: The group to compare to self |
||||
* |
||||
* - returns: True if the periods arrays are the same |
||||
*/ |
||||
public func equals(_ group: TimePeriodGroup) -> Bool { |
||||
return containSameElements(array1: periods, group.periods) |
||||
} |
||||
|
||||
// MARK: - Sequence Protocol |
||||
|
||||
public func makeIterator() -> IndexingIterator<[TimePeriodProtocol]> { |
||||
return periods.makeIterator() |
||||
} |
||||
|
||||
public func map<T>(_ transform: (TimePeriodProtocol) throws -> T) rethrows -> [T] { |
||||
return try periods.map(transform) |
||||
} |
||||
|
||||
public func filter(_ isIncluded: (TimePeriodProtocol) throws -> Bool) rethrows -> [TimePeriodProtocol] { |
||||
return try periods.filter(isIncluded) |
||||
} |
||||
|
||||
public func forEach(_ body: (TimePeriodProtocol) throws -> Void) rethrows { |
||||
return try periods.forEach(body) |
||||
} |
||||
|
||||
public func split(maxSplits: Int, omittingEmptySubsequences: Bool, whereSeparator isSeparator: (TimePeriodProtocol) throws -> Bool) rethrows -> [AnySequence<TimePeriodProtocol>] { |
||||
return try periods.split(maxSplits: maxSplits, omittingEmptySubsequences: omittingEmptySubsequences, whereSeparator: isSeparator).map(AnySequence.init) |
||||
} |
||||
|
||||
subscript(index: Int) -> TimePeriodProtocol { |
||||
return periods[index] |
||||
} |
||||
|
||||
internal func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, TimePeriodProtocol) throws -> Result) rethrows -> Result { |
||||
return try periods.reduce(initialResult, nextPartialResult) |
||||
} |
||||
|
||||
internal func containSameElements(array1: [TimePeriodProtocol], _ array2: [TimePeriodProtocol]) -> Bool { |
||||
guard array1.count == array2.count else { |
||||
return false // No need to sorting if they already have different counts |
||||
} |
||||
|
||||
let compArray1: [TimePeriodProtocol] = array1.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 compArray2: [TimePeriodProtocol] = array2.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! |
||||
} |
||||
} |
||||
for x in 0 ..< compArray1.count { |
||||
if !compArray1[x].equals(compArray2[x]) { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
} |
Loading…
Reference in new issue