How to format a Date in iOS 15 and macOS 12

During WWDC 2021, Apple introduced the new APIs that make date formatting a lot easier. Before iOS 15, we had to create a DateFormatter and later use it to convert a Date into a String:

static let dateFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.timeZone = TimeZone.current
    formatter.dateStyle = .medium
    formatter.timeStyle = .medium
    return formatter
}()

let now = Date.now
let dateString = dateFormatter.string(from: now)

// Oct 21, 2020 at 7:48 AM

From iOS 15, we can condense the code above to just two lines:

let now = Date.now
let dateString = now.formatted()

// 10/21/2020, 7:48 AM

By default, the formatted() method will use a sensible default format. What's cool is that it will respect the user's preferences. For example, for October 21st, 2020 at 07:48 in the US region, we will get 10/21/2020, 7:48 AM, but in Poland, we will have 21/10/2020 07:48.

Of course, we can adjust the formatting to our needs by customizing the date and time styles separately:

let now = Date.now
let dateString = now.formatted(date: .long, time: .standard)

// October 21, 2020, 7:48:43 AM

And we can also completely omit the part we don't need:

let onlyDate = now.formatted(date: .numeric, time: .omitted)
// 10/21/2020

let onlyTime = now.formatted(date: .omitted, time: .shortened)
// 7:48 AM

Here is the list of currently available DateStyles:

Name Example
omited
numeric 10/21/2020
abbreviated Oct 21, 2010
long October 21, 2020
complete Wednesday, October 21, 2020

And TimeStyles:

Name Example
omited
shortened
04:29 PM or 16:29
standard
4:29:24 PM or 16:29:24
complete
4:29:24 PM PDT or 16:29:24 GMT

If the built-in styles are not suitable, we can define a custom format:

let now = Date.now
let dateString = now.formatted(.dateTime.year().month(.wide).day(.twoDigits).weekday().hour(.twoDigitsNoAMPM).minute(.defaultDigits))

// Wed, October 21, 2020, 07:48

Using this method, we can select and customize the date or time components we need. The order of components does not matter as the formatted() method will adjust the output to the user's locale. Beside the .dateTime style, Apple also provided the .iso8601 style:

let now = Date.now
let dateString = now.formatted(.iso8601)

// 20201021T074843Z

let dateString = now.formatted(.iso8601.year().month().day().dateSeparator(.dash).dateTimeSeparator(.space).time(includingFractionalSeconds: false) .timeSeparator(.colon))

// 2020-10-21 07:48:43

If we need our custom style, we can do this by creating a formatter that conforms to the FormatStyle protocol:

struct BrackedDateTimeStyle: FormatStyle {
    typealias FormatInput = Date
    typealias FormatOutput = String
    
    func format(_ value: Self.FormatInput) -> Self.FormatOutput {
        return "[\(value)]"
    }
}

let now = Date.now
let dateString = now.formatted(BrackedDateTimeStyle())

// [2020-10-21 07:48:43 +0000]

We can also add an extension to the FormatStyle to make usage of our custom format more similar to Apple's API:

extension FormatStyle where Self == BrackedDateTimeStyle {
    static var brackedDateTime: BrackedDateTimeStyle {
        BrackedDateTimeStyle()
    }
}

let now = Date.now
let dateString = now.formatted(.brackedDateTime)

That's it. The new Date formatting APIs will not replace the good old DateFormatter. But in most cases, it's going to make our life easier.

If you want to know how to convert a String into a Date, check out my other article:

How to convert a String into a Date in iOS 15 and macOS 12 | This Dev Brain by Michal Tynior
Learn how to convert a String into a Date using the new APIs introduced in iOS 15 and macOS 12.