Updated: December 2021
Overview
Since its introduction to the Apple development platform in 2014 (announced at WWDC 2014), Swift continues to mature rapidly due to its open development community (swift.org). As such, Swift code is clean code with clear consistent syntax and strongly typed, compiles to a fast executable, and includes many advanced design and coding principles. It is truly a great modern programming language!
Alas, developing code for real-world apps is hard and still a humbling experience. This post focuses on the insanely complex rabbit hole known as date and time and its implementation in Swift. Some of the complexity of getting date and time correct is humorously presented by Tom Scott in the following video.
An enlightening article by Dave Delong (Your Calendrical Fallacy Is …) lists some of the false assumptions regarding date and time including:
As referenced in the video, updates to the Timezone Database still occur. The recent updates (Jan 31, 2018) to the Timezone Database are highlighted in an article by Dave Delong (The 2018c Timezone Datebase Update). Ideally, time zones would be equally spaced around the earth every 15 degrees in longitude. Not even close! The following world map of current timezones highlights this complexity.
And, finally, the overall weirdness of calendars, dates and times is examined philosophically by Dave Delong (Intercalation).
Bottom-line: Use the blackbox approach. Always use Calendar, Date, DateFormatter, Locale, and DateComponents), which are all built on top of the ICU (International Components for Unicode) libraries.
The software examples in the next several sections are included as an Xcode playground located on GitHub.
Basic Date and Time
In Swift, a Date() value encapsulates a single point in time, independent of any particular calendrical system or time zone. To accomplish this, a Date() value is stored as a Double (64-bit floating point number) counting the number of seconds as a relative offset from the reference date of January 1, 2001 at 00:00:00 UTC (Coordinated Universal Time).
So let’s begin with getting the current date/time and printing out the default result as an ISO8061-formatted string referenced to UTC.
let now = Date() // current date and time print(now) // e.g., 2021-10-05 18:17:50 +0000
To see the actual number of seconds relative to Apple’s reference date (January 1, 2001 at 00:00:00 UTC), the code needs to be more specific. The fractional part of the result represents the fractional part of a second.
let now = Date() // current date/time (e.g., 2021-10-05 18:17:50 +0000) let dateRef = now.timeIntervalSinceReferenceDate print(dateRef) // number of seconds since reference date (e.g., 655150670.057823)
To see the date given an integer (e.g., truncated to one second resolution) representing the number of seconds relative to Apple’s reference date (January 1, 2001 at 00:00:00 UTC), the code to convert to a date is still straightforward.
let seconds: Int = 661573661 // integer example with resolution of 1 sec let timeSeconds = TimeInterval(seconds) let date = Date(timeIntervalSinceReferenceDate: timeSeconds) print("date: \(date)") // 2021-12-19 02:27:41 +0000
Note to Unix users: To see the actual number of seconds relative to the Unix’s reference data known as ‘epoch date’ (January 1, 1970 at 00:00:00 UTC), the code needs to be specific with use of the ‘timeIntervalSince1970’ modifier.
let now = Date() // current date/time (e.g., 2021-10-05 18:17:50 +0000) let dateEpoch = now.timeIntervalSince1970 print(dateEpoch) // number of seconds since epoch data (e.g., 1633457870.057823)
Standard Date and Time Formatting
However, the real feature-rich functionality benefit in Swift comes from using DateFormatter, Locale, and Calendar. This is how Swift supports timezones, localized date formats, and calendar arithmetic (e.g., 1 month ago, 5 days later). To use DateFormatter, we need to create a DateFormatter object. Then, we can specify various date and time formatting options as shown in the examples below (based on en_US in Seattle).
The above options provide commonly used combinations of date and time formats. We can access just date only (timeStyle set to .none) or time only (dateStyle set to .none) as the following examples show (based on en_US in Seattle).
myDateFormat.dateStyle = .medium myDateFormat.timeStyle = .medium print(myDateFormat.string(from: now)) // date/time, both medium format (e.g., Oct 5, 2021 at 11:45:20 AM) myDateFormat.dateStyle = .long myDateFormat.timeStyle = .none print(myDateFormat.string(from: now)) // date with long format (e.g., October 5, 2021) myDateFormat.dateStyle = .none myDateFormat.timeStyle = .short print(myDateFormat.string(from: now)) // time with short format (e.g., 11:45 AM)
Localization of Date and Time
Now for the cool part. We can create a Locale object that will auto-magically localize the date and time formatted results.
let myDateFormat = DateFormatter() let now = Date() myDateFormat.dateStyle = .medium myDateFormat.timeStyle = .none myDateFormat.locale = Locale(identifier: "en_US") print(myDateFormat.string(from: Date())) // Oct 5, 2021 myDateFormat.locale = Locale(identifier: "fr_FR") print(myDateFormat.string(from: Date())) // 5 oct. 2021 myDateFormat.locale = Locale(identifier: "ja_JP") print(myDateFormat.string(from: Date())) // 2021/10/05
In iOS, localization changes will cause an app restart. Typically, the DateFormatter will re-instantiate with an updated locale. However, we can do better and ensure auto-updating by adding the following code:
let myDateFormat = DateFormatter() let now = Date() myDateFormat.dateStyle = .medium myDateFormat.timeStyle = .none myDateFormat.locale = Locale.autoupdatingCurrent
Custom Date and Time Formatting
In addition to the standard date and time formats associated with DateFormatter, custom date and time formats are entirely possible with much flexibility.
let myDateFormat = DateFormatter() let now = Date() myDateFormat.dateFormat = "yyyy-MM-dd HH:mm:ss" print(myDateFormat.string(from: now)) // 2021-10-05 13:05:35 myDateFormat.dateFormat = "MM/dd/yy h:mm a" print(myDateFormat.string(from: now)) // 10/05/21 1:05 PM
An extensive suite of iOS Swift custom formatting options is available at NSDataFormatter.
SwiftUI Date and Time
Beginning with iOS15, we get some simplified date/time formats without the need for a date formatter object. And we still have the existing date formatter and all its options.
let now = Date() print(now.formatted()) // 10/5/2021, 3:46 PM print(now.formatted(date: .complete, time: .omitted)) // Tuesday, October 5, 2021 print(now.formatted(.iso8601.dateSeparator(.dash))) // 2021-10-05T22:46:58Z print(now.formatted(.dateTime.year().month())) // Oct 2021 print(now.formatted(.dateTime.year(.twoDigits).month(.wide))) // October 21 print(now.formatted(.dateTime.locale(Locale(identifier: "cs")))) // 5. 10. 2021 15:46
Dates and Codable
Matching date/time string formats when coding with ISO8601 data is different depending on whether the date/time string format is in whole seconds or includes fractional seconds (i.e., milliseconds).
let isoFormatter = ISO8601DateFormatter() // Example 1: Using ISO8601 date formatter with date string that ends with whole seconds let dateStringSeconds = "2021-10-06T11:27:55Z" let newDateSec = isoFormatter.date(from: dateStringSeconds) print(String(describing: newDateSec)) // 2021-10-06 11:27:55 +0000 // Example 2: Using ISO8601 date formatter with date string that ends with milliseconds let dateStringMilliSeconds = "2021-10-06T11:27:55.341Z" isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] let newDateMilli = isoFormatter.date(from: dateStringMilliSeconds) print(String(describing: newDateMilli)) // 2021-10-06 11:27:55 +0000
Summary:
After spending some time learning about handling date and time in Swift and the Xcode development environment, the functionality that I needed for an app under development is relatively clean. I am especially pleased with the localization benefit with using Locale. It is important to always test both functionality and performance. When using DateFormatter, the recommendation for better performance is to create a single instance of DateFormatter. And, iOS 15 brings simplified date and time formatting to SwiftUI.
General References on Date and Time:
Dealing With Dates by Richard Turton.
Keeping Dates Local by William Boles.
Sneaky Date Formatters Exposing More Than You Think by William Boles.
The 2018c Timezone Datebase Update by Dave DeLong.
Intercalation by Dave DeLong.
Your Calendrical Fallacy Is … by Dave DeLong.
Coordinated Universal Time – Wikipedia.
World Timezone Map (Image) – Wikipedia.
Coding References on Date and Time:
How to display date and time in SwiftUI by Benoit Pasquier
Formatter by Mattt Thompson
Computing dates in Swift by John Sundell
Codable Cheat Sheet by Paul Hudson
Working with Dates by Paul Hudson
How to convert dates and times to a string using DataFormatter by Paul Hudson
New Formatters in iOS 15: Why do we need another formatter by Sarun Wongpatcharapakorn
How expensive is DateFormatter by Sarun Wongpatcharapakorn
How to use DateFormatter in Swift by Sarun Wongpatcharapakorn
NSDateFormattert by Ben Scheirman
Dates and times in Swift 5, part 1: Creating and deconstructing dates and times by Joey deVilla
Apple Developer Documentation: Calendar
Apple Developer Documentation: DateComponents
Credits
The Xcode logo is a copyright and trademark of Apple.