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