close icon
iOS

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.

Last Updated On: December 21, 2023

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 Dates into strings. So far, we’ve used only the former capability.
  • Calendar provides a context for Dates, and converts Dates to DateComponents and DateComponents to Dates.
  • 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 Dates 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 Dates into ISO8601 Strings with the ISO8601Format() method

If you need to convert Dates 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 Dates into Strings with DateFormatter

Prior to Swift 5.5 and iOS 15, you used DateFormatter to convert Dates 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:

SettingdateStyletimeStyle
.[empty string ][empty string ]
.6/3/193:08 PM
.Jun 3, 20193:08:00 PM
.June 3, 20193:08:00 PM EDT
.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: 201963일 월요일 오후 380초 미 동부 하계 표준시.

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 Dates 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.

  • Twitter icon
  • LinkedIn icon
  • Faceboook icon