//
|
// 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)!
|
}
|
|
}
|