Files
cod-backend/Sources/App/Libraries/SwiftDate/Foundation+Extras/TimeInterval+Formatter.swift
2020-06-14 21:46:44 -05:00

169 lines
6.6 KiB
Swift

//
// 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]
}
}