Skip to content

Instantly share code, notes, and snippets.

@vitsky91
Created February 18, 2022 11:14
Show Gist options
  • Save vitsky91/b046bc6df8c7a3f867f1102d97697dc3 to your computer and use it in GitHub Desktop.
Save vitsky91/b046bc6df8c7a3f867f1102d97697dc3 to your computer and use it in GitHub Desktop.
Extend Date for convenient using
import Foundation
public extension Date {
// MARK: - Convert from String
/*
Creates a new Date based on a string of a specified format. Supports optional timezone and locale.
*/
init?(fromString string: String, format: DateFormatType, timeZone: TimeZoneType = .local, locale: Locale = Foundation.Locale.current, isLenient: Bool = true) {
guard !string.isEmpty else {
return nil
}
var string = string
switch format {
case .dotNet:
let pattern = "\\\\?/Date\\((\\d+)(([+-]\\d{2})(\\d{2}))?\\)\\\\?/"
let regex = try! NSRegularExpression(pattern: pattern)
guard let match = regex.firstMatch(in: string, range: NSRange(location: 0, length: string.utf16.count)) else {
return nil
}
#if swift(>=4.0)
let dateString = (string as NSString).substring(with: match.range(at: 1))
#else
let dateString = (string as NSString).substring(with: match.rangeAt(1))
#endif
let interval = Double(dateString)! / 1000.0
self.init(timeIntervalSince1970: interval)
return
case .rss, .altRSS:
if string.hasSuffix("Z") {
string = string[..<string.index(string.endIndex, offsetBy: -1)].appending("GMT")
}
case .isoDateTimeMilliSec, .isoDateTimeSec, .isoDateTime, .isoYear, . isoDate, .isoYearMonth:
if #available(iOS 10.0, watchOS 4, tvOS 10, macOS 11, *) {
let formatter = Date.cachedDateFormatters.cachedISOFormatter(format, timeZone: timeZone, locale: locale)
guard let date = formatter.date(from: string) else {
return nil
}
self.init(timeInterval: 0, since: date)
return
}
default:
break
}
let formatter = Date.cachedDateFormatters.cachedFormatter(
format.stringFormat,
timeZone: timeZone.timeZone,
locale: locale,
isLenient: isLenient)
guard let date = formatter.date(from: string) else {
return nil
}
self.init(timeInterval: 0, since: date)
}
/*
Creates a new Date based on the first date detected on a string using data dectors.
*/
init?(detectFromString string: String) {
let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.date.rawValue)
let matches = detector?.matches(in: string, options: [], range: NSMakeRange(0, string.utf16.count))
if let date = matches?.first?.date {
self.init()
self = date
} else {
return nil
}
}
// MARK: - Convert to String
/// Converts the date to string using the short date and time style.
func toString(style: DateStyleType = .short) -> String {
switch style {
case .short:
return self.toString(dateStyle: .short, timeStyle: .short, isRelative: false)
case .medium:
return self.toString(dateStyle: .medium, timeStyle: .medium, isRelative: false)
case .long:
return self.toString(dateStyle: .long, timeStyle: .long, isRelative: false)
case .full:
return self.toString(dateStyle: .full, timeStyle: .full, isRelative: false)
case .ordinalDay:
let formatter = Date.cachedDateFormatters.cachedNumberFormatter()
if #available(iOSApplicationExtension 9.0, *) {
formatter.numberStyle = .ordinal
}
return formatter.string(from: component(.day)! as NSNumber)!
case .weekday:
let weekdaySymbols = Date.cachedDateFormatters.cachedFormatter().weekdaySymbols!
let string = weekdaySymbols[component(.weekday)! - 1] as String
return string
case .shortWeekday:
let shortWeekdaySymbols = Date.cachedDateFormatters.cachedFormatter().shortWeekdaySymbols!
return shortWeekdaySymbols[component(.weekday)! - 1] as String
case .veryShortWeekday:
let veryShortWeekdaySymbols = Date.cachedDateFormatters.cachedFormatter().veryShortWeekdaySymbols!
return veryShortWeekdaySymbols[component(.weekday)! - 1] as String
case .month:
let monthSymbols = Date.cachedDateFormatters.cachedFormatter().monthSymbols!
return monthSymbols[component(.month)! - 1] as String
case .shortMonth:
let shortMonthSymbols = Date.cachedDateFormatters.cachedFormatter().shortMonthSymbols!
return shortMonthSymbols[component(.month)! - 1] as String
case .veryShortMonth:
let veryShortMonthSymbols = Date.cachedDateFormatters.cachedFormatter().veryShortMonthSymbols!
return veryShortMonthSymbols[component(.month)! - 1] as String
}
}
/// Converts the date to string based on a date format, optional timezone and optional locale.
func toString(format: DateFormatType, timeZone: TimeZoneType = .local, locale: Locale = Locale.current) -> String {
var useLocale = locale
switch format {
case .dotNet:
let offset = Foundation.NSTimeZone.default.secondsFromGMT() / 3600
let nowMillis = 1000 * self.timeIntervalSince1970
return String(format: format.stringFormat, nowMillis, offset)
case .isoDateTimeMilliSec, .isoDateTimeSec, .isoDateTime,
.isoYear, . isoDate, .isoYearMonth:
if #available(iOS 10.0, watchOS 4, tvOS 10, macOS 11, *) {
let formatter = Date.cachedDateFormatters.cachedISOFormatter(format, timeZone: timeZone, locale: useLocale)
return formatter.string(from: self)
} else {
useLocale = Locale(identifier: "en_US_POSIX")
}
default:
break
}
let formatter = Date.cachedDateFormatters.cachedFormatter(format.stringFormat, timeZone: timeZone.timeZone, locale: useLocale)
formatter.amSymbol = "am"
formatter.pmSymbol = "pm"
return formatter.string(from: self)
}
/// Converts the date to string based on DateFormatter's date style and time style with optional relative date formatting, optional time zone and optional locale.
func toString(dateStyle: DateFormatter.Style, timeStyle: DateFormatter.Style, isRelative: Bool = false, timeZone: Foundation.TimeZone = Foundation.NSTimeZone.local, locale: Locale = Locale.current) -> String {
let formatter = Date.cachedDateFormatters.cachedFormatter(dateStyle, timeStyle: timeStyle, doesRelativeDateFormatting: isRelative, timeZone: timeZone, locale: locale)
return formatter.string(from: self)
}
/// Converts the date to string based on a relative time language. i.e. just now, 1 minute ago etc...
func toStringWithRelativeTime(strings: [RelativeTimeStringType: String]? = nil) -> String {
let time = self.timeIntervalSince1970
let now = Date().timeIntervalSince1970
let sec: Double = abs(now - time)
let min: Double = round(sec / 60)
let hr: Double = round(min / 60)
let d: Double = round(hr / 24)
switch toRelativeTime() {
case .nowPast:
return strings?[.nowPast] ?? NSLocalizedString("just now", comment: "Date format")
case .nowFuture:
return strings?[.nowFuture] ?? NSLocalizedString("in a few seconds", comment: "Date format")
case .secondsPast:
return String(
format: strings?[.secondsPast] ?? NSLocalizedString("%.f seconds ago", comment: "Date format"),
sec
)
case .secondsFuture:
return String(
format: strings?[.secondsFuture] ?? NSLocalizedString("in %.f seconds", comment: "Date format"),
sec
)
case .oneMinutePast:
return strings?[.oneMinutePast] ?? NSLocalizedString("1 minute ago", comment: "Date format")
case .oneMinuteFuture:
return strings?[.oneMinuteFuture] ?? NSLocalizedString("in 1 minute", comment: "Date format")
case .minutesPast:
return String(
format: strings?[.minutesPast] ?? NSLocalizedString("%.f minutes ago", comment: "Date format"),
min
)
case .minutesFuture:
return String(
format: strings?[.minutesFuture] ?? NSLocalizedString("in %.f minutes", comment: "Date format"),
min
)
case .oneHourPast:
return strings?[.oneHourPast] ?? NSLocalizedString("last hour", comment: "Date format")
case .oneHourFuture:
return strings?[.oneHourFuture] ?? NSLocalizedString("next hour", comment: "Date format")
case .hoursPast:
return String(
format: strings?[.hoursPast] ?? NSLocalizedString("%.f hours ago", comment: "Date format"),
hr
)
case .hoursFuture:
return String(
format: strings?[.hoursFuture] ?? NSLocalizedString("in %.f hours", comment: "Date format"),
hr
)
case .oneDayPast:
return strings?[.oneDayPast] ?? NSLocalizedString("yesterday", comment: "Date format")
case .oneDayFuture:
return strings?[.oneDayFuture] ?? NSLocalizedString("tomorrow", comment: "Date format")
case .daysPast:
return String(
format: strings?[.daysPast] ?? NSLocalizedString("%.f days ago", comment: "Date format"),
d
)
case .daysFuture:
return String(
format: strings?[.daysFuture] ?? NSLocalizedString("in %.f days", comment: "Date format"),
d
)
case .oneWeekPast:
return strings?[.oneWeekPast] ?? NSLocalizedString("last week", comment: "Date format")
case .oneWeekFuture:
return strings?[.oneWeekFuture] ?? NSLocalizedString("next week", comment: "Date format")
case .weeksPast:
let string = strings?[.weeksPast] ?? NSLocalizedString("%.f weeks ago", comment: "Date format")
return String(format: string, Double(abs(since(Date(), in: .week))))
case .weeksFuture:
let string = strings?[.weeksFuture] ?? NSLocalizedString("in %.f weeks", comment: "Date format")
return String(format: string, Double(abs(since(Date(), in: .week))))
case .oneMonthPast:
return strings?[.oneMonthPast] ?? NSLocalizedString("last month", comment: "Date format")
case .oneMonthFuture:
return strings?[.oneMonthFuture] ?? NSLocalizedString("next month", comment: "Date format")
case .monthsPast:
let string = strings?[.monthsPast] ?? NSLocalizedString("%.f months ago", comment: "Date format")
return String(format: string, Double(abs(since(Date(), in: .month))))
case .monthsFuture:
let string = strings?[.monthsFuture] ?? NSLocalizedString("in %.f months", comment: "Date format")
return String(format: string, Double(abs(since(Date(), in: .month))))
case .oneYearPast:
return strings?[.oneYearPast] ?? NSLocalizedString("last year", comment: "Date format")
case .oneYearFuture:
return strings?[.oneYearFuture] ?? NSLocalizedString("next year", comment: "Date format")
case .yearsPast:
let string = strings?[.yearsPast] ?? NSLocalizedString("%.f years ago", comment: "Date format")
return String(format: string, Double(abs(since(Date(), in: .year))))
case .yearsFuture:
let string = strings?[.yearsFuture] ?? NSLocalizedString("in %.f years", comment: "Date format")
return String(format: string, Double(abs(since(Date(), in: .year))))
}
}
/// Converts the date to a relative time language. i.e. .nowPast, .minutesFuture
func toRelativeTime() -> RelativeTimeStringType {
let time = self.timeIntervalSince1970
let now = Date().timeIntervalSince1970
let isPast = now - time > 0
let sec: Double = abs(now - time)
let min: Double = round(sec / 60)
let hr: Double = round(min / 60)
let d: Double = round(hr / 24)
if sec < 60 {
if sec < 10 {
if isPast {
return .nowPast
} else {
return .nowFuture
}
} else {
if isPast {
return .secondsPast
} else {
return .secondsFuture
}
}
}
if min < 60 {
if min == 1 {
if isPast {
return .oneMinutePast
} else {
return .oneMinuteFuture
}
} else {
if isPast {
return .minutesPast
} else {
return .minutesFuture
}
}
}
if hr < 24 {
if hr == 1 {
if isPast {
return .oneHourPast
} else {
return .oneHourFuture
}
} else {
if isPast {
return .hoursPast
} else {
return .hoursFuture
}
}
}
if d < 7 {
if d == 1 {
if isPast {
return .oneDayPast
} else {
return .oneDayFuture
}
} else {
if isPast {
return .daysPast
} else {
return .daysFuture
}
}
}
if d < 28 {
if isPast {
if compare(.isLastWeek) {
return .oneWeekPast
} else {
return .weeksPast
}
} else {
if compare(.isNextWeek) {
return .oneWeekFuture
} else {
return .weeksFuture
}
}
}
if compare(.isThisYear) {
if isPast {
if compare(.isLastMonth) {
return .oneMonthPast
} else {
return .monthsPast
}
} else {
if compare(.isNextMonth) {
return .oneMonthFuture
} else {
return .monthsFuture
}
}
}
if isPast {
if compare(.isLastYear) {
return .oneYearPast
} else {
return .yearsPast
}
} else {
if compare(.isNextYear) {
return .oneYearFuture
} else {
return .yearsFuture
}
}
}
// MARK: - Compare Dates
/// Compares dates to see if they are equal while ignoring time.
func compare(_ comparison: DateComparisonType) -> Bool {
switch comparison {
case .isToday:
return compare(.isSameDay(as: Date()))
case .isTomorrow:
let comparison = Date().adjust(.day, offset: 1)
return compare(.isSameDay(as: comparison))
case .isYesterday:
let comparison = Date().adjust(.day, offset: -1)
return compare(.isSameDay(as: comparison))
case .isSameDay(let date):
return component(.year) == date.component(.year)
&& component(.month) == date.component(.month)
&& component(.day) == date.component(.day)
case .isThisWeek:
return self.compare(.isSameWeek(as: Date()))
case .isNextWeek:
let comparison = Date().adjust(.week, offset: 1)
return compare(.isSameWeek(as: comparison))
case .isLastWeek:
let comparison = Date().adjust(.week, offset: -1)
return compare(.isSameWeek(as: comparison))
case .isSameWeek(let date):
if component(.week) != date.component(.week) {
return false
}
// Ensure time interval is under 1 week
return abs(self.timeIntervalSince(date)) < Date.weekInSeconds
case .isThisMonth:
return self.compare(.isSameMonth(as: Date()))
case .isNextMonth:
let comparison = Date().adjust(.month, offset: 1)
return compare(.isSameMonth(as: comparison))
case .isLastMonth:
let comparison = Date().adjust(.month, offset: -1)
return compare(.isSameMonth(as: comparison))
case .isSameMonth(let date):
return component(.year) == date.component(.year) && component(.month) == date.component(.month)
case .isThisYear:
return self.compare(.isSameYear(as: Date()))
case .isNextYear:
let comparison = Date().adjust(.year, offset: 1)
return compare(.isSameYear(as: comparison))
case .isLastYear:
let comparison = Date().adjust(.year, offset: -1)
return compare(.isSameYear(as: comparison))
case .isSameYear(let date):
return component(.year) == date.component(.year)
case .isInTheFuture:
return self.compare(.isLater(than: Date()))
case .isInThePast:
return self.compare(.isEarlier(than: Date()))
case .isEarlier(let date):
return (self as NSDate).earlierDate(date) == self
case .isLater(let date):
return (self as NSDate).laterDate(date) == self
case .isWeekday:
return !compare(.isWeekend)
case .isWeekend:
let range = Calendar.current.maximumRange(of: Calendar.Component.weekday)!
return (component(.weekday) == range.lowerBound || component(.weekday) == range.upperBound - range.lowerBound)
}
}
// MARK: - Adjust dates
/// Creates a new date with adjusted components
func adjust(_ component: DateComponentType, offset: Int) -> Date {
var dateComp = DateComponents()
switch component {
case .second:
dateComp.second = offset
case .minute:
dateComp.minute = offset
case .hour:
dateComp.hour = offset
case .day:
dateComp.day = offset
case .weekday:
dateComp.weekday = offset
case .nthWeekday:
dateComp.weekdayOrdinal = offset
case .week:
dateComp.weekOfYear = offset
case .month:
dateComp.month = offset
case .year:
dateComp.year = offset
}
return Calendar.current.date(byAdding: dateComp, to: self)!
}
/// Return a new Date object with the new hour, minute and seconds values.
func adjust(hour: Int?, minute: Int?, second: Int?, day: Int? = nil, month: Int? = nil) -> Date {
var comp = Date.components(self)
comp.month = month ?? comp.month
comp.day = day ?? comp.day
comp.hour = hour ?? comp.hour
comp.minute = minute ?? comp.minute
comp.second = second ?? comp.second
return Calendar.current.date(from: comp)!
}
// MARK: - Date for...
func dateFor(_ type: DateForType, calendar: Calendar = Calendar.current) -> Date {
switch type {
case .startOfDay:
return adjust(hour: 0, minute: 0, second: 0)
case .endOfDay:
return adjust(hour: 23, minute: 59, second: 59)
case .startOfWeek:
return calendar.date(from: calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: self))!
case .endOfWeek:
let weekStart = self.dateFor(.startOfWeek, calendar: calendar)
return weekStart.adjust(.day, offset: 6)
case .startOfMonth:
return adjust(hour: 0, minute: 0, second: 0, day: 1)
case .endOfMonth:
let month = (component(.month) ?? 0) + 1
return adjust(hour: 0, minute: 0, second: 0, day: 0, month: month)
case .tomorrow:
return adjust(.day, offset: 1)
case .yesterday:
return adjust(.day, offset: -1)
case .nearestMinute(let nearest):
let minutes = (component(.minute)! + nearest / 2) / nearest * nearest
return adjust(hour: nil, minute: minutes, second: nil)
case .nearestHour(let nearest):
let hours = (component(.hour)! + nearest / 2) / nearest * nearest
return adjust(hour: hours, minute: 0, second: nil)
case .startOfYear:
let month = Date().component(.month)! - 1
let day = Date().component(.day)! - 1
return Date()
.adjust(.month, offset: -(month))
.adjust(.day, offset: -(day))
.adjust(hour: 0, minute: 0, second: 0)
case .endOfYear:
let month = Date().component(.month)!
let day = Date().component(.day)!
return Date()
.adjust(.month, offset: 12 - month)
.adjust(.day, offset: 31 - day)
.adjust(hour: 23, minute: 59, second: 59)
}
}
// MARK: - Time since...
func since(_ date: Date, in component: DateComponentType) -> Int64 {
switch component {
case .second:
return Int64(timeIntervalSince(date))
case .minute:
let interval = timeIntervalSince(date)
return Int64(interval / Date.minuteInSeconds)
case .hour:
let interval = timeIntervalSince(date)
return Int64(interval / Date.hourInSeconds)
case .day:
let calendar = Calendar.current
let end = calendar.ordinality(of: .day, in: .era, for: self)
let start = calendar.ordinality(of: .day, in: .era, for: date)
return Int64(end! - start!)
case .weekday:
let calendar = Calendar.current
let end = calendar.ordinality(of: .weekday, in: .era, for: self)
let start = calendar.ordinality(of: .weekday, in: .era, for: date)
return Int64(end! - start!)
case .nthWeekday:
let calendar = Calendar.current
let end = calendar.ordinality(of: .weekdayOrdinal, in: .era, for: self)
let start = calendar.ordinality(of: .weekdayOrdinal, in: .era, for: date)
return Int64(end! - start!)
case .week:
let calendar = Calendar.current
let end = calendar.ordinality(of: .weekOfYear, in: .era, for: self)
let start = calendar.ordinality(of: .weekOfYear, in: .era, for: date)
return Int64(end! - start!)
case .month:
let calendar = Calendar.current
let end = calendar.ordinality(of: .month, in: .era, for: self)
let start = calendar.ordinality(of: .month, in: .era, for: date)
return Int64(end! - start!)
case .year:
let calendar = Calendar.current
let end = calendar.ordinality(of: .year, in: .era, for: self)
let start = calendar.ordinality(of: .year, in: .era, for: date)
return Int64(end! - start!)
}
}
// MARK: - Extracting components
func component(_ component: DateComponentType) -> Int? {
let components = Date.components(self)
switch component {
case .second:
return components.second
case .minute:
return components.minute
case .hour:
return components.hour
case .day:
return components.day
case .weekday:
return components.weekday
case .nthWeekday:
return components.weekdayOrdinal
case .week:
return components.weekOfYear
case .month:
return components.month
case .year:
return components.year
}
}
func numberOfDaysInMonth() -> Int {
let range = Calendar.current.range(of: Calendar.Component.day, in: Calendar.Component.month, for: self)!
return range.upperBound - range.lowerBound
}
func firstDayOfWeek() -> Int {
let distanceToStartOfWeek = Date.dayInSeconds * Double(self.component(.weekday)! - 1)
let interval: TimeInterval = self.timeIntervalSinceReferenceDate - distanceToStartOfWeek
return Date(timeIntervalSinceReferenceDate: interval).component(.day)!
}
func lastDayOfWeek() -> Int {
let distanceToStartOfWeek = Date.dayInSeconds * Double(self.component(.weekday)! - 1)
let distanceToEndOfWeek = Date.dayInSeconds * Double(7)
let interval: TimeInterval = self.timeIntervalSinceReferenceDate - distanceToStartOfWeek + distanceToEndOfWeek
return Date(timeIntervalSinceReferenceDate: interval).component(.day)!
}
// MARK: - Internal Components
internal static func componentFlags() -> Set<Calendar.Component> { return [Calendar.Component.year, Calendar.Component.month, Calendar.Component.day, Calendar.Component.weekOfYear, Calendar.Component.hour, Calendar.Component.minute, Calendar.Component.second, Calendar.Component.weekday, Calendar.Component.weekdayOrdinal, Calendar.Component.weekOfYear] }
internal static func components(_ fromDate: Date) -> DateComponents {
return Calendar.current.dateComponents(Date.componentFlags(), from: fromDate)
}
internal class ConcurrentFormatterCache {
private static let cachedISODateFormattersQueue = DispatchQueue(
label: "iso-date-formatter-queue",
attributes: .concurrent
)
private static let cachedDateFormattersQueue = DispatchQueue(
label: "date-formatter-queue",
attributes: .concurrent
)
private static let cachedNumberFormatterQueue = DispatchQueue(
label: "number-formatter-queue",
attributes: .concurrent
)
private static var cachedISODateFormatters = [String: ISO8601DateFormatter]()
private static var cachedDateFormatters = [String: DateFormatter]()
private static var cachedNumberFormatter = NumberFormatter()
private func register(hashKey: String, formatter: ISO8601DateFormatter) -> Void {
ConcurrentFormatterCache.cachedISODateFormattersQueue.async(flags: .barrier) {
ConcurrentFormatterCache.cachedISODateFormatters.updateValue(formatter, forKey: hashKey)
}
}
private func register(hashKey: String, formatter: DateFormatter) -> Void {
ConcurrentFormatterCache.cachedDateFormattersQueue.async(flags: .barrier) {
ConcurrentFormatterCache.cachedDateFormatters.updateValue(formatter, forKey: hashKey)
}
}
private func retrieve(hashKeyForISO hashKey: String) -> ISO8601DateFormatter? {
let dateFormatter = ConcurrentFormatterCache.cachedISODateFormattersQueue.sync { () -> ISO8601DateFormatter? in
guard let result = ConcurrentFormatterCache.cachedISODateFormatters[hashKey] else { return nil }
return result.copy() as? ISO8601DateFormatter
}
return dateFormatter
}
private func retrieve(hashKey: String) -> DateFormatter? {
let dateFormatter = ConcurrentFormatterCache.cachedDateFormattersQueue.sync { () -> DateFormatter? in
guard let result = ConcurrentFormatterCache.cachedDateFormatters[hashKey] else { return nil }
return result.copy() as? DateFormatter
}
return dateFormatter
}
private func retrieve() -> NumberFormatter {
let numberFormatter = ConcurrentFormatterCache.cachedNumberFormatterQueue.sync { () -> NumberFormatter in
// Should always be NumberFormatter
return ConcurrentFormatterCache.cachedNumberFormatter.copy() as! NumberFormatter
}
return numberFormatter
}
public func cachedISOFormatter(_ format: DateFormatType, timeZone: TimeZoneType, locale: Locale) -> ISO8601DateFormatter {
let hashKey = "\(format.stringFormat.hashValue)\(timeZone.timeZone.hashValue)\(locale.hashValue)"
if Date.cachedDateFormatters.retrieve(hashKeyForISO: hashKey) == nil {
let formatter = ISO8601DateFormatter()
formatter.timeZone = timeZone.timeZone
var options: ISO8601DateFormatter.Options = []
switch format {
case .isoDate:
options = [.withFullDate]
case .isoYearMonth:
options = [.withYear, .withMonth]
case .isoYear:
options = [.withYear, .withFractionalSeconds]
case .isoDateTimeSec, .isoDateTime:
options = [.withInternetDateTime]
case .isoDateTimeMilliSec:
options = [.withInternetDateTime, .withFractionalSeconds]
default:
fatalError("Unimplemented format \(format)")
}
formatter.formatOptions = options
Date.cachedDateFormatters.register(hashKey: hashKey, formatter: formatter)
}
return Date.cachedDateFormatters.retrieve(hashKeyForISO: hashKey)!
}
public func cachedFormatter(_ format: String = DateFormatType.standard.stringFormat,
timeZone: Foundation.TimeZone = Foundation.TimeZone.current,
locale: Locale = Locale.current, isLenient: Bool = true) -> DateFormatter {
let hashKey = "\(format.hashValue)\(timeZone.hashValue)\(locale.hashValue)"
if Date.cachedDateFormatters.retrieve(hashKey: hashKey) == nil {
let formatter = DateFormatter()
formatter.dateFormat = format
formatter.timeZone = timeZone
formatter.locale = locale
formatter.isLenient = isLenient
Date.cachedDateFormatters.register(hashKey: hashKey, formatter: formatter)
}
return Date.cachedDateFormatters.retrieve(hashKey: hashKey)!
}
/// Generates a cached formatter based on the provided date style, time style and relative date.
/// Formatters are cached in a singleton array using hashkeys.
public func cachedFormatter(_ dateStyle: DateFormatter.Style, timeStyle: DateFormatter.Style, doesRelativeDateFormatting: Bool, timeZone: Foundation.TimeZone = Foundation.NSTimeZone.local, locale: Locale = Locale.current, isLenient: Bool = true) -> DateFormatter {
let hashKey = "\(dateStyle.hashValue)\(timeStyle.hashValue)\(doesRelativeDateFormatting.hashValue)\(timeZone.hashValue)\(locale.hashValue)"
if Date.cachedDateFormatters.retrieve(hashKey: hashKey) == nil {
let formatter = DateFormatter()
formatter.dateStyle = dateStyle
formatter.timeStyle = timeStyle
formatter.doesRelativeDateFormatting = doesRelativeDateFormatting
formatter.timeZone = timeZone
formatter.locale = locale
formatter.isLenient = isLenient
Date.cachedDateFormatters.register(hashKey: hashKey, formatter: formatter)
}
return Date.cachedDateFormatters.retrieve(hashKey: hashKey)!
}
public func cachedNumberFormatter() -> NumberFormatter {
return Date.cachedDateFormatters.retrieve()
}
}
/// A cached static array of DateFormatters so that thy are only created once.
private static var cachedDateFormatters = ConcurrentFormatterCache()
// MARK: - Intervals In Seconds
internal static let minuteInSeconds: Double = 60
internal static let hourInSeconds: Double = 3600
internal static let dayInSeconds: Double = 86400
internal static let weekInSeconds: Double = 604800
internal static let yearInSeconds: Double = 31556926
}
// MARK: - Enums used
/**
The string format used for date string conversion.
case isoYear: i.e. 1997 case isoYearMonth: i.e. 1997-07 case isoDate: i.e. 1997-07-16 case isoDateTime: i.e. 1997-07-16T19:20+01:00 case isoDateTimeSec: i.e. 1997-07-16T19:20:30+01:00 case isoDateTimeMilliSec: i.e. 1997-07-16T19:20:30.45+01:00 case dotNet: i.e. "/Date(1268123281843)/" case rss: i.e. "Fri, 09 Sep 2011 15:26:08 +0200" case altRSS: i.e. "09 Sep 2011 15:26:08 +0200" case httpHeader: i.e. "Tue, 15 Nov 1994 12:45:26 GMT" case standard: "EEE MMM dd HH:mm:ss Z yyyy" case custom(String): a custom date format string
*/
public enum DateFormatType {
/// The ISO8601 formatted year "yyyy" i.e. 1997
case isoYear
/// The ISO8601 formatted year and month "yyyy-MM" i.e. 1997-07
case isoYearMonth
/// The ISO8601 formatted date "yyyy-MM-dd" i.e. 1997-07-16
case isoDate
/// The ISO8601 formatted date and time "yyyy-MM-dd'T'HH:mmZ" i.e. 1997-07-16T19:20+01:00
case isoDateTime
/// The ISO8601 formatted date, time and sec "yyyy-MM-dd'T'HH:mm:ssZ" i.e. 1997-07-16T19:20:30+01:00
case isoDateTimeSec
/// The ISO8601 formatted date, time and millisec "yyyy-MM-dd'T'HH:mm:ss.SSSZ" i.e. 1997-07-16T19:20:30.45+01:00
case isoDateTimeMilliSec
/// The dotNet formatted date "/Date(%d%d)/" i.e. "/Date(1268123281843)/"
case dotNet
/// The RSS formatted date "EEE, d MMM yyyy HH:mm:ss ZZZ" i.e. "Fri, 09 Sep 2011 15:26:08 +0200"
case rss
/// The Alternative RSS formatted date "d MMM yyyy HH:mm:ss ZZZ" i.e. "09 Sep 2011 15:26:08 +0200"
case altRSS
/// The http header formatted date "EEE, dd MM yyyy HH:mm:ss ZZZ" i.e. "Tue, 15 Nov 1994 12:45:26 GMT"
case httpHeader
/// A generic standard format date i.e. "EEE MMM dd HH:mm:ss Z yyyy"
case standard
/// A custom date format string
case custom(String)
var stringFormat: String {
switch self {
case .isoYear: return "yyyy"
case .isoYearMonth: return "yyyy-MM"
case .isoDate: return "yyyy-MM-dd"
case .isoDateTime: return "yyyy-MM-dd'T'HH:mmZ"
case .isoDateTimeSec: return "yyyy-MM-dd'T'HH:mm:ssZ"
case .isoDateTimeMilliSec: return "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
case .dotNet: return "/Date(%d%f)/"
case .rss: return "EEE, d MMM yyyy HH:mm:ss ZZZ"
case .altRSS: return "d MMM yyyy HH:mm:ss ZZZ"
case .httpHeader: return "EEE, dd MM yyyy HH:mm:ss ZZZ"
case .standard: return "EEE MMM dd HH:mm:ss Z yyyy"
case .custom(let customFormat): return customFormat
}
}
}
extension DateFormatType: Equatable {
public static func == (lhs: DateFormatType, rhs: DateFormatType) -> Bool {
switch (lhs, rhs) {
case (.custom(let lhsString), .custom(let rhsString)):
return lhsString == rhsString
default:
return lhs == rhs
}
}
}
/// The time zone to be used for date conversion
public enum TimeZoneType {
case local, `default`, utc, custom(Int)
var timeZone: TimeZone {
switch self {
case .local: return NSTimeZone.local
case .default: return NSTimeZone.default
case .utc: return TimeZone(secondsFromGMT: 0)!
case let .custom(gmt): return TimeZone(secondsFromGMT: gmt)!
}
}
}
// The string keys to modify the strings in relative format
public enum RelativeTimeStringType {
case nowPast, nowFuture, secondsPast, secondsFuture, oneMinutePast, oneMinuteFuture, minutesPast, minutesFuture, oneHourPast, oneHourFuture, hoursPast, hoursFuture, oneDayPast, oneDayFuture, daysPast, daysFuture, oneWeekPast, oneWeekFuture, weeksPast, weeksFuture, oneMonthPast, oneMonthFuture, monthsPast, monthsFuture, oneYearPast, oneYearFuture, yearsPast, yearsFuture
}
// The type of comparison to do against today's date or with the suplied date.
public enum DateComparisonType {
// Days
/// Checks if date today.
case isToday
/// Checks if date is tomorrow.
case isTomorrow
/// Checks if date is yesterday.
case isYesterday
/// Compares date days
case isSameDay(as: Date)
// Weeks
/// Checks if date is in this week.
case isThisWeek
/// Checks if date is in next week.
case isNextWeek
/// Checks if date is in last week.
case isLastWeek
/// Compares date weeks
case isSameWeek(as: Date)
// Months
/// Checks if date is in this month.
case isThisMonth
/// Checks if date is in next month.
case isNextMonth
/// Checks if date is in last month.
case isLastMonth
/// Compares date months
case isSameMonth(as: Date)
// Years
/// Checks if date is in this year.
case isThisYear
/// Checks if date is in next year.
case isNextYear
/// Checks if date is in last year.
case isLastYear
/// Compare date years
case isSameYear(as: Date)
// Relative Time
/// Checks if it's a future date
case isInTheFuture
/// Checks if the date has passed
case isInThePast
/// Checks if earlier than date
case isEarlier(than: Date)
/// Checks if later than date
case isLater(than: Date)
/// Checks if it's a weekday
case isWeekday
/// Checks if it's a weekend
case isWeekend
}
// The date components available to be retrieved or modifed
public enum DateComponentType {
case second, minute, hour, day, weekday, nthWeekday, week, month, year
}
// The type of date that can be used for the dateFor function.
public enum DateForType {
case startOfDay, endOfDay, startOfWeek, endOfWeek, startOfMonth, endOfMonth, tomorrow, yesterday, nearestMinute(minute: Int), nearestHour(hour: Int), startOfYear, endOfYear
}
// Convenience types for date to string conversion
public enum DateStyleType {
/// Short style: "2/27/17, 2:22 PM"
case short
/// Medium style: "Feb 27, 2017, 2:22:06 PM"
case medium
/// Long style: "February 27, 2017 at 2:22:06 PM EST"
case long
/// Full style: "Monday, February 27, 2017 at 2:22:06 PM Eastern Standard Time"
case full
/// Ordinal day: "27th"
case ordinalDay
/// Weekday: "Monday"
case weekday
/// Short week day: "Mon"
case shortWeekday
/// Very short weekday: "M"
case veryShortWeekday
/// Month: "February"
case month
/// Short month: "Feb"
case shortMonth
/// Very short month: "F"
case veryShortMonth
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment