In Swift 5.5 and iOS 15, Apple has introduced new interfaces that allow developers to convert data types from Foundation into localized strings and vice versa —
The main aim is to provide an easier way to create formatted display strings with less customization instead of using old
This approach supports dates, date ranges, numerics, measurements, sequences, durations (from iOS 16), URLs (from iOS 16), byte counts, and a person’s name components.
In this article, we are going to focus on how to convert Dates and Date ranges to formatted localized strings for display, parse Date objects from string constant, and create our own custom format styles for Dates.
As you know, if you want to display the date in your app using, for example, some formats — short, long, or custom ("yyyy-MM-dd"), you need to create DateFormatter’s instances and set “dateStyle” or “dateFormat” (for custom style) properties:
extension Date {
func string(formatter: DateFormatter) -> String {
return formatter.string(from: self)
}
}
extension DateFormatter {
static var shortDateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .short
return formatter
}
static var longDateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}
static var customDateWithDashFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}
}
let date = Date()
print(date.string(formatter: .shortDateFormatter)) // 3/12/23
print(date.string(formatter: .longDateFormatter)) // March 12, 2023
print(date.string(formatter: .customDateWithDashFormatter)) // 2023-03-12
At first glance, everything seems fine here. But what if we need to add one more custom style to the display Date — "yyyy/MM/dd”? The main problem is we have to extend our date formatters by adding new:
extension DateFormatter {
static var customDateWithSlashFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy/MM/dd"
return formatter
}
}
print(Date().string(formatter: .customDateWithSlashFormatter)) // 2023/03/12
Let’s take a look at how Apple helps us resolve that issue. They have created a few methods that assist us in solving the problem described above.
public func formatted() -> String
print(Date().formatted()) // 3/12/2023, 2:05 PM
The basic method that converts Date into a localized string using the default transformation style.
public func formatted<F>(_ format: F) -> F.FormatOutput where F : FormatStyle, F.FormatInput == Date
print(Date().formatted(.dateTime.day(.twoDigits).month(.twoDigits).year(.twoDigits))) // 03/12/23
As we see here, we must pass a parameter that conforms to the FormatStyle protocol. Apple took care of that too: Swift offers us a built-in struct that conforms to the FormatStyle protocol, and we do not need to create our own.
Also, it has a static variable “static var dateTime: Date.FormatStyle”.
Besides, we have the possibility to create a new instance of Date.FormatStyle:
print(Date().formatted(Date.FormatStyle().day().month(.wide).year())) // March 12, 2023
Date.FormatStyle has a lot of methods like “day(…), month(…), year(…), etc.”; they have their own parameters for customization and return the same type (Date.FormatStyle type). These instance methods offer us many options for creating a multitude of options.
Furthermore, we do not need to worry about the order we call the functions; Swift takes care of that and chooses the correct format based on the user's preferences.
public func formatted(date: Date.FormatStyle.DateStyle, time: Date.FormatStyle.TimeStyle) -> String
print(Date().formatted(date: .long, time: .omitted)) // March 12, 2023
Here, Swift provides some default pre-defined format styles for date and time — Date.FormatStyle.DateStyle and Date.FormatStyle.TimeStyle.
public func ISO8601Format(_ style: Date.ISO8601FormatStyle = .init()) -> String
print(Date().ISO8601Format(.iso8601.day().month().year().dateSeparator(.dash))) // 2023-03-12
It is used to convert Date to localized strings using iso8601 format. Date.ISO8601FormatStyle also has a static variable “static var iso8601: Date.ISO8601FormatStyle” and has similar methods like Date.FormatStyle. Moreover, we can use “formatted(…)” method to do the same:
print(Date().formatted(.iso8601.day().month().year().dateSeparator(.dash))) // 2023-03-12
For date ranges, likewise, it is easy to use the same approach to create a localized formatted string because Swift provides very similar methods that we have for Date:
public func formatted() -> String
public func formatted<S>(_ style: S) -> S.FormatOutput where S : FormatStyle, S.FormatInput == Range<Date>
public func formatted(date: Date.IntervalFormatStyle.DateStyle, time: Date.IntervalFormatStyle.TimeStyle) -> String
We see that we already have “static var interval: Date.IntervalFormatStyle” and can use it in the same way as we use Date.FormatStyle:
let dateRange = Date(timeInterval: -3600, since: Date())..<Date()
print(dateRange.formatted(.interval.day().month(.wide).year().minute().hour())) // March 12, 2023, 5:18 – 6:18 PM
Or create a new instance of Date.IntervalFormatStyle:
print(dateRange.formatted(Date.IntervalFormatStyle().day().month(.wide).year().minute().hour())) // March 12, 2023, 5:18 – 6:18 PM
Furthermore, you can find the time gap between the earliest and latest dates in a given date range using distinct units:
print(dateRange.formatted(.components(style: .wide, fields: [.hour]))) // 1 hour
Swift provides several variants to transform a string into a Date object.
Let’s create a date string and a new instance of Date.FormatStyle with some customization that expects in what format our localized string constant can be parsed:
let dateStr = "March 12, 2023"
let formatStyle = Date.FormatStyle().day().month().year()
Apple introduced the ParseStrategy protocol to do such a task. ParseStrategy has two associated types: Input and Output, for Date input is String, and output is Date. By default, Date.FormatStyle conforms to that protocol, so we can use the method “parse(…)” directly:
try? formatStyle.parse(dateStr)
Or we can use ParseableFormatStyle’s property “parseStrategy” (Date.FormatStyle conforms to ParseableFormatStyle protocol by default too):
try? formatStyle.parseStrategy.parse(dateStr)
Swift provides a default parse strategy for Date — Date.ParseStrategy. It can be used with a new initializer for Date (for the format, we are using an interpolation initializer):
let parseStrategy = Date.ParseStrategy(format: "\(month: .wide) \(day: .defaultDigits), \(year: .defaultDigits)", locale: .current, timeZone: .current)
try? Date(dateStr, strategy: parseStrategy)
For Date.ISO8601FormatStyle, we can do the same manipulations:
let iso8601DateStr = "2023-03-12T18:06:55Z"
let iso8601FormatStyle = Date.ISO8601FormatStyle()
try? iso8601FormatStyle.parse(iso8601DateStr)
try? iso8601FormatStyle.parseStrategy.parse(iso8601DateStr)
try? Date(iso8601DateStr, strategy: iso8601FormatStyle)
try? Date(iso8601DateStr, strategy: iso8601FormatStyle.parseStrategy)
Suppose we need to convert some dates in our app into localized string constants using concrete Calendar or Locale. We should create our own FormatStyle’s object:
struct UkrainianLocaleFormatStyle: FormatStyle {
typealias FormatInput = Date
typealias FormatOutput = String
private static let customFormatStyle = Date.FormatStyle(date: .long, time: .omitted, locale: Locale(identifier: "uk_UA"), calendar: Calendar(identifier: .gregorian))
func format(_ value: Date) -> String {
return Self.customFormatStyle.format(value)
}
}
As we see, we have some requirements from FormatStyle protocol: for Input type, we have Date, for Output — String, for “format(…)” method, we have created a custom format style. For using our custom format style, we extend FormatStyle:
extension FormatStyle where Self == UkrainianLocaleFormatStyle {
static var ukrainianLocale: UkrainianLocaleFormatStyle { return UkrainianLocaleFormatStyle() }
}
print(Date().formatted(.ukrainianLocale)) // 12 березня 2023 р.
In this article, we’ve made a brief overview of how to convert data types (in our example — Date) to and from localized strings.
Apple brought in a highly configurable instrument to format the built-in data types, allowing developers to customize the formatting rules to suit their specific needs.
They perform better and are simpler to use. However, there are some limitations: