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