Formatting a time interval in Swift is trivial thanks to Foundation's DateComponentsFormatter class. While it can do more than formatting time intervals this episode shows you how to use the DateComponentsFormatter class to convert a TimeInterval object to a String object.

From Time Interval to String

Fire up Xcode and create a playground by choosing the Blank template from the iOS > Playground section.

Formatting a Time Interval in Swift

The DateComponentsFormatter class is defined in the Foundation framework. Clear the contents of the playground and add an import statement for Foundation.

import Foundation

We first define an array of time intervals. We convert the TimeInterval objects to String objects in a few moments using a date components formatter.

import Foundation

let timeIntervals: [TimeInterval] = [
    0.0,
    100.0,
    600.0,
    10_450.0,
    100_844.0
]

To format the time intervals, we create an instance of the DateComponentsFormatter class. It is available on iOS 8.0 and later, tvOS 9.0 and later, macOS 10.10 and later, and watchOS 2.0 and later. The initializer accepts no arguments.

import Foundation

let timeIntervals: [TimeInterval] = [
    0.0,
    100.0,
    600.0,
    10_450.0,
    100_844.0
]

let formatter = DateComponentsFormatter()

We call string(from:) on the date components formatter to convert the time interval to a string, passing in the TimeInterval object as the only argument. We use a for loop to convert every time interval of timeIntervals to a string. We print the result to the console. The return type of the string(from:) method is String?. Since we are working in a playground, we use the exclamation mark to forced unwrap the result. I don't recommend forced unwrapping the result in production.

import Foundation

let timeIntervals: [TimeInterval] = [
    0.0,
    100.0,
    600.0,
    10_450.0,
    100_844.0
]

let formatter = DateComponentsFormatter()

for timeInterval in timeIntervals {
    print(formatter.string(from: timeInterval)!)
}

The output in Xcode's console shows us the default formatting behavior of a DateComponentsFormatter instance.

0
1:40
10:00
2:54:10
1d 4:00:44

Configuring a Date Components Formatter

Modifying the behavior of the date components formatter is easy. The DateComponentsFormatter class defines a concise API to configure its behavior. To define which units are allowed in the resulting string, we set the allowedUnits property. In this example, we instruct the date components formatter to only include hours and minutes.

let formatter = DateComponentsFormatter()

formatter.allowedUnits = [.hour, .minute]

Take a look at the result. The allowedUnits property is a bitmask of type NSCalendar.Unit. The possible values are year, month, weekOfMonth, day, hour, minute, and second.

0
1
10
2:54
28:00

We can configure the units style of the date components formatter by setting its unitsStyle property. The default and most concise units style is positional. Let's make the output more verbose by setting the units style of the date components formatter to spellOut. As the name suggests, the resulting string is spelled out word for word.

let formatter = DateComponentsFormatter()

formatter.unitsStyle = .spellOut
formatter.allowedUnits = [.hour, .minute]
zero minutes
one minute
ten minutes
two hours, fifty-four minutes
twenty-eight hours

Before we move on, we set the unitsStyle property to abbreviated.

let formatter = DateComponentsFormatter()

formatter.unitsStyle = .abbreviated
formatter.allowedUnits = [.hour, .minute]
0m
1m
10m
2h 54m
28h

Another useful property of the DateComponentsFormatter class is zeroFormattingBehavior. As the name suggests, this property defines the formatting behavior for units that are equal to 0. There are a number of possibilities. The option I often use is pad.

let formatter = DateComponentsFormatter()

formatter.unitsStyle = .abbreviated
formatter.zeroFormattingBehavior = .pad
formatter.allowedUnits = [.hour, .minute]
0h 0m
0h 1m
0h 10m
2h 54m
28h 0m

Let's add day and second to the allowed units and set zeroFormattingBehavior to dropAll. As you might have guessed, the dropAll zero formatting behavior excludes units that are equal to 0.

let formatter = DateComponentsFormatter()

formatter.unitsStyle = .abbreviated
formatter.zeroFormattingBehavior = .dropAll
formatter.allowedUnits = [.day, .hour, .minute, .second]
0s
1m 40s
10m
2h 54m 10s
1d 4h 44s

Let's end this episode with the maximumUnitCount property. As the name suggests, it defines the number of units the resulting string includes. Set maximumUnitCount to 1 and inspect the output in the console.

let formatter = DateComponentsFormatter()

formatter.maximumUnitCount = 1
formatter.unitsStyle = .abbreviated
formatter.zeroFormattingBehavior = .dropAll
formatter.allowedUnits = [.day, .hour, .minute, .second]
0s
2m
10m
3h
1d

NaN and Infinite

You need to be careful what values you pass to the string(from:) method. If you don't pass it a valid number or the number is too large (infinite), an exception is thrown at runtime.

Runtime Exception

In this example, we sanitize the time interval before passing it to the string(from:) method.

import Foundation

let timeIntervals: [TimeInterval] = [
    0.0,
    100.0,
    600.0,
    10_450.0,
    100_844.0,
    .nan,
    .infinity
]

let formatter = DateComponentsFormatter()

formatter.maximumUnitCount = 1
formatter.unitsStyle = .abbreviated
formatter.zeroFormattingBehavior = .dropAll
formatter.allowedUnits = [.day, .hour, .minute, .second]

for timeInterval in timeIntervals where (!timeInterval.isInfinite && !timeInterval.isNaN) {
    print(formatter.string(from: timeInterval)!)
}

What's Next?

The DateComponentsFormatter class has a lot more to offer. In this episode, we scratched the surface by converting time intervals to strings. The class also supports DateComponents and date ranges. DateComponentsFormatter is a convenient, easy to use class that you need to know about.