Date Formatter Swift Extensions
We create many DateFormatters in our codebase, which are currently grouped into one “utility” category on DateFormatter. The problem with this situation is that once written, it is very hard to see where these often specific formatters are actually used.
public extension DateFormatter {
convenience init(format: String,
locale: Locale = .current,
calendar: Calendar = .current,
timeZone: TimeZone = .current) {
self.init()
self.calendar = calendar
self.timeZone = timeZone
self.locale = locale
dateFormat = format
}
}
This allows:
DateFormatter(format: "dd/MM/yyyy").string(from: Date())
// This would give 20/02/2018 for today.
As well as providing specific arguments:
DateFormatter(format: "dd/MM/yyyy", calendar: Calendar(identifier: .indian))
// This would give 01/12/1939 for today.
DateFormatter has introduced the idea of date templates in recent years, and it would probably be preferable to use that instead. We can use our existing initializer extension.
public extension DateFormatter {
convenience init(template: String,
locale: Locale = .current,
calendar: Calendar = .current,
timeZone: TimeZone = .current) {
let format = DateFormatter.dateFormat(fromTemplate: template, options: 0, locale: locale)
self.init(format: format, locale: locale, calendar: calendar, timeZone: timeZone)
}
}
Which again allows:
DateFormatter(template: "yyyyMMdd").string(from: Date())
// This would give 20/02/2018 for today.
Note that template and format are both strings. It would be potentially easy to mix up a format string with a template string in the app. We’d strive to make this a little better. To do this we can introduce separate Format and Template types, which are essentially wrappers around String. This is called a BLAH TYPE.
We can add a Format type like so:
public extension DateFormatter {
public struct Format {
fileprivate let string: String
public init(_ formatString: String) {
string = formatString
}
}
convenience init(format: Format?,
locale: Locale = .current,
calendar: Calendar = .current,
timeZone: TimeZone = .current) {
self.init()
self.calendar = calendar
self.timeZone = timeZone
self.locale = locale
dateFormat = format?.string
}
}
For the template extension we can make use of our fomatter
public extension DateFormatter {
public struct Template {
fileprivate let string: String
public init(_ templateString: String) {
string = templateString
}
}
private static func format(from template: Template, locale: Locale) -> Format? {
return dateFormat(fromTemplate: template.string, options: 0, locale: locale).flatMap { Format($0) }
}
convenience init(template: Template,
locale: Locale = .current,
calendar: Calendar = .current,
timeZone: TimeZone = .current) {
let format = DateFormatter.format(from: template, locale: locale)
self.init(format: format, locale: locale, calendar: calendar, timeZone: timeZone)
}
}