add all api to sort by month

This commit is contained in:
Michael Simard
2020-06-14 21:46:44 -05:00
parent 6b9e9a4c35
commit 842abb0895
162 changed files with 34480 additions and 71 deletions

View File

@@ -0,0 +1,159 @@
//
// SwiftDate
// Parse, validate, manipulate, and display dates, time and timezones in Swift
//
// Created by Daniele Margutti
// - Web: https://www.danielemargutti.com
// - Twitter: https://twitter.com/danielemargutti
// - Mail: hello@danielemargutti.com
//
// Copyright © 2019 Daniele Margutti. Licensed under MIT License.
//
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.
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.count > 0) ? periods.last!.end! : period.start
let newPeriod = TimePeriod(start: beginning!, duration: period.duration)
periods.append(newPeriod)
//Update updateExtremes
if periods.count == 1 {
start = period.start
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.count > 0) ? periods.last!.end! : period.start
let newPeriod = TimePeriod(start: beginning!, duration: period.duration)
periods.append(newPeriod)
//Update updateExtremes
if periods.count == 1 {
start = period.start
end = period.end
} else {
end = end?.addingTimeInterval(period.duration)
}
}
}
/// Insert period into periods array at given index.
///
/// - Parameters:
/// - period: The period to insert
/// - 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.start != nil && period.end != nil {
//Insert new period
periods.insert(period, at: index)
} else if period.start != nil && period.end != nil {
//Insert new period
periods.insert(period, at: index)
} else {
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(start: period.start, end: period.end)
periods[i].start = periods[i - 1].end
periods[i].end = periods[i].start!.addingTimeInterval(currentPeriod.duration)
}
}
updateExtremes()
}
/// Remove from period array at the given index.
///
/// - Parameter index: 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)
}
start = start?.addingTimeInterval(duration)
end = end?.addingTimeInterval(duration)
}
public override func map<T>(_ transform: (TimePeriodProtocol) throws -> T) rethrows -> [T] {
return try periods.map(transform)
}
public override func filter(_ isIncluded: (TimePeriodProtocol) throws -> Bool) rethrows -> [TimePeriodProtocol] {
return try periods.filter(isIncluded)
}
internal override 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() {
start = periods.first?.start
end = periods.last?.end
}
}

View File

@@ -0,0 +1,241 @@
//
// SwiftDate
// Parse, validate, manipulate, and display dates, time and timezones in Swift
//
// Created by Daniele Margutti
// - Web: https://www.danielemargutti.com
// - Twitter: https://twitter.com/danielemargutti
// - Mail: hello@danielemargutti.com
//
// Copyright © 2019 Daniele Margutti. Licensed under MIT License.
//
import Foundation
/// Sort type
///
/// - ascending: sort in ascending order
/// - descending: sort in descending order
public enum SortMode {
case ascending
case descending
}
/// Sorting type
///
/// - start: sort by start date
/// - end: sort by end date
/// - duration: sort by duration
/// - custom: sort using custom function
public enum SortType {
case start(_: SortMode)
case end(_: SortMode)
case duration(_: SortMode)
case custom(_: ((TimePeriodProtocol, TimePeriodProtocol) -> Bool))
}
/// 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 `start` and `end` that are extrapolated from the time periods within.
/// Time period collections allow overlaps within their set of time periods.
open class TimePeriodCollection: TimePeriodGroup {
// MARK: - Collection Manipulation
/// Append a TimePeriodProtocol to the periods array and check if the Collection's start 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
/// start 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
/// start 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.
///
/// - Parameters:
/// - newElement: The period to insert
/// - 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
/// Sort elements in place using given method.
///
/// - Parameter type: sorting method
public func sort(by type: SortType) {
switch type {
case .duration(let mode): periods.sort(by: sortFuncDuration(mode))
case .start(let mode): periods.sort(by: sortFunc(byStart: true, type: mode))
case .end(let mode): periods.sort(by: sortFunc(byStart: false, type: mode))
case .custom(let f): periods.sort(by: f)
}
}
/// Generate a new `TimePeriodCollection` where items are sorted with specified method.
///
/// - Parameters:
/// - type: sorting method
/// - Returns: collection ordered by given function
public func sorted(by type: SortType) -> TimePeriodCollection {
var sortedList: [TimePeriodProtocol]!
switch type {
case .duration(let mode): sortedList = periods.sorted(by: sortFuncDuration(mode))
case .start(let mode): sortedList = periods.sorted(by: sortFunc(byStart: true, type: mode))
case .end(let mode): sortedList = periods.sorted(by: sortFunc(byStart: false, type: mode))
case .custom(let f): sortedList = periods.sorted(by: f)
}
return TimePeriodCollection(sortedList)
}
// MARK: - Collection Relationship
/// 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 periodsInside(period: TimePeriodProtocol) -> TimePeriodCollection {
return TimePeriodCollection(periods.filter({ $0.isInside(period) }))
}
// 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: DateInRegion) -> TimePeriodCollection {
return TimePeriodCollection(periods.filter({ $0.contains(date: date, interval: .closed) }))
}
/// 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 {
return TimePeriodCollection(periods.filter({ $0.intersects(with: period) }))
}
/// Returns an instance of DTTimePeriodCollection with all the time periods in the receiver that overlap a given time period.
/// Overlap with the given time period does NOT include other time periods that simply touch it.
/// (i.e. one's start date is equal to another's end date)
///
/// - Parameter period: The time period to check against the receiver's time periods.
/// - Returns: Collection of periods overlapped by the given period
public func periodsOverlappedBy(_ period: TimePeriodProtocol) -> TimePeriodCollection {
return TimePeriodCollection(periods.filter({ $0.overlaps(with: period) }))
}
// 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: - Helpers
private func sortFuncDuration(_ type: SortMode) -> ((TimePeriodProtocol, TimePeriodProtocol) -> Bool) {
switch type {
case .ascending: return { $0.duration < $1.duration }
case .descending: return { $0.duration > $1.duration }
}
}
private func sortFunc(byStart start: Bool = true, type: SortMode) -> ((TimePeriodProtocol, TimePeriodProtocol) -> Bool) {
return {
let date0 = (start ? $0.start : $0.end)
let date1 = (start ? $1.start : $1.end)
if date0 == nil && date1 == nil {
return false
} else if date0 == nil {
return true
} else if date1 == nil {
return false
} else {
return (type == .ascending ? date1! > date0! : date0! > date1!)
}
}
}
private func updateExtremes(period: TimePeriodProtocol) {
//Check incoming period against previous start and end date
guard count != 1 else {
start = period.start
end = period.end
return
}
start = nilOrEarlier(date1: start, date2: period.start)
end = nilOrLater(date1: end, date2: period.end)
}
private func updateExtremes() {
guard periods.count > 0 else {
start = nil
end = nil
return
}
start = periods.first!.start
end = periods.first!.end
for i in 1..<periods.count {
start = nilOrEarlier(date1: start, date2: periods[i].start)
end = nilOrEarlier(date1: end, date2: periods[i].end)
}
}
private func nilOrEarlier(date1: DateInRegion?, date2: DateInRegion?) -> DateInRegion? {
guard date1 != nil && date2 != nil else { return nil }
return date1!.earlierDate(date2!)
}
private func nilOrLater(date1: DateInRegion?, date2: DateInRegion?) -> DateInRegion? {
guard date1 != nil && date2 != nil else { return nil }
return date1!.laterDate(date2!)
}
}

View File

@@ -0,0 +1,123 @@
//
// SwiftDate
// Parse, validate, manipulate, and display dates, time and timezones in Swift
//
// Created by Daniele Margutti
// - Web: https://www.danielemargutti.com
// - Twitter: https://twitter.com/danielemargutti
// - Mail: hello@danielemargutti.com
//
// Copyright © 2019 Daniele Margutti. Licensed under MIT License.
//
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`.
open class TimePeriodGroup: Sequence, Equatable {
/// Array of periods that define the group.
internal var periods: [TimePeriodProtocol] = []
/// The earliest beginning date of a `TimePeriod` in the group.
/// `nil` if any `TimePeriod` in group has a nil beginning date (indefinite).
public internal(set) var start: DateInRegion?
/// The latest end date of a `TimePeriod` in the group.
/// `nil` if any `TimePeriod` in group has a nil end date (indefinite).
public internal(set) var end: DateInRegion?
/// 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? {
guard let start = start, let end = end else { return nil }
return end.timeIntervalSince(start)
}
/// The number of periods in the periods array.
public var count: Int {
return periods.count
}
// MARK: - Equatable
public static func == (lhs: TimePeriodGroup, rhs: TimePeriodGroup) -> Bool {
return TimePeriodGroup.hasSameElements(array1: lhs.periods, rhs.periods)
}
// MARK: - Initializers
public init(_ periods: [TimePeriodProtocol]? = nil) {
self.periods = (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 {
get {
return periods[index]
}
}
internal func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, TimePeriodProtocol) throws -> Result) rethrows -> Result {
return try periods.reduce(initialResult, nextPartialResult)
}
// MARK: - Internal Helper Functions
internal static func hasSameElements(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.start == nil && period2.start == nil {
return false
} else if period1.start == nil {
return true
} else if period2.start == nil {
return false
} else {
return period2.start! < period1.start!
}
}
let compArray2: [TimePeriodProtocol] = array2.sorted { (period1: TimePeriodProtocol, period2: TimePeriodProtocol) -> Bool in
if period1.start == nil && period2.start == nil {
return false
} else if period1.start == nil {
return true
} else if period2.start == nil {
return false
} else {
return period2.start! < period1.start!
}
}
for x in 0..<compArray1.count {
if !compArray1[x].equals(compArray2[x]) {
return false
}
}
return true
}
}