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 withDate
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.Calendar
provides a context forDate
s, and convertsDate
s toDateComponents
andDateComponents
toDate
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.
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 |
---|---|---|
. | [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
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
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
, andISo8601DateFormatter
- Creating known and computed dates using
DateComponents
andCalendar
- Extracting date components from a
Date
usingDateComponents
andCalendar
- Presenting user-friendly date and time strings using
Date
’sformatted()
method andDateFormatter
- 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.