iOS and macOS contain built in support for various ways to format dates, such as for human readable text and to show intervals between dates.

At WWDC19, Apple added a new RelativeDateTimeFormatter, which formats relative dates from the current date, for example by formatting a past date as "X days ago" or today as "today".

This guide explores how to display and format dates using many of the different date formatters built into Apple's platforms.

Setup

As all of the built in date formatters are available in Apple's Foundation framework, no installation is required to use them. However, make sure to add import Foundation to the top of your file to be able to use them in your code.

If you'd like to follow along with this guide, just create a Swift Playground using Xcode or the Swift Playgrounds app for iPad.

Additionally, you can click any of the class or header names to go to Apple's official Developer Documentation.

Formatting Basic Dates

Formatting basic dates is easy with DateFormatter. It supports many different time and date styles, allowing you to get the format that you want.

Just create a formatter and set its time and date style to use it:

let formatter = DateFormatter()
formatter.timeStyle = .short
formatter.dateStyle = .short

let currentDate = Date()
formatter.string(from: currentDate) // 21/7/2019, 9:41 AM

DateFormatter can be used with either a date or time style, or both.

Date Styles

None

The .none style ignores the date and only uses the time style.

formatter.dateStyle = .none
formatter.string(from: currentDate)

Short

The .short style only uses numbers without text.

formatter.dateStyle = .short
formatter.string(from: currentDate) // 21/7/2019

Medium

The .medium style is more human readable and uses the short name of the month.

formatter.dateStyle = .medium
formatter.string(from: currentDate) // 21 Jul 2019

Long

The .long style is the same as the medium style but slightly longer, using the full name of the month.

formatter.dateStyle = .long
formatter.string(from: currentDate) // 21 July 2019

Full

The .full style displays the entire date, including the name of the week.

formatter.dateStyle = .full
formatter.string(from: currentDate) // Sunday, 21 July 2019

Time Styles

None

The .none style ignores the time and only uses the date style.

formatter.timeStyle = .none
formatter.string(from: currentDate)

Short

The .short style is the simplest time style and just shows the hours, minutes, and whether it is currently AM or PM.

formatter.timeStyle = .short
formatter.string(from: currentDate) // 9:41 AM

Medium

The .medium style is the same as the short style but also displays seconds.

formatter.timeStyle = .medium
formatter.string(from: currentDate) // 9:41:00 AM

Long

The .long style is the same as the short style but also displays the time zone.

formatter.timeStyle = .long
formatter.string(from: currentDate) // 9:41:00 AM GMT+2

Full

The .full style displays everything from the other time styles as well as the entire name of the time zone.

formatter.timeStyle = .full
formatter.string(from: currentDate) // 9:41:00 AM Central European Summer Time

Formatting DateComponents

DateComponentsFormatter formats DateComponents instances, which contain dates and times specified in terms of units.

Just create a formatter and pass an instance of DateComponents, and the formatter will represent the components the best it can.

let formatter = DateComponentsFormatter()

let components = DateComponents(hour: 9, minute: 41)
formatter.string(from: components) // 9:41

Unit Styles

DateComponentsFormatter contains five unit styles, which format the date components in different ways.

formatter.unitsStyle = .positional // .positional, .abbreviated, .short, .full or .spellOut

Positional

The .positional style is the default style, and uses typical date position formatting instead of separating each component like the abbreviated style.

formatter.unitsStyle = .positional
formatter.string(from: components) // 9:41

Abbreviated

The .abbreviated style is the shortest style apart from the positional style and abbreviates the components as much as possible.

formatter.unitsStyle = .abbreviated
formatter.string(from: components) // 9h 41m

Short

The .short style uses slightly longer versions of each date component.

formatter.unitsStyle = .short
formatter.string(from: components) // 9hr 41min

Full

The .full style uses the full name for each date component.

formatter.unitsStyle = .full
formatter.string(from: components) // 9 hours, 41 minutes

Spelled Out

The .spellOut style is the longest style and uses spelled out versions of everything.

formatter.unitsStyle = .spellOut
formatter.string(from: components) // Nine hours, forty-one minutes

Formatting Date Intervals

DateIntervalFormatter is similar to the basic DateFormatter, but it displays both a beginning and end date, such as 21-26 July 2019.

To use it, create a formatter and generate a string from two dates:

let formatter = DateIntervalFormatter()

let currentDate = Date()

let twoMinutesAgo = Calendar.current.date(byAdding: .minute, value: -2, to: currentDate) ?? currentDate
formatter.string(from: currentDate, to: twoMinutesAgo) // 21/7/2019, 9:41-9:43 AM

let fiveDaysAway = Calendar.current.date(byAdding: .day, value: 5, to: currentDate) ?? currentDate
formatter.string(from: currentDate, to: fiveDaysAway) // 21/7/2019, 9:41 AM - 26/7/2019, 9:43 AM

Note that by default, the output will be based on the locale and time style from the device preferences, so you might see something different.

Date Styles

DateIntervalFormatter uses the same date styles that the basic date formatter does. Here are examples of each style:

formatter.dateStyle = .none
formatter.string(from: currentDate, to: fiveDaysAway)

formatter.dateStyle = .short
formatter.string(from: currentDate, to: fiveDaysAway) // 21/7/2019-26/7/2019

formatter.dateStyle = .medium
formatter.string(from: currentDate, to: fiveDaysAway) // 21-26 Jul 2019

formatter.dateStyle = .long
formatter.string(from: currentDate, to: fiveDaysAway) // 21-26 July 2019

formatter.dateStyle = .full
formatter.string(from: currentDate, to: fiveDaysAway) // Sunday, 21-Friday, 26 July 2019

Time Styles

DateIntervalFormatter uses the same time styles that the basic date formatter does. Here are examples of each style:

formatter.timeStyle = .none
formatter.string(from: currentDate, to: twoMinutesAgo)

formatter.timeStyle = .short
formatter.string(from: currentDate, to: twoMinutesAgo) // 11:00-11:02 AM

formatter.timeStyle = .medium
formatter.string(from: currentDate, to: twoMinutesAgo) // 11:00:00 AM-11:02:00 AM

formatter.timeStyle = .long
formatter.string(from: currentDate, to: twoMinutesAgo) // 11:00:00 AM GMT+2-11:02:00 AM GMT+2

formatter.timeStyle = .full
formatter.string(from: currentDate, to: twoMinutesAgo) // 11:00:00 AM Central European Summer Time-11:02:00 AM Central European Summer Time

Formatting Relative Dates

Note: RelativeDateTimeFormatter requires Xcode 11 and the latest beta versions of macOS 10.15 or iOS 13, which are currently in beta, as it is not available on previous versions.

RelativeDateTimeFormatter is a new date formatter from WWDC19 that formats relative dates as the amount of time between two dates or according to the current date and time.

To use it, create a formatter and generate a string from two dates.

RelativeDateTimeFormatter supports different date/time and unit styles, which we'll go through later on.

let formatter = RelativeDateTimeFormatter()
formatter.dateTimeStyle = .named
formatter.unitsStyle = .full

let currentDate = Date()
formatter.localizedString(for: currentDate, relativeTo: currentDate) // now

let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: currentDate) ?? currentDate
formatter.localizedString(for: yesterday, relativeTo: currentDate) // yesterday

let fiveDays = Calendar.current.date(byAdding: .day, value: 5, to: currentDate) ?? currentDate
formatter.localizedString(for: fiveDays, relativeTo: currentDate) // in 5 days

You can also use a instance of DateComponents, which will format it based on the current date.

let minusOneDay = DateComponents(day: -1)
formatter.localizedString(from: minusOneDay) // yesterday

let plusOneDay = DateComponents(day: 1)
formatter.localizedString(from: plusOneDay) // tomorrow

As a bonus, all the strings that are generated are already localized, so you don't need to worry about localization.

Date/Time Styles

RelativeDateTimeFormatter supports two different date/time styles, which can be used by setting them before generating the string.

formatter.dateTimeStyle = .numeric // .numeric or .named

Numeric

The .numeric style is the default style and always uses the literal definition of the date.

formatter.dateTimeStyle = .numeric

formatter.localizedString(for: currentDate, relativeTo: currentDate) // in 0 seconds
formatter.localizedString(for: yesterday, relativeTo: currentDate) // 1 day ago
formatter.localizedString(for: fiveDays, relativeTo: currentDate) // in 5 days

Named

The .named style falls back to the numeric style, but when possible, uses relative names such as yesterday or tomorrow.

formatter.dateTimeStyle = .named

formatter.localizedString(for: currentDate, relativeTo: currentDate) // now
formatter.localizedString(for: yesterday, relativeTo: currentDate) // yesterday
formatter.localizedString(for: fiveDays, relativeTo: currentDate) // in 5 days

Unit Styles

RelativeDateTimeFormatter contains four different unit styles, which format the date in different ways.

formatter.unitsStyle = .full // .abbreviated, .short, .full or .spellOut

Abbreviated

The .abbreviated style is the shortest style and abbreviates as much as possible.

formatter.unitsStyle = .abbreviated

let oneMonthAgo = Calendar.current.date(byAdding: .month, value: 1, to: currentDate) ?? currentDate
formatter.localizedString(for: currentDate, relativeTo: oneMonthAgo) // 1 mo. ago

Short

The .short style is identical to the abbreviated style in English, but might generate different strings in other languages.

formatter.unitsStyle = .short

let oneMonthAgo = Calendar.current.date(byAdding: .month, value: 1, to: currentDate) ?? currentDate
formatter.localizedString(for: currentDate, relativeTo: oneMonthAgo) // 1 mo. ago

Full

The .full style is the default style and uses longer names, such as month instead of mo..

formatter.unitsStyle = .full

let oneMonthAgo = Calendar.current.date(byAdding: .month, value: 1, to: currentDate) ?? currentDate
formatter.localizedString(for: currentDate, relativeTo: oneMonthAgo) // 1 month ago

Spelled Out

The .spellOut style is the longest style and uses spelled out versions of everything.

formatter.unitsStyle = .spellOut

let oneMonthAgo = Calendar.current.date(byAdding: .month, value: 1, to: currentDate) ?? currentDate
formatter.localizedString(for: currentDate, relativeTo: oneMonthAgo) // one month ago

Conclusion

In this guide, you learned about the different date formatters that are built into Apple's platforms, and how to use them.

I hope this post was useful and taught you something new. If you have any questions or feedback, feel free to mention me on Twitter or email me: [email protected]. Thanks for reading 📆