In the previous chapter of this tutorial, we looked at four key structs for date and time programming in Swift:
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).Date
, which works in conjunction withDateFormatter
to convert a properly-formatted string into a date and can also convertDate
s into strings. So far, we’ve used only the former capability.Date
provides a context forCalendar
s, and convertsDate
s toDate
andDateComponents
toDateComponents
s.Date
specifies time units like year, month, day, hour, minute, and more to represent either a point in time or a duration of time.DateComponents
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
Date
formatted()
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
formatted()
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
Date
ISO8601Format()
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
Date
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.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 |
|
|
---|---|---|
| [empty string ] | [empty string ] |
| 6/3/19 | 3:08 PM |
| Jun 3, 2019 | 3:08:00 PM |
| June 3, 2019 | 3:08:00 PM EDT |
| Monday, June 3, 2019 | 3:08:00 PM Eastern Daylight Time |
Expressing dates and times in languages other than English
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
Date
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.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
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)Date
- Creating dates using
’s initializers,Date
, andDateFormatter
ISo8601DateFormatter
- Creating known and computed dates using
andDateComponents
Calendar
- Extracting date components from a
usingDate
andDateComponents
Calendar
- Presenting user-friendly date and time strings using
’sDate
method andformatted()
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:
- Dates and Times from Apple’s documentation
- Swift Date by Harish Suthar
- Time Steering in Swift 5.5 by Sai Durga Mahesh
- Why is Programming with Dates So Hard? by Dave Taubler
- Falsehoods programmers believe about time
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.
About the author
Joey deVilla
Senior Developer Advocate