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,290 @@
//
// 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
// MARK: - Date Components Extensions
public extension Calendar.Component {
/// Return a description of the calendar component in seconds.
/// Note: Values for `era`,`weekday`,`weekdayOrdinal`, `yearForWeekOfYear`, `calendar`, `timezone` are `nil`.
/// For `weekOfYear` it return the same value of `weekOfMonth`.
var timeInterval: Double? {
switch self {
case .era: return nil
case .year: return (Calendar.Component.day.timeInterval! * 365.0)
case .month: return (Calendar.Component.minute.timeInterval! * 43800)
case .day: return 86400
case .hour: return 3600
case .minute: return 60
case .second: return 1
case .quarter: return (Calendar.Component.day.timeInterval! * 91.25)
case .weekOfMonth, .weekOfYear: return (Calendar.Component.day.timeInterval! * 7)
case .nanosecond: return 1e-9
default: return nil
}
}
/// Return the localized identifier of a calendar component
///
/// - parameter unit: unit
/// - parameter value: value
///
/// - returns: return the plural or singular form of the time unit used to compose a valid identifier for search a localized
/// string in resource bundle
internal func localizedKey(forValue value: Int) -> String {
let locKey = localizedKey
let absValue = abs(value)
switch absValue {
case 0: // zero difference for this unit
return "0\(locKey)"
case 1: // one unit of difference
return locKey
default: // more than 1 unit of difference
return "\(locKey)\(locKey)"
}
}
internal var localizedKey: String {
switch self {
case .year: return "y"
case .month: return "m"
case .weekOfYear: return "w"
case .day: return "d"
case .hour: return "h"
case .minute: return "M"
case .second: return "s"
default:
return ""
}
}
}
public extension DateComponents {
/// Shortcut for 'all calendar components'.
static var allComponentsSet: Set<Calendar.Component> {
return [.era, .year, .month, .day, .hour, .minute,
.second, .weekday, .weekdayOrdinal, .quarter,
.weekOfMonth, .weekOfYear, .yearForWeekOfYear,
.nanosecond, .calendar, .timeZone]
}
internal static let allComponents: [Calendar.Component] = [.nanosecond, .second, .minute, .hour,
.day, .month, .year, .yearForWeekOfYear,
.weekOfYear, .weekday, .quarter, .weekdayOrdinal,
.weekOfMonth]
/// This function return the absolute amount of seconds described by the components of the receiver.
/// Note: evaluated value maybe not strictly exact because it ignore the context (calendar/date) of
/// the date components. In details:
/// - The following keys are ignored: `era`,`weekday`,`weekdayOrdinal`,
/// `weekOfYear`, `yearForWeekOfYear`, `calendar`, `timezone
///
/// Some other values dependant from dates are fixed. This is a complete table:
/// - `year` is 365.0 `days`
/// - `month` is 30.4167 `days` (or 43800 minutes)
/// - `quarter` is 91.25 `days`
/// - `weekOfMonth` is 7 `days`
/// - `day` is 86400 `seconds`
/// - `hour` is 3600 `seconds`
/// - `minute` is 60 `seconds`
/// - `nanosecond` is 1e-9 `seconds`
var timeInterval: TimeInterval {
var totalAmount: TimeInterval = 0
DateComponents.allComponents.forEach {
if let multipler = $0.timeInterval, let value = value(for: $0), value != Int(NSDateComponentUndefined) {
totalAmount += (TimeInterval(value) * multipler)
}
}
return totalAmount
}
/// Create a new `DateComponents` instance with builder pattern.
///
/// - Parameter builder: callback for builder
/// - Returns: new instance
static func create(_ builder: ((inout DateComponents) -> Void)) -> DateComponents {
var components = DateComponents()
builder(&components)
return components
}
/// Return the current date plus the receive's interval
/// The default calendar used is the `SwiftDate.defaultRegion`'s calendar.
var fromNow: Date {
return SwiftDate.defaultRegion.calendar.date(byAdding: (self as DateComponents) as DateComponents, to: Date() as Date)!
}
/// Returns the current date minus the receiver's interval
/// The default calendar used is the `SwiftDate.defaultRegion`'s calendar.
var ago: Date {
return SwiftDate.defaultRegion.calendar.date(byAdding: -self as DateComponents, to: Date())!
}
/// - returns: the date that will occur once the receiver's components pass after the provide date.
func from(_ date: DateRepresentable) -> Date? {
return date.calendar.date(byAdding: self, to: date.date)
}
/// Return `true` if all interval components are zeroes
var isZero: Bool {
for component in DateComponents.allComponents {
if let value = value(for: component), value != 0 {
return false
}
}
return true
}
/// Transform a `DateComponents` instance to a dictionary where key is the `Calendar.Component` and value is the
/// value associated.
///
/// - returns: a new `[Calendar.Component : Int]` dict representing source `DateComponents` instance
internal func toDict() -> [Calendar.Component: Int] {
var list: [Calendar.Component: Int] = [:]
DateComponents.allComponents.forEach { component in
let value = self.value(for: component)
if value != nil && value != Int(NSDateComponentUndefined) {
list[component] = value!
}
}
return list
}
/// Alter date components specified into passed dictionary.
///
/// - Parameter components: components dictionary with their values.
internal mutating func alterComponents(_ components: [Calendar.Component: Int?]) {
components.forEach {
if let v = $0.value {
setValue(v, for: $0.key)
}
}
}
/// Adds two NSDateComponents and returns their combined individual components.
static func + (lhs: DateComponents, rhs: DateComponents) -> DateComponents {
return combine(lhs, rhs: rhs, transform: +)
}
/// Subtracts two NSDateComponents and returns the relative difference between them.
static func - (lhs: DateComponents, rhs: DateComponents) -> DateComponents {
return lhs + (-rhs)
}
/// Applies the `transform` to the two `T` provided, defaulting either of them if it's
/// `nil`
internal static func bimap<T>(_ a: T?, _ b: T?, default: T, _ transform: (T, T) -> T) -> T? {
if a == nil && b == nil { return nil }
return transform(a ?? `default`, b ?? `default`)
}
/// - returns: a new `NSDateComponents` that represents the negative of all values within the
/// components that are not `NSDateComponentUndefined`.
static prefix func - (rhs: DateComponents) -> DateComponents {
var components = DateComponents()
components.era = rhs.era.map(-)
components.year = rhs.year.map(-)
components.month = rhs.month.map(-)
components.day = rhs.day.map(-)
components.hour = rhs.hour.map(-)
components.minute = rhs.minute.map(-)
components.second = rhs.second.map(-)
components.nanosecond = rhs.nanosecond.map(-)
components.weekday = rhs.weekday.map(-)
components.weekdayOrdinal = rhs.weekdayOrdinal.map(-)
components.quarter = rhs.quarter.map(-)
components.weekOfMonth = rhs.weekOfMonth.map(-)
components.weekOfYear = rhs.weekOfYear.map(-)
components.yearForWeekOfYear = rhs.yearForWeekOfYear.map(-)
return components
}
/// Combines two date components using the provided `transform` on all
/// values within the components that are not `NSDateComponentUndefined`.
private static func combine(_ lhs: DateComponents, rhs: DateComponents, transform: (Int, Int) -> Int) -> DateComponents {
var components = DateComponents()
components.era = bimap(lhs.era, rhs.era, default: 0, transform)
components.year = bimap(lhs.year, rhs.year, default: 0, transform)
components.month = bimap(lhs.month, rhs.month, default: 0, transform)
components.day = bimap(lhs.day, rhs.day, default: 0, transform)
components.hour = bimap(lhs.hour, rhs.hour, default: 0, transform)
components.minute = bimap(lhs.minute, rhs.minute, default: 0, transform)
components.second = bimap(lhs.second, rhs.second, default: 0, transform)
components.nanosecond = bimap(lhs.nanosecond, rhs.nanosecond, default: 0, transform)
components.weekday = bimap(lhs.weekday, rhs.weekday, default: 0, transform)
components.weekdayOrdinal = bimap(lhs.weekdayOrdinal, rhs.weekdayOrdinal, default: 0, transform)
components.quarter = bimap(lhs.quarter, rhs.quarter, default: 0, transform)
components.weekOfMonth = bimap(lhs.weekOfMonth, rhs.weekOfMonth, default: 0, transform)
components.weekOfYear = bimap(lhs.weekOfYear, rhs.weekOfYear, default: 0, transform)
components.yearForWeekOfYear = bimap(lhs.yearForWeekOfYear, rhs.yearForWeekOfYear, default: 0, transform)
return components
}
/// Subscription support for `DateComponents` instances.
/// ie. `cmps[.day] = 5`
///
/// Note: This does not take into account any built-in errors, `Int.max` returned instead of `nil`.
///
/// - Parameter component: component to get
subscript(component: Calendar.Component) -> Int? {
switch component {
case .era: return era
case .year: return year
case .month: return month
case .day: return day
case .hour: return hour
case .minute: return minute
case .second: return second
case .weekday: return weekday
case .weekdayOrdinal: return weekdayOrdinal
case .quarter: return quarter
case .weekOfMonth: return weekOfMonth
case .weekOfYear: return weekOfYear
case .yearForWeekOfYear: return yearForWeekOfYear
case .nanosecond: return nanosecond
default: return nil // `calendar` and `timezone` are ignored in this context
}
}
/// Express a `DateComponents` instance in another time unit you choose.
///
/// - parameter component: time component
/// - parameter calendar: context calendar to use
///
/// - returns: the value of interval expressed in selected `Calendar.Component`
func `in`(_ component: Calendar.Component, of calendar: CalendarConvertible? = nil) -> Int? {
let cal = (calendar?.toCalendar() ?? SwiftDate.defaultRegion.calendar)
let dateFrom = Date()
let dateTo = (dateFrom + self)
let components: Set<Calendar.Component> = [component]
let value = cal.dateComponents(components, from: dateFrom, to: dateTo).value(for: component)
return value
}
/// Express a `DateComponents` instance in a set of time units you choose.
///
/// - Parameters:
/// - component: time component
/// - calendar: context calendar to use
/// - Returns: a dictionary of extract values.
func `in`(_ components: Set<Calendar.Component>, of calendar: CalendarConvertible? = nil) -> [Calendar.Component: Int] {
let cal = (calendar?.toCalendar() ?? SwiftDate.defaultRegion.calendar)
let dateFrom = Date()
let dateTo = (dateFrom + self)
let extractedCmps = cal.dateComponents(components, from: dateFrom, to: dateTo)
return extractedCmps.toDict()
}
}

View File

@@ -0,0 +1,78 @@
//
// 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
// MARK: Int Extension
/// This allows us to transform a literal number in a `DateComponents` and use it in math operations
/// For example `5.days` will create a new `DateComponents` where `.day = 5`.
public extension Int {
/// Internal transformation function
///
/// - parameter type: component to use
///
/// - returns: return self value in form of `DateComponents` where given `Calendar.Component` has `self` as value
internal func toDateComponents(type: Calendar.Component) -> DateComponents {
var dateComponents = DateComponents()
dateComponents.setValue(self, for: type)
return dateComponents
}
/// Create a `DateComponents` with `self` value set as nanoseconds
var nanoseconds: DateComponents {
return toDateComponents(type: .nanosecond)
}
/// Create a `DateComponents` with `self` value set as seconds
var seconds: DateComponents {
return toDateComponents(type: .second)
}
/// Create a `DateComponents` with `self` value set as minutes
var minutes: DateComponents {
return toDateComponents(type: .minute)
}
/// Create a `DateComponents` with `self` value set as hours
var hours: DateComponents {
return toDateComponents(type: .hour)
}
/// Create a `DateComponents` with `self` value set as days
var days: DateComponents {
return toDateComponents(type: .day)
}
/// Create a `DateComponents` with `self` value set as weeks
var weeks: DateComponents {
return toDateComponents(type: .weekOfYear)
}
/// Create a `DateComponents` with `self` value set as months
var months: DateComponents {
return toDateComponents(type: .month)
}
/// Create a `DateComponents` with `self` value set as years
var years: DateComponents {
return toDateComponents(type: .year)
}
/// Create a `DateComponents` with `self` value set as quarters
var quarters: DateComponents {
return toDateComponents(type: .quarter)
}
}

View File

@@ -0,0 +1,120 @@
//
// 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
// MARK: - DataParsable Protocol
public protocol DateParsable {
/// Convert a string to a `DateInRegion` instance by parsing it with given parser
/// or using one of the built-in parser (if you know the format of the date you
/// should consider explicitly pass it to avoid unecessary computations).
///
/// - Parameters:
/// - format: format of the date, `nil` to leave the library to found the best
/// one via `SwiftDate.autoFormats`
/// - region: region in which the date should be expressed in.
/// Region's locale is used to format the date when using long readable unit names (like MMM
/// for month).
/// - Returns: date in region representation, `nil` if parse fails
func toDate(_ format: String?, region: Region) -> DateInRegion?
/// Convert a string to a `DateInRegion` instance by parsing it with the ordered
/// list of provided formats.
/// If `formats` array is not provided it uses the `SwiftDate.autoFormats` array instead.
/// Note: if you knwo the format of the date you should consider explicitly pass it to avoid
/// unecessary computations.
///
/// - Parameters:
/// - format: ordered formats to parse date (if you don't have a list of formats you can pass `SwiftDate.autoFormats`)
/// - region: region in which the date should be expressed in.
/// Region's locale is used to format the date when using long readable unit names (like MMM
/// for month).
/// - Returns: date in region representation, `nil` if parse fails
func toDate(_ formats: [String], region: Region) -> DateInRegion?
/// Convert a string to a valid `DateInRegion` using passed style.
///
/// - Parameters:
/// - style: parsing style.
/// - region: region in which the date should be expressed in
/// - Returns: date in region representation, `nil` if parse fails
func toDate(style: StringToDateStyles, region: Region) -> DateInRegion?
/// Convert to date from a valid ISO8601 string
///
/// - Parameters:
/// - options: options of the parser
/// - region: region in which the date should be expressed in (timzone is ignored and evaluated automatically)
/// - Returns: date in region representation, `nil` if parse fails
func toISODate(_ options: ISOParser.Options?, region: Region?) -> DateInRegion?
/// Convert to date from a valid DOTNET string
///
/// - region: region in which the date should be expressed in (timzone is ignored and evaluated automatically)
/// - Returns: date in region representation, `nil` if parse fails
func toDotNETDate(region: Region) -> DateInRegion?
/// Convert to a date from a valid RSS/ALT RSS string
///
/// - Parameters:
/// - alt: `true` if string represent an ALT RSS formatted date, `false` if a standard RSS formatted date.
/// - region: region in which the date should be expressed in (timzone is ignored and evaluated automatically)
/// - Returns: date in region representation, `nil` if parse fails
func toRSSDate(alt: Bool, region: Region) -> DateInRegion?
/// Convert to a date from a valid SQL format string.
///
/// - Parameters:
/// - region: region in which the date should be expressed in (timzone is ignored and evaluated automatically)
/// - Returns: date in region representation, `nil` if parse fails
func toSQLDate(region: Region) -> DateInRegion?
}
// MARK: - DataParsable Implementation for Strings
extension String: DateParsable {
public func toDate(_ format: String? = nil, region: Region = SwiftDate.defaultRegion) -> DateInRegion? {
return DateInRegion(self, format: format, region: region)
}
public func toDate(_ formats: [String], region: Region) -> DateInRegion? {
return DateInRegion(self, formats: formats, region: region)
}
public func toDate(style: StringToDateStyles, region: Region = SwiftDate.defaultRegion) -> DateInRegion? {
return style.toDate(self, region: region)
}
public func toISODate(_ options: ISOParser.Options? = nil, region: Region? = nil) -> DateInRegion? {
return ISOParser.parse(self, region: region, options: options)
}
public func toDotNETDate(region: Region = Region.ISO) -> DateInRegion? {
return DOTNETParser.parse(self, region: region, options: nil)
}
public func toRSSDate(alt: Bool, region: Region = Region.ISO) -> DateInRegion? {
switch alt {
case true: return StringToDateStyles.altRSS.toDate(self, region: region)
case false: return StringToDateStyles.rss.toDate(self, region: region)
}
}
public func toSQLDate(region: Region = Region.ISO) -> DateInRegion? {
return StringToDateStyles.sql.toDate(self, region: region)
}
}

View File

@@ -0,0 +1,168 @@
//
// 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
public extension TimeInterval {
struct ComponentsFormatterOptions {
/// Fractional units may be used when a value cannot be exactly represented using the available units.
/// For example, if minutes are not allowed, the value 1h 30m could be formatted as 1.5h.
public var allowsFractionalUnits: Bool?
/// Specify the units that can be used in the output.
public var allowedUnits: NSCalendar.Unit?
/// A Boolean value indicating whether to collapse the largest unit into smaller units when a certain threshold is met.
public var collapsesLargestUnit: Bool?
/// The maximum number of time units to include in the output string.
/// If 0 does not cause the elimination of any units.
public var maximumUnitCount: Int?
/// The formatting style for units whose value is 0.
public var zeroFormattingBehavior: DateComponentsFormatter.ZeroFormattingBehavior?
/// The preferred style for units.
public var unitsStyle: DateComponentsFormatter.UnitsStyle?
/// Locale of the formatter
public var locale: LocaleConvertible? {
set { calendar.locale = newValue?.toLocale() }
get { return calendar.locale }
}
/// Calendar
public var calendar = Calendar.autoupdatingCurrent
public func apply(toFormatter formatter: DateComponentsFormatter) {
formatter.calendar = calendar
if let allowsFractionalUnits = self.allowsFractionalUnits {
formatter.allowsFractionalUnits = allowsFractionalUnits
}
if let allowedUnits = self.allowedUnits {
formatter.allowedUnits = allowedUnits
}
if let collapsesLargestUnit = self.collapsesLargestUnit {
formatter.collapsesLargestUnit = collapsesLargestUnit
}
if let maximumUnitCount = self.maximumUnitCount {
formatter.maximumUnitCount = maximumUnitCount
}
if let zeroFormattingBehavior = self.zeroFormattingBehavior {
formatter.zeroFormattingBehavior = zeroFormattingBehavior
}
if let unitsStyle = self.unitsStyle {
formatter.unitsStyle = unitsStyle
}
}
public init() {}
}
/// Return the local thread shared formatter for date components
private static func sharedFormatter() -> DateComponentsFormatter {
let name = "SwiftDate_\(NSStringFromClass(DateComponentsFormatter.self))"
return threadSharedObject(key: name, create: {
let formatter = DateComponentsFormatter()
formatter.includesApproximationPhrase = false
formatter.includesTimeRemainingPhrase = false
return formatter
})
}
//@available(*, deprecated: 5.0.13, obsoleted: 5.1, message: "Use toIntervalString function instead")
func toString(options callback: ((inout ComponentsFormatterOptions) -> Void)? = nil) -> String {
return self.toIntervalString(options: callback)
}
/// Format a time interval in a string with desidered components with passed style.
///
/// - Parameters:
/// - units: units to include in string.
/// - style: style of the units, by default is `.abbreviated`
/// - Returns: string representation
func toIntervalString(options callback: ((inout ComponentsFormatterOptions) -> Void)? = nil) -> String {
let formatter = DateComponentsFormatter()
var options = ComponentsFormatterOptions()
callback?(&options)
options.apply(toFormatter: formatter)
let formattedValue = formatter.string(from: self)!
if options.zeroFormattingBehavior?.contains(.pad) ?? false {
// for some strange reason padding is not added at the very beginning positional item.
// we'll add it manually if necessaru
if let index = formattedValue.firstIndex(of: ":"), index.utf16Offset(in: formattedValue) < 2 {
return "0\(formattedValue)"
}
}
return formattedValue
}
/// Format a time interval in a string with desidered components with passed style.
///
/// - Parameter options: options for formatting.
/// - Returns: string representation
func toString(options: ComponentsFormatterOptions) -> String {
let formatter = TimeInterval.sharedFormatter()
options.apply(toFormatter: formatter)
return (formatter.string(from: self) ?? "")
}
/// Return a string representation of the time interval in form of clock countdown (ie. 57:00:00)
///
/// - Parameter zero: behaviour with zero.
/// - Returns: string representation
func toClock(zero: DateComponentsFormatter.ZeroFormattingBehavior = [.pad, .dropLeading]) -> String {
return toIntervalString(options: {
$0.collapsesLargestUnit = true
$0.maximumUnitCount = 0
$0.unitsStyle = .positional
$0.locale = Locales.englishUnitedStatesComputer
$0.zeroFormattingBehavior = zero
})
}
/// Extract requeste time units components from given interval.
/// Reference date's calendar is used to make the extraction.
///
/// NOTE:
/// Extraction is calendar/date based; if you specify a `refDate` calculation is made
/// between the `refDate` and `refDate + interval`.
/// If `refDate` is `nil` evaluation is made from `now()` and `now() + interval` in the context
/// of the `SwiftDate.defaultRegion` set.
///
/// - Parameters:
/// - units: units to extract
/// - from: starting reference date, `nil` means `now()` in the context of the default region set.
/// - Returns: dictionary with extracted components
func toUnits(_ units: Set<Calendar.Component>, to refDate: DateInRegion? = nil) -> [Calendar.Component: Int] {
let dateTo = (refDate ?? DateInRegion())
let dateFrom = dateTo.addingTimeInterval(-self)
let components = dateFrom.calendar.dateComponents(units, from: dateFrom.date, to: dateTo.date)
return components.toDict()
}
/// Express a time interval (expressed in seconds) in another time unit you choose.
/// Reference date's calendar is used to make the extraction.
///
/// - parameter component: time unit in which you want to express the calendar component
/// - parameter from: starting reference date, `nil` means `now()` in the context of the default region set.
///
/// - returns: the value of interval expressed in selected `Calendar.Component`
func toUnit(_ component: Calendar.Component, to refDate: DateInRegion? = nil) -> Int? {
return toUnits([component], to: refDate)[component]
}
}