In this new series on Swift code topics, descriptions of various basic features of the Swift programming language will be explored with examples of code. I use Xcode playgrounds for most of my prototype code development since that platform fosters exploration. The code examples used in this post have been uploaded to GitHub.

Since its introduction to the Apple development platform at WWDC 2014, Swift continues to mature rapidly due to its open development community (swift.org). As such, Swift code is cleaner to write, is strongly typed, compiles to a fast executable, and includes many safe design principles.

Coding for real-world apps is a humbling experience. This post looks at the insanely complex rabbit hole known as date and time. 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:

  • Are days always 24 hours long?
  • Does an hour ever occur twice in a single day?
  • Does every day have a midnight?
  • Are days in a work week always contiguous?
  • Does a year always consist of 12 months?
  • Does a leap year always occur every 4 years except when the year is evenly divisible by 100, unless it is also divisible by 400?
  • Does each year always have just one New Year’s day?
  • Are timezones always on the hour mark?
  • Do the rules for Daylight Saving Time change?

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

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 and its cohorts (e.g., Date, DateFormatter, Locale, and DateComponents), which are all built on top of the ICU (International Components for Unicode) libraries.

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 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. Note: UTC stands for Coordinated Universal Time.

So let’s begin with setting a variable to the current date/time (as a 64-bit floating point number typecast as a Date structure) and printing out the result. The default date/time output is printed as an ISO8061-formatted string referenced to UTC.

let now = Date()
print("current date/time: \(now)")
// current date/time: 2019-09-03 21:29:17 +0000

To see the actual number of seconds relative to the 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 syncTimeStamp = Date().timeIntervalSince1970
print("number of seconds since ref date: \(syncTimeStamp)")
// number of seconds since ref date: 1567546157.970784

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. This allows us to specify a custom format, such as the example shown below (based on en_US in Seattle). In addition, the current timezone can be accessed.

// create a DateFormatter object to format date/time
let myDateFormat = DateFormatter()

// custom date/time format example
myDateFormat.dateFormat = "yyyy-MM-dd HH:mm:ss"
print(myDateFormat.string(from: now))
// 2019-09-03 14:29:17

// to confirm current timezone
print(myDateFormat.timeZone!)
// America/Los_Angeles (current)

For example, the dateStyle and timeStyle options include the following with example results (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).

// date & time styles format example
myDateFormat.dateStyle = .long
myDateFormat.timeStyle = .short
print(myDateFormat.string(from: now))
// September 3, 2019 at 2:29 PM

// date & time styles format example
myDateFormat.dateStyle = .medium
myDateFormat.timeStyle = .none
print(myDateFormat.string(from: now))
// Sep 3, 2019

Now for the cool part: localization of date and time! We create a Locale object that will use localization of date and time formats in the results.

// localization of date and time using Locale
// english, US
myDateFormat.locale = Locale(identifier: "en_US")
print(myDateFormat.string(from: Date()))
// Sep 3, 2019

// french, France
myDateFormat.locale = Locale(identifier: "fr_FR")
print(myDateFormat.string(from: Date()))
// 3 sept. 2019

// Japanese, Japan
myDateFormat.locale = Locale(identifier: "ja_JP")
print(myDateFormat.string(from: Date()))
// 2019/09/03

Let’s assume an app being developed needs to provide a range of dates (i.e., ‘from’ and ‘to’ date fields in the format ‘yyyy-mm-dd’) to a backend server api in order to retrieve a history of items (e.g., orders, financial transactions). Since there will be date calculations, the Calendar object provides the functionality cleanly. As an example, select the ‘from’ date to be 3 months ago and the ‘to’ date to be 2 days ahead. This example is shown in the sample code below:

// custom use as from/to dates in a range
// from date should be: current - 3 months
// to date should be: current + 2 days
// format should be yyyy-mm-dd
myDateFormat.locale = Locale(identifier: "en_US_POSIX")
myDateFormat.dateFormat = "y-MM-dd"

if let fromDate = Calendar.current.date(byAdding: .month, value: -3, to: now) {
    let formattedFromDate = myDateFormat.string(from: fromDate)
    print("fromDate: \(formattedFromDate)")
}
if let toDate = Calendar.current.date(byAdding: .day, value: 2, to: now) {
    let formattedToDate = myDateFormat.string(from: toDate)
    print("toDate: \(formattedToDate)")
}
// fromDate: 2019-06-03
// toDate: 2019-09-05

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. Since the references pointed to a performance issue regarding creating and initializing the DateFormatter, the recommendation is to create a single instance of DateFormatter and reuse like I have done with these Swift playground examples.

Reference Information:

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.

Calendar

DateComponents

Parsing and Formatting Dates in Swift

Locale by Mattt Thompson

TimeInterval, Date, and DateInterval by Mattt Thompson

Formatter by Mattt Thompson

Dates by Rick Maddy

Computing dates in Swift by John Sundell

Credits

The Xcode logo is a copyright and trademark of Apple.
The CocoaPods logo is a copyright and trademark of CocoaPods.