developers

Introduction to Date and Time Programming in Swift, Part 2

Now that you can create dates and times in Swift, learn how to display date and time values to your users.

Mar 30, 202318 min read

In the previous chapter of this tutorial, we looked at four key structs for date and time programming in Swift:

  • Date
    represents a single point in time, using a format that can easily be translated into just about any calendar and time-reckoning system: a number of seconds relative to the start of the Third Millennium (January 1, 2001, 00:00:00 UTC).
  • DateFormatter
    , which works in conjunction with
    Date
    to convert a properly-formatted string into a date and can also convert
    Date
    s into strings. So far, we’ve used only the former capability.
  • Calendar
    provides a context for
    Date
    s, and converts
    Date
    s to
    DateComponents
    and
    DateComponents
    to
    Date
    s.
  • DateComponents
    specifies time units like year, month, day, hour, minute, and more to represent either a point in time or a duration of time.

Up until now, we’ve been working largely with the internal representation of dates and times in Swift. While we have used the

description
property and
description(with:)
method to print a
Date
’s value in a human-readable form to the Xcode console, they’re meant for debugging purposes only. They’re not for presenting date and time information to the user.

In this chapter, we’ll spend our time converting

Dates
into formatted
Strings
using the
Date
’s
formatter()
method and
DateFormmatter
to create strings with date and time information for your users.

👀 Look for the 🛠 emoji if you’d like to skim through the content while focusing on the build and execution steps.

💻 You can find an Xcode playground with all the code in this article in this GitHub repository — it’s

intro-dates-times-swift-2.playground
.

Turning
Date
s into Strings with the
formatted()
Method

If you’re coding in Swift 5.5 and developing apps for iOS 15 and later, the first tool you should reach for when turning

Date
instances into strings is
Date
’s new
formatted()
method. It’s the way to format a
Date
value into a user-friendly
String
that requires the least code.

Getting the date and time in predefined string formats

Let’s start with a key date in Swift history: the day that it was first presented to the world: June 2, 2014.

🛠️ Open Xcode, start a new blank macOS playground (I find them more reliable and less crash-prone than iOS playgrounds), then add and run the following code:

import Cocoa


var userLocale = Locale.autoupdatingCurrent
// We need a calendar instance to add context to `Date` objects,
// which can only tell time as a number of seconds
// before or after January 1, 2001, 00:00:00 UTC
var gregorianCalendar = Calendar(identifier: .gregorian)
gregorianCalendar.locale = userLocale

var swiftDebutComponents = DateComponents(
  year: 2014,
  month: 6,
  day: 2
)
var swiftDebutDate = gregorianCalendar.date(from: swiftDebutComponents)!
print("Swift's debut date: \(swiftDebutDate.formatted())")

The

formatted()
method returns a string containing the date and time value contained within the
Date
. The format of this string is the default date and time format for the system locale. On my system, the output for the code above was
6/2/2014, 12:00 AM
. Your output may look different depending on your locale settings.

formatted()
has a more complex form:
formatted(date:time:)
, which lets you specify the formatting style for the date and time contained within the
Date
. This is one of those cases where it’s better to show
formatted(date:time:)
in action rather than tell you about it...

🛠️ Add the following to the playground and run it:

print("\nJust the date, in all its forms:")
print("1. Complete: \(swiftDebutDate.formatted(date: .complete, time: .omitted))")
print("2. Abbreviated: \(swiftDebutDate.formatted(date: .abbreviated, time: .omitted))")
print("3. Long: \(swiftDebutDate.formatted(date: .long, time: .omitted))")
print("4. Numeric: \(swiftDebutDate.formatted(date: .numeric, time: .omitted))")

print("\nJust the time, in all its forms:")
print("1. Complete: \(swiftDebutDate.formatted(date: .omitted, time: .complete))")
print("2. Shortened: \(swiftDebutDate.formatted(date: .omitted, time: .shortened))")
print("3. Standard: \(swiftDebutDate.formatted(date: .omitted, time: .standard))")

print("\nJust a few date and time combinations:")
print("1. Complete/Complete: \(swiftDebutDate.formatted(date: .complete, time: .complete))")
print("2. Abbreviated/Shortened: \(swiftDebutDate.formatted(date: .abbreviated, time: .shortened))")
print("3. Numeric/Shortened: \(swiftDebutDate.formatted(date: .numeric, time: .shortened))")

Here’s what my system displayed when I ran the code:

Just the date, in all its forms:
1. Complete: Monday, June 2, 2014
2. Abbreviated: Jun 2, 2014
3. Long: June 2, 2014
4. Numeric: 6/2/2014

Just the time, in all its forms:
1. Complete: 12:00:00 AM EDT
2. Shortened: 12:00 AM
3. Standard: 12:00:00 AM

Just a few date and time combinations:
1. Complete/Complete: Monday, June 2, 2014 at 12:00:00 AM EDT
2. Abbreviated/Shortened: Jun 2, 2014 at 12:00 AM
3. Numeric/Shortened: 6/2/2014, 12:00 AM

Getting very specific about
formatted()
’s date and time formats

If what’s above isn’t enough customization for you, there’s a variant of the

formatted()
method that takes a
Date.FormatStyle
argument, which allows for even finer control over the formatted date string. Once again, this is a case where it’s better to show you the code and its output rather than explain what it does.

🛠️ Add this code to the playground and run it:

let fancyDateString = swiftDebutDate.formatted(
    Date.FormatStyle()
        .year(.twoDigits)
        .month(.wide)
        .day(.twoDigits)
)
print("\n• Fancy date string: \(fancyDateString)")

let superFancyDateString = swiftDebutDate.formatted(
    .dateTime // The `dateTime` property returns a `Date.FormatStyle` object
        .year(.defaultDigits)
        .month(.abbreviated)
        .day(.twoDigits)
        .hour(.defaultDigits(amPM: .abbreviated))
        .minute(.twoDigits)
        .timeZone(.identifier(.long))
        .era(.wide)
        .dayOfYear(.defaultDigits)
        .weekday(.abbreviated)
        .week(.defaultDigits)
)
print("• Super fancy date string: \(superFancyDateString)")

On my system, this code produces the following output:

• Fancy date string: June 02, 14
• Super fancy date string: Mon, Jun 02, 2014 Anno Domini (week: 23) at 12:00 AM America/New_York

You should experiment with the different parameters for each part of the date to find the precise result you’re looking for.

Turning
Date
s into ISO8601 Strings with the
ISO8601Format()
method

If you need to convert

Date
s to ISO 8601 strings — perhaps you need to provide a date parameter for an API call, you’ll appreciate the
ISO8601Format()
method. Like
Date
’s
formatted()
method,
ISO8601Format()
Here’s another method introduced in Swift 5.5 for developing apps targeting iOS 15 and later.

🛠️ Add this code to the playground and run it:

print("\nDates to ISO8601 strings:")
print("• ISO 8601 format for Swift’s debut date: \(swiftDebutDate.ISO8601Format()).")
swiftDebutDate.ISO8601Format()

You should see this output:

Dates to ISO8601 strings:
• ISO 8601 format for Swift’s debut date: 2014-06-02T04:00:00Z.

Like the

formatted()
method,
ISO8601Format()
has a variant that takes a parameter that allows for even finer control over the output string. It takes a Date.ISO8601FormatStyle argument in a way similar to the way
formatted()
takes a
Date.FormatStyle
argument.

🛠️ Let’s see an example of this fancier

ISO8601Format()
in action. Add this code to the playground and run it:

let customizedISO8601DebutDate = swiftDebutDate.ISO8601Format(
    .iso8601
    .weekOfYear()     // Include week of the year, followed by “W”
    .year()           // Include year, omit month and day
    // .month()       // Uncomment to include the month
    // .day()         // Uncomment to include the day
    .dateSeparator(.omitted) // `.omitted` parameter removes data separator character
    .time(includingFractionalSeconds: true) // Include time and display fractions of seconds
    .timeSeparator(.colon) // `.colon` includes hour/minute separator
)
print("• Customized ISO 8601 format for Swift’s debut date: \(customizedISO8601DebutDate).")

As with

formatted()
, you should experiment with
ISO8601Format
’s parameters.

Turning
Date
s into Strings with
DateFormatter

Prior to Swift 5.5 and iOS 15, you used

DateFormatter
to convert
Date
s into strings. While
Date
’s
formatter()
method means that you probably won’t use
DateFormatter
as often,
DateFormatter
still gives you the most control over the format of date and time strings. You’ll also want to be familiar with
DateFormatter
if you have to read or revise older Swift code.

Diagram showing how a DateFormatter turns Dates into Strings

Turning just the date into a string

Let’s turn

swiftDebutDate
into a string using a
DateFormatter
.

🛠️ Add the following to the playground and run it:

print("\nDates to strings:")

var myFormatter = DateFormatter()
print("• Swift’s debut date, via the DateFormatter: \(myFormatter.string(from: swiftDebutDate))")

Here’s the output:

Dates to strings:
• Swift’s debut date, via the DateFormatter:

You may be surprised that the result is an empty string. That’s because you need to specify a

dateStyle
, which specifies which pre-defined format should be used for the date. We’ll start with the
short
style.

🛠️ Add this code to the playground and run it:

myFormatter.dateStyle = .short
print("• Swift’s debut date, “short” style: \(myFormatter.string(from: swiftDebutDate)).")

Here’s the output on my system:

• Swift’s debut date, “short” style: 6/2/14.

Let’s try the other styles: medium, long, full, and none.

🛠️ Add the following to the playground, then run it:

myFormatter.dateStyle = .medium
print("• Swift’s debut date, “medium” style: \(myFormatter.string(from: swiftDebutDate))")
myFormatter.dateStyle = .long
print("• Swift’s debut date, “long” style: \(myFormatter.string(from: swiftDebutDate))")
myFormatter.dateStyle = .full
print("• Swift’s debut date, “full” style: \(myFormatter.string(from: swiftDebutDate))")
myFormatter.dateStyle = .none
print("• Swift’s debut date, “none” style: \(myFormatter.string(from: swiftDebutDate))")

You’ll see the following output:

• Swift’s debut date, “medium” style: 6/2/14
• Swift’s debut date, “long” style: June 2, 2014
• Swift’s debut date, “full” style: Monday, June 2, 2014
• Swift’s debut date, “none” style:

The default

dateStyle
is
none
, which is
DateFormatter
’s equivalent of the
.omitted
parameter in
Date
’s
formatted(date:time)
method.

Turning a date and time into a string

Let’s create a date and approximate known time. When SwiftUI was announced at WWDC 2019, it was 2 hours and 8 minutes into the keynote. Since the keynote started at 10:00 a.m. Pacific Daylight Time, we’ll say that SwiftUI’s debute happened at 12:08 p.m. PDT on that day, June 3, 2019.

🛠️ Add this code to the playground, then run it:

print("\nThe Swift debut date and time, converted into a string:")

let swiftUIDebutDateComponents = DateComponents(
  timeZone: TimeZone(abbreviation: "PDT"),
  year: 2019,
  month: 6,
  day: 3,
  hour: 12,
  minute: 8
)
var swiftUIDebutDate = gregorianCalendar.date(from: swiftUIDebutDateComponents)!
print("• The newly-created date: \(swiftUIDebutDate.description(with: userLocale)).")

Here’s the output on my system:

The Swift debut date and time, converted into a string:
• The newly-created date: Monday, June 3, 2019 at 3:08:00 PM Eastern Daylight Time.

The date and time you’ll see will be determined your system calendar settings.

Now that we have a date and time, let’s format it using the

dateStyle
property to style the date part, and
timeStyle
property to style the time part.

🛠️ Add the following to the playground, then run it:

myFormatter.dateStyle = .short
myFormatter.timeStyle = .short
print("• Swift’s debut date and time, “short” style: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateStyle = .medium
myFormatter.timeStyle = .medium
print("• Swift’s debut date and time, “medium” style: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateStyle = .long
myFormatter.timeStyle = .long
print("• Swift’s debut date and time, “long” style: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateStyle = .full
myFormatter.timeStyle = .full
print("• Swift’s debut date and time, “full” style: \(myFormatter.string(from: swiftUIDebutDate)).")

Here’s the output on my system:

• Swift’s debut date and time, “short” style: 6/3/19, 3:08 PM.
• Swift’s debut date and time, “medium” style: Jun 3, 2019 at 3:08:00 PM.
• Swift’s debut date and time, “long” style: June 3, 2019 at 3:08:00 PM EDT.
• Swift’s debut date and time, “full” style: Monday, June 3, 2019 at 3:08:00 PM Eastern Daylight Time.

Just as you can mix and match date and time styles with

Date
’s
formatted()
method, you can also mix and match
dateStyle
and
timeStyle
settings.

🛠️ Add this code to the playground, then run it:

myFormatter.dateStyle = .full
myFormatter.timeStyle = .short
print("• Swift’s debut date and time, with “full” style date and “short” style time: \(myFormatter.string(from: swiftUIDebutDate)).")

Here’s the output on my system:

• Swift’s debut date and time, with “full” style date and “short” style time: Monday, June 3, 2019 at 3:08 PM.

The

.none
style lets you suppress the display of the date or time in a formatted date string.

🛠️ Add the following to the playground, then run it:

// Show only the time
myFormatter.dateStyle = .none
myFormatter.timeStyle = .medium
print("• Swift’s debut time: \(myFormatter.string(from: swiftUIDebutDate)).")

// Show only the date
myFormatter.dateStyle = .full
myFormatter.timeStyle = .none
print("• Swift’s debut date: \(myFormatter.string(from: swiftUIDebutDate)).")

Here’s the output on my system:

• Swift’s debut time: 3:08:00 PM.
• Swift’s debut date: Monday, June 3, 2019.

This table summarizes the different

dateStyle
and
timeStyle
settings for the US English language setting:

Setting
dateStyle
timeStyle
.none
[empty string ][empty string ]
.short
6/3/193:08 PM
.medium
Jun 3, 20193:08:00 PM
.long
June 3, 20193:08:00 PM EDT
.full
Monday, June 3, 20193:08:00 PM Eastern Daylight Time

Expressing dates and times in languages other than English

Cartoon globe with "Hello" in many languages

DateFormatter
defaults to the user’s preferred language, as specified in their settings. In my case, that’s US English. By setting
DateFormatter
’s
locale
property, you can specify the language for your formatted date strings.

🛠️ Add this code to the playground, then run it:

print("\nDates and times in languages other than English")

// We want to see as much of these languages as possible,
// so let’s set both dateStyle and timeStyle to .full.
myFormatter.dateStyle = .full
myFormatter.timeStyle = .full

myFormatter.locale = Locale(identifier: "fr")
print("• International French: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.locale = Locale(identifier: "fr-CA")
print("• Canadian French: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.locale = Locale(identifier: "hr")
print("• Croatian: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.locale = Locale(identifier: "ko_KR")
print("• Korean: \(myFormatter.string(from: swiftUIDebutDate)).")

The output should look like this:

Dates and times in languages other than English
• International French: lundi 3 juin 2019 à 15:08:00 heure d’été de l’Est.
• Canadian French: lundi 3 juin 2019 à 15:08:00 heure avancée de l’Est.
• Croatian: ponedjeljak, 3. lipnja 2019. u 15:08:00 (istočno ljetno vrijeme).
• Korean: 2019년 6월 3일 월요일 오후 3시 8분 0초 미 동부 하계 표준시.

Expressing dates and times in custom formats

In addition to the built-in formats for dates, you can use

DateFormatter
to create date and time strings in your own custom formats.

Before we begin working with custom date/time formats, I should point out that if you need to present a

Date
as a
String
to the user, it’s best if you use the built-in
dateStyle
and
timeStyle
values. They format dates and times properly, according to the user’s settings, which include country and language. You’d be surprised how date formats differ from culture to culture, and it’s often better to let Swift do the formatting work.

However, there are times when you need to format dates and times in away that doesn’t match any of the styles provided by

DateFormatter
’s
dateStyle
and
timeStyle
properties, such as when dealing with certain APIs (especially older ones). That’s where
DateFormatter
’s
dateFormat
property comes in handy.

To be certain that your

DateFormatter
instance will use your custom date format, set its locale property to POSIX, then define the custom date format string in
dateFormat
.

🛠️ Add the following to the playground, then run it:

print("\nDates and times in custom formats")

// Setting the locale to POSIX ensures that the user's locale
// won't be used to format the Date.
myFormatter.locale = Locale(identifier: "en_US_POSIX")

// DateFormatter's format string uses the date format specifiers
// spelled out in Unicode Technical Standard #35 (located at
// http://www.unicode.org/reports/tr35/tr35-25.html#Date_Format_Patterns)
myFormatter.dateFormat = "y-MM-dd"
print("• y-MM-dd format: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateFormat = "MM/dd/yy"
print("• MM/dd/yy format: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateFormat = "MMM dd, yyyy"
print("• MMM dd, yyyy format: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateFormat = "EEEE, MMMM dd, yyyy' at 'h:mm a zzzz"
print("• EEEE, MMMM dd, yyyy' at 'h:mm a zzzz format: \(myFormatter.string(from: swiftUIDebutDate)).")

// You can get really experimental with formatting strings.
// (Don’t know what “Zero Wing” is? See this article:
// https://tvtropes.org/pmwiki/pmwiki.php/VideoGame/ZeroWing
myFormatter.dateFormat = "'🚀 In' G y', SwiftUI was beginning. All your UI are belong to us! 🚀"
print("• Zero Wing format: \(myFormatter.string(from: swiftUIDebutDate)).")

Here’s what my system displayed when I ran the code:

Dates and times in custom formats
• y-MM-dd format: 2019-06-03.
• MM/dd/yy format: 06/03/19.
• MMM dd, yyyy format: Jun 03, 2019.
• EEEE, MMMM dd, yyyy' at 'h:mm a zzzz format: Monday, June 03, 2019 at 3:08 PM Eastern Daylight Time.
• Zero Wing format: 🚀 In AD 2019, SwiftUI was beginning. All your UI are belong to us! 🚀.

Turning Strings into
Date
s with
DateFormatter

In the previous chapter of this tutorial, we briefly looked at using

DateFormatter
(and its cousin,
ISO8601DateFormatter
) to convert date and time strings into
Date
instances. Let’s take another look at this use case.

Diagram showing how a DateFormatter turns String into Dates

By setting a

DateFormatter
instance’s
dateFormat
property to the format of the date and time string it should expect, you can use its
date(from:)
method to convert it into a
Date
. Once again, you’ll want to consult the Unicode Technical Standard for formatting strings for the
dateFormat
property.

🛠️ Add this code to the playground, then run it:

print("\nTurning strings into Dates with DateFormatter")

// Let’s define the format for date strings we want to parse:
myFormatter.dateFormat = "yyyy/MM/dd hh:mm Z"

// Here's a date in the specified format.
// DateFormatter’s date(from:) method will be able to parse it.
let newDate1 = myFormatter.date(from: "2019/06/03 12:08 -0700")
print("• newDate1’s value is: \(newDate1?.description ?? "nil").")

// And here's the same date, but in a different format.
// The formatter won’t be able to convert it into a date.
let newDate2 = myFormatter.date(from: "Jun 6, 2019, 12:08 PM PDT")
print("• newDate2’s value is: \(newDate2?.description ?? "nil").")

The output should look like this:

Turning strings into Dates with DateFormatter
• newDate1’s value is: 2019-06-03 07:08:00 +0000.
• newDate2’s value is: nil.

Let’s change the

dateFormat
string and try it again.

🛠️ Add the following to the playground, then run it:

// Let's change the date format strings and try
// date(from:) with the same two strings:
myFormatter.dateFormat = "MMM d, yyyy, hh:mm a zz"

let newDate3 = myFormatter.date(from: "2019/06/03 12:08 -0700")
print("• newDate3’s value is: \(newDate3?.description ?? "nil").")

let newDate4 = myFormatter.date(from: "Jun 6, 2019, 12:08 PM PDT")
print("• newDate4’s value is: \(newDate4?.description ?? "nil").")

The output should look like this:

• newDate3’s value is: nil.
• newDate4’s value is: 2019-06-06 19:08:00 +0000.

If you’re trying to parse an unconventional date format, use a custom date format string.

🛠️ Add this code to the playground, then run it:

// Don’t forget that you can get unconventional if you expect to
// parse dates in unconventional formats!
// (D is the format string for “day of year”, which can be 1...366)
myFormatter.dateFormat = "y 😍 D"
let emojiDate = myFormatter.date(from: "2024 😍 333")
print("• emojiDate’s value is: \(emojiDate?.description ?? "nil").")

The output should look like this:

• emojiDate’s value is: 2024-11-28 05:00:00 +0000.

The Big Challenge, Once Again

In the previous chapter, we took on the challenge of creating the date for the third Wednesday of July at 3:30 p.m. Pacific Time and then display it as it would appear in the Coptic calendar system in Melbourne, Australia’s time zone. We had to do it in a roundabout way based on

DateComponents
. Using
DateFormatter
, we can do it more elegantly.

🛠️ Add the following to the playground, then run it:

print("\nThe Big Challenge, once again")

// First, create the the date —
// third Wednesday of July at 3:30 p.m. Pacific Time
let challengeDateComponents = DateComponents(
    calendar: gregorianCalendar,
    timeZone: TimeZone(identifier: "America/Los_Angeles")!,
    year: 2023,
    month: 7,
    hour: 15,
    minute: 30,
    weekday: 4,
    weekdayOrdinal: 3
)
let challengeDate = gregorianCalendar.date(from: challengeDateComponents)!
print("• The challenge date in the Gregorian calendar and the US Pacific time zone is \(challengeDate.description(with: userLocale)).")

// Then, we’ll need a Coptic calendar set Melbourne, Australia’s time zone:
var copticCalendar = Calendar(identifier: .coptic)
copticCalendar.locale = Locale(identifier: "ar_EG")
copticCalendar.timeZone = TimeZone(identifier: "Australia/Melbourne")!

// And finally, we’ll use a `DateFormatter` to display the date
// in the desired format
myFormatter.calendar = copticCalendar
myFormatter.timeZone = copticCalendar.timeZone
myFormatter.dateStyle = .full
myFormatter.timeStyle = .full
print("• The challenge date is \(myFormatter.string(from: challengeDate)).")

Here’s the output:

The Big Challenge, once again
• The challenge date in the Gregorian calendar and the US Pacific time zone is Wednesday, July 19, 2023 at 6:30:00 PM Eastern Daylight Time.
• The challenge date is Thursday, Epep 13, 1739 ERA1 at 8:30:00 AM Australian Eastern Standard Time.

Summary

💻 Once again, you can find an Xcode playground with all the code in this article in this GitHub repository — it’s

intro-dates-times-swift-2.playground
.

In this tutorial, we covered the basics of working with dates and times in Swift. We looked at:

  • The
    Date
    struct, which represents a single point in time, expressed in seconds before or after the start of the Third Millennium (January 1, 2001, 00:00:00 UTC)
  • Creating dates using
    Date
    ’s initializers,
    DateFormatter
    , and
    ISo8601DateFormatter
  • Creating known and computed dates using
    DateComponents
    and
    Calendar
  • Extracting date components from a
    Date
    using
    DateComponents
    and
    Calendar
  • Presenting user-friendly date and time strings using
    Date
    ’s
    formatted()
    method and
    DateFormatter
  • Computing a date in one calendar system and displaying that date in another calendar system in another time zone

We’ve only covered a small part of date and time programming in Swift. I recommend that you consult these other pages to expand your knowledge:

I’ll continue this series on date and time programming in Swift. The next tutorial will cover date calculations and writing extensions to simplify date calculations.