Files
cod-backend/Sources/App/Libraries/SwiftDate/DateRepresentable.swift
2020-06-14 21:46:44 -05:00

571 lines
18 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 protocol DateRepresentable {
// MARK: - Date Components
var year: Int { get }
/// Represented month
var month: Int { get }
/// Represented month name with given style.
///
/// - Parameter style: style in which the name must be formatted.
/// - Returns: name of the month
func monthName(_ style: SymbolFormatStyle) -> String
/// Number of the days in the receiver.
var monthDays: Int { get }
/// Day unit of the receiver.
var day: Int { get }
/// Day of year unit of the receiver
var dayOfYear: Int { get }
/// The number of day in ordinal style format for the receiver in current locale.
/// For example, in the en_US locale, the number 3 is represented as 3rd;
/// in the fr_FR locale, the number 3 is represented as 3e.
@available(iOS 9.0, macOS 10.11, *)
var ordinalDay: String { get }
/// Hour unit of the receiver.
var hour: Int { get }
/// Nearest rounded hour from the date
var nearestHour: Int { get }
/// Minute unit of the receiver.
var minute: Int { get }
/// Second unit of the receiver.
var second: Int { get }
/// Nanosecond unit of the receiver.
var nanosecond: Int { get }
/// Milliseconds in day of the receiver
/// This field behaves exactly like a composite of all time-related fields, not including the zone fields.
/// As such, it also reflects discontinuities of those fields on DST transition days.
/// On a day of DST onset, it will jump forward. On a day of DST cessation, it will jump backward.
/// This reflects the fact that is must be combined with the offset field to obtain a unique local time value.
var msInDay: Int { get }
/// Weekday unit of the receiver.
/// The weekday units are the numbers 1-N (where for the Gregorian calendar N=7 and 1 is Sunday).
var weekday: Int { get }
/// Name of the weekday expressed in given format style.
///
/// - Parameter style: style to express the value.
/// - Parameter locale: locale to use; ignore it to use default's region locale.
/// - Returns: weekday name
func weekdayName(_ style: SymbolFormatStyle, locale: LocaleConvertible?) -> String
/// Week of a year of the receiver.
var weekOfYear: Int { get }
/// Week of a month of the receiver.
var weekOfMonth: Int { get }
/// Ordinal position within the month unit of the corresponding weekday unit.
/// For example, in the Gregorian calendar a weekday ordinal unit of 2 for a
/// weekday unit 3 indicates "the second Tuesday in the month".
var weekdayOrdinal: Int { get }
/// Return the first day number of the week where the receiver date is located.
var firstDayOfWeek: Int { get }
/// Return the last day number of the week where the receiver date is located.
var lastDayOfWeek: Int { get }
/// Relative year for a week within a year calendar unit.
var yearForWeekOfYear: Int { get }
/// Quarter value of the receiver.
var quarter: Int { get }
/// Quarter name expressed in given format style.
///
/// - Parameter style: style to express the value.
/// - Parameter locale: locale to use; ignore it to use default's region locale.
/// - Returns: quarter name
func quarterName(_ style: SymbolFormatStyle, locale: LocaleConvertible?) -> String
/// Era value of the receiver.
var era: Int { get }
/// Name of the era expressed in given format style.
///
/// - Parameter style: style to express the value.
/// - Parameter locale: locale to use; ignore it to use default's region locale.
/// - Returns: era
func eraName(_ style: SymbolFormatStyle, locale: LocaleConvertible?) -> String
/// The current daylight saving time offset of the represented date.
var DSTOffset: TimeInterval { get }
// MARK: - Common Properties
/// Absolute representation of the date
var date: Date { get }
/// Associated region
var region: Region { get }
/// Associated calendar
var calendar: Calendar { get }
/// Extract the date components from the date
var dateComponents: DateComponents { get }
/// Returns whether the given date is in today as boolean.
var isToday: Bool { get }
/// Returns whether the given date is in yesterday.
var isYesterday: Bool { get }
/// Returns whether the given date is in tomorrow.
var isTomorrow: Bool { get }
/// Returns whether the given date is in the weekend.
var isInWeekend: Bool { get }
/// Return true if given date represent a passed date
var isInPast: Bool { get }
/// Return true if given date represent a future date
var isInFuture: Bool { get }
/// Use this object to format the date object.
/// By default this object return the `customFormatter` instance (if set) or the
/// local thread shared formatter (via `sharedFormatter()` func; this is the most typical scenario).
///
/// - Parameters:
/// - format: format string to set.
/// - configuration: optional callback used to configure the object inline.
/// - Returns: formatter instance
func formatter(format: String?, configuration: ((DateFormatter) -> Void)?) -> DateFormatter
/// User this object to get an DateFormatter already configured to format the data object with the associated region.
/// By default this object return the `customFormatter` instance (if set) configured for region or the
/// local thread shared formatter even configured for region (via `sharedFormatter()` func; this is the most typical scenario).
///
/// - format: format string to set.
/// - configuration: optional callback used to configure the object inline.
/// - Returns: formatter instance
func formatterForRegion(format: String?, configuration: ((inout DateFormatter) -> Void)?) -> DateFormatter
/// Set a custom formatter for this object.
/// Typically you should not need to set a value for this property.
/// With a `nil` value SwiftDate will uses the threa shared formatter returned by `sharedFormatter()` function.
/// In case you need to a custom formatter instance you can override the default behaviour by setting a value here.
var customFormatter: DateFormatter? { get set }
/// Return a formatter instance created as singleton into the current caller's thread.
/// This object is used for formatting when no `dateFormatter` is set for the object
/// (this is the common scenario where you want to avoid multiple formatter instances to
/// parse dates; instances of DateFormatter are very expensive to create and you should
/// use a single instance in each thread to perform this kind of tasks).
///
/// - Returns: formatter instance
var sharedFormatter: DateFormatter { get }
// MARK: - Init
/// Initialize a new date by parsing a string.
///
/// - Parameters:
/// - string: string with the date.
/// - format: format used to parse date. Pass `nil` to use built-in formats
/// (if you know you should pass it to optimize the parsing process)
/// - region: region in which the date in `string` is expressed.
init?(_ string: String, format: String?, region: Region)
/// Initialize a new date from a number of seconds since the Unix Epoch.
///
/// - Parameters:
/// - interval: seconds since the Unix Epoch timestamp.
/// - region: region in which the date must be expressed.
init(seconds interval: TimeInterval, region: Region)
/// Initialize a new date corresponding to the number of milliseconds since the Unix Epoch.
///
/// - Parameters:
/// - interval: seconds since the Unix Epoch timestamp.
/// - region: region in which the date must be expressed.
init(milliseconds interval: Int, region: Region)
/// Initialize a new date with the opportunity to configure single date components via builder pattern.
/// Date is therfore expressed in passed region (`DateComponents`'s `timezone`,`calendar` and `locale` are ignored
/// and overwritten by the region if not `nil`).
///
/// - Parameters:
/// - configuration: configuration callback
/// - region: region in which the date is expressed. Ignore to use `SwiftDate.defaultRegion`,
/// `nil` to use `DateComponents` data.
init?(components configuration: ((inout DateComponents) -> Void), region: Region?)
/// Initialize a new date with time components passed.
///
/// - Parameters:
/// - components: date components
/// - region: region in which the date is expressed. Ignore to use `SwiftDate.defaultRegion`,
/// `nil` to use `DateComponents` data.
init?(components: DateComponents, region: Region?)
/// Initialize a new date with given components.
init(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int, nanosecond: Int, region: Region)
// MARK: - Conversion
/// Convert a date to another region.
///
/// - Parameter region: destination region in which the date must be represented.
/// - Returns: converted date
func convertTo(region: Region) -> DateInRegion
// MARK: - To String Formatting
/// Convert date to a string using passed pre-defined style.
///
/// - Parameter style: formatter style, `nil` to use `standard` style
/// - Returns: string representation of the date
func toString(_ style: DateToStringStyles?) -> String
/// Convert date to a string using custom date format.
///
/// - Parameters:
/// - format: format of the string representation
/// - locale: locale to fix a custom locale, `nil` to use associated region's locale
/// - Returns: string representation of the date
func toFormat(_ format: String, locale: LocaleConvertible?) -> String
/// Convert a date to a string representation relative to another reference date (or current
/// if not passed).
///
/// - Parameters:
/// - since: reference date, if `nil` current is used.
/// - style: style to use to format relative date.
/// - locale: force locale print, `nil` to use the date own region's locale
/// - Returns: string representation of the date.
func toRelative(since: DateInRegion?, style: RelativeFormatter.Style?, locale: LocaleConvertible?) -> String
/// Return ISO8601 representation of the date
///
/// - Parameter options: optional options, if nil extended iso format is used
func toISO(_ options: ISOFormatter.Options?) -> String
/// Return DOTNET compatible representation of the date.
///
/// - Returns: string representation of the date
func toDotNET() -> String
/// Return SQL compatible representation of the date.
///
/// - Returns: string represenation of the date
func toSQL() -> String
/// Return RSS compatible representation of the date
///
/// - Parameter alt: `true` to return altRSS version, `false` to return the standard RSS representation
/// - Returns: string representation of the date
func toRSS(alt: Bool) -> String
// MARK: - Extract Components
/// Extract time components for elapsed interval between the receiver date
/// and a reference date.
///
/// - Parameters:
/// - units: units to extract.
/// - refDate: reference date
/// - Returns: extracted time units
func toUnits(_ units: Set<Calendar.Component>, to refDate: DateRepresentable) -> [Calendar.Component: Int]
/// Extract time unit component from given date.
///
/// - Parameters:
/// - unit: time component to extract
/// - refDate: reference date
/// - Returns: extracted time unit value
func toUnit(_ unit: Calendar.Component, to refDate: DateRepresentable) -> Int
}
public extension DateRepresentable {
// MARK: - Common Properties
var calendar: Calendar {
return region.calendar
}
// MARK: - Date Components Properties
var year: Int {
return dateComponents.year!
}
var month: Int {
return dateComponents.month!
}
var monthDays: Int {
return calendar.range(of: .day, in: .month, for: date)!.count
}
func monthName(_ style: SymbolFormatStyle) -> String {
let formatter = self.formatter(format: nil)
let idx = (month - 1)
switch style {
case .default: return formatter.monthSymbols[idx]
case .defaultStandalone: return formatter.standaloneMonthSymbols[idx]
case .short: return formatter.shortMonthSymbols[idx]
case .standaloneShort: return formatter.shortStandaloneMonthSymbols[idx]
case .veryShort: return formatter.veryShortMonthSymbols[idx]
case .standaloneVeryShort: return formatter.veryShortStandaloneMonthSymbols[idx]
}
}
var day: Int {
return dateComponents.day!
}
var dayOfYear: Int {
return calendar.ordinality(of: .day, in: .year, for: date)!
}
@available(iOS 9.0, macOS 10.11, *)
var ordinalDay: String {
let day = self.day
return DateFormatter.sharedOrdinalNumberFormatter(locale: region.locale).string(from: day as NSNumber) ?? "\(day)"
}
var hour: Int {
return dateComponents.hour!
}
var nearestHour: Int {
let newDate = (date + (date.minute >= 30 ? 60 - date.minute : -date.minute).minutes)
return newDate.in(region: region).hour
}
var minute: Int {
return dateComponents.minute!
}
var second: Int {
return dateComponents.second!
}
var nanosecond: Int {
return dateComponents.nanosecond!
}
var msInDay: Int {
return (calendar.ordinality(of: .second, in: .day, for: date)! * 1000)
}
var weekday: Int {
return dateComponents.weekday!
}
func weekdayName(_ style: SymbolFormatStyle, locale: LocaleConvertible? = nil) -> String {
let formatter = self.formatter(format: nil) {
$0.locale = (locale ?? self.region.locale).toLocale()
}
let idx = (weekday - 1)
switch style {
case .default: return formatter.weekdaySymbols[idx]
case .defaultStandalone: return formatter.standaloneWeekdaySymbols[idx]
case .short: return formatter.shortWeekdaySymbols[idx]
case .standaloneShort: return formatter.shortStandaloneWeekdaySymbols[idx]
case .veryShort: return formatter.veryShortWeekdaySymbols[idx]
case .standaloneVeryShort: return formatter.veryShortStandaloneWeekdaySymbols[idx]
}
}
var weekOfYear: Int {
return dateComponents.weekOfYear!
}
var weekOfMonth: Int {
return dateComponents.weekOfMonth!
}
var weekdayOrdinal: Int {
return dateComponents.weekdayOrdinal!
}
var yearForWeekOfYear: Int {
return dateComponents.yearForWeekOfYear!
}
var firstDayOfWeek: Int {
return date.dateAt(.startOfWeek).day
}
var lastDayOfWeek: Int {
return date.dateAt(.endOfWeek).day
}
var quarter: Int {
let monthsInQuarter = Double(Calendar.current.monthSymbols.count) / 4.0
return Int(ceil( Double(month) / monthsInQuarter))
}
var isToday: Bool {
return calendar.isDateInToday(date)
}
var isYesterday: Bool {
return calendar.isDateInYesterday(date)
}
var isTomorrow: Bool {
return calendar.isDateInTomorrow(date)
}
var isInWeekend: Bool {
return calendar.isDateInWeekend(date)
}
var isInPast: Bool {
return date < Date()
}
var isInFuture: Bool {
return date > Date()
}
func quarterName(_ style: SymbolFormatStyle, locale: LocaleConvertible? = nil) -> String {
let formatter = self.formatter(format: nil) {
$0.locale = (locale ?? self.region.locale).toLocale()
}
let idx = (quarter - 1)
switch style {
case .default: return formatter.quarterSymbols[idx]
case .defaultStandalone: return formatter.standaloneQuarterSymbols[idx]
case .short, .veryShort: return formatter.shortQuarterSymbols[idx]
case .standaloneShort, .standaloneVeryShort: return formatter.shortStandaloneQuarterSymbols[idx]
}
}
var era: Int {
return dateComponents.era!
}
func eraName(_ style: SymbolFormatStyle, locale: LocaleConvertible? = nil) -> String {
let formatter = self.formatter(format: nil) {
$0.locale = (locale ?? self.region.locale).toLocale()
}
let idx = (era - 1)
switch style {
case .default, .defaultStandalone: return formatter.longEraSymbols[idx]
case .short, .standaloneShort, .veryShort, .standaloneVeryShort: return formatter.eraSymbols[idx]
}
}
var DSTOffset: TimeInterval {
return region.timeZone.daylightSavingTimeOffset(for: date)
}
// MARK: - Date Formatters
func formatter(format: String? = nil, configuration: ((DateFormatter) -> Void)? = nil) -> DateFormatter {
let formatter = (customFormatter ?? sharedFormatter)
if let dFormat = format {
formatter.dateFormat = dFormat
}
configuration?(formatter)
return formatter
}
func formatterForRegion(format: String? = nil, configuration: ((inout DateFormatter) -> Void)? = nil) -> DateFormatter {
var formatter = self.formatter(format: format, configuration: {
$0.timeZone = self.region.timeZone
$0.calendar = self.calendar
$0.locale = self.region.locale
})
configuration?(&formatter)
return formatter
}
var sharedFormatter: DateFormatter {
return DateFormatter.sharedFormatter(forRegion: region)
}
func toString(_ style: DateToStringStyles? = nil) -> String {
guard let style = style else {
return DateToStringStyles.standard.toString(self)
}
return style.toString(self)
}
func toFormat(_ format: String, locale: LocaleConvertible? = nil) -> String {
guard let fixedLocale = locale else {
return DateToStringStyles.custom(format).toString(self)
}
let fixedRegion = Region(calendar: region.calendar, zone: region.timeZone, locale: fixedLocale)
let fixedDate = DateInRegion(date.date, region: fixedRegion)
return DateToStringStyles.custom(format).toString(fixedDate)
}
func toRelative(since: DateInRegion? = nil, style: RelativeFormatter.Style? = nil, locale: LocaleConvertible? = nil) -> String {
return RelativeFormatter.format(date: self, to: since, style: style, locale: locale?.toLocale())
}
func toISO(_ options: ISOFormatter.Options? = nil) -> String {
return DateToStringStyles.iso( (options ?? ISOFormatter.Options([.withInternetDateTime])) ).toString(self)
}
func toDotNET() -> String {
return DOTNETFormatter.format(self, options: nil)
}
func toRSS(alt: Bool) -> String {
switch alt {
case true: return DateToStringStyles.altRSS.toString(self)
case false: return DateToStringStyles.rss.toString(self)
}
}
func toSQL() -> String {
return DateToStringStyles.sql.toString(self)
}
// MARK: - Conversion
func convertTo(region: Region) -> DateInRegion {
return DateInRegion(date, region: region)
}
// MARK: - Extract Time Components
func toUnits(_ units: Set<Calendar.Component>, to refDate: DateRepresentable) -> [Calendar.Component: Int] {
let cal = region.calendar
let components = cal.dateComponents(units, from: date, to: refDate.date)
return components.toDict()
}
func toUnit(_ unit: Calendar.Component, to refDate: DateRepresentable) -> Int {
let cal = region.calendar
let components = cal.dateComponents([unit], from: date, to: refDate.date)
return components.value(for: unit)!
}
}