Hi everybody,
I'd like to improve the TimeInterval
type in the Swift Standard Library since I think it doesn't fit Swifts goals.
The Problem
In our projects we are often seeing code like this:
// property example
let pollingInterval: TimeInterval = 5
// method example
func animate(duration: TimeInterval = 20, animations: () -> Void) {
// do something
}
// method usage example
animate(duration: 0.5, animations: { /* do something */ })
As most of you will know, the TimeInterval
type is basically a typealias for Double
.
The documentation also states:
A TimeInterval value is always specified in seconds; (...).
What I don't like about the TimeInterval
type regarding Swifts goal for being an expressive language is that the time unit is hidden to the reader of Swift code. This not only makes code harder to understand for Swift newbies but it's also more error prone since people are doing unit conversion calculation manually:
let intervalThreshold: TimeInterval = 6 * 60 * 60 // 6 hours
Also often times (as you can see in the above real world example) the actual time which was meant here is documented via a comment so the reader knows the intention. This again is not only unnecessary extra work, but again error prone when the code is being changed.
Proposed Solution
In the type DispatchTimeInterval
which is very similar to the TimeInterval
type in usage we have a solution for this. Replacing TimeInterval
in the above example code with DispatchTimeInterval
would look something like this:
// property example
let pollingInterval: DispatchTimeInterval = .seconds(5)
// method example
func animate(duration: DispatchTimeInterval = .seconds(20), animations: () -> Void) {
// do something
}
// method usage example
animate(duration: .milliseconds(500), animations: { /* do something */ })
As you can see, the units are now explicit. We could implement the same behavior for TimeInterval
by extending it like this:
extension TimeInterval {
// MARK: - Computed Type Properties
internal static var secondsPerDay: Double { return 24 * 60 * 60 }
internal static var secondsPerHour: Double { return 60 * 60 }
internal static var secondsPerMinute: Double { return 60 }
internal static var millisecondsPerSecond: Double { return 1_000 }
internal static var microsecondsPerSecond: Double { return 1_000 * 1_000 }
internal static var nanosecondsPerSecond: Double { return 1_000 * 1_000 * 1_000 }
// MARK: - Type Methods
/// - Returns: The time in days using the `TimeInterval` type.
public static func days(_ value: Double) -> TimeInterval {
return value * secondsPerDay
}
/// - Returns: The time in hours using the `TimeInterval` type.
public static func hours(_ value: Double) -> TimeInterval {
return value * secondsPerHour
}
/// - Returns: The time in minutes using the `TimeInterval` type.
public static func minutes(_ value: Double) -> TimeInterval {
return value * secondsPerMinute
}
/// - Returns: The time in seconds using the `TimeInterval` type.
public static func seconds(_ value: Double) -> TimeInterval {
return value
}
/// - Returns: The time in milliseconds using the `TimeInterval` type.
public static func milliseconds(_ value: Double) -> TimeInterval {
return value / millisecondsPerSecond
}
/// - Returns: The time in microseconds using the `TimeInterval` type.
public static func microseconds(_ value: Double) -> TimeInterval {
return value / microsecondsPerSecond
}
/// - Returns: The time in nanoseconds using the `TimeInterval` type.
public static func nanoseconds(_ value: Double) -> TimeInterval {
return value / nanosecondsPerSecond
}
}
Then the example code would read exactly like with DispatchTimeInterval
:
// property example
let pollingInterval: TimeInterval = .seconds(5)
// method example
func animate(duration: TimeInterval = .seconds(20), animations: () -> Void) {
// do something
}
// method usage example
animate(duration: .milliseconds(500), animations: { /* do something */ })
Much more readable and less error prone in cases conversions are needed:
let intervalThreshold: TimeInterval = .hours(6)
One more thing ...
While my main pitch here is to make the creation of TimeInterval
objects more expressive, we could also improve usage of TimeInterval
variables in case they need to be converted to other unit scales. For this we could add the following content to the TimeInterval
extension above:
extension TimeInterval {
// MARK: - Computed Type Properties
// ...
// MARK: - Computed Instance Properties
/// - Returns: The `TimeInterval` in days.
public var days: Double {
return self / TimeInterval.secondsPerDay
}
/// - Returns: The `TimeInterval` in hours.
public var hours: Double {
return self / TimeInterval.secondsPerHour
}
/// - Returns: The `TimeInterval` in minutes.
public var minutes: Double {
return self / TimeInterval.secondsPerMinute
}
/// - Returns: The `TimeInterval` in seconds.
public var seconds: Double {
return self
}
/// - Returns: The `TimeInterval` in milliseconds.
public var milliseconds: Double {
return self * TimeInterval.millisecondsPerSecond
}
/// - Returns: The `TimeInterval` in microseconds.
public var microseconds: Double {
return self * TimeInterval.microsecondsPerSecond
}
/// - Returns: The `TimeInterval` in nanoseconds.
public var nanoseconds: Double {
return self * TimeInterval.nanosecondsPerSecond
}
// MARK: - Type Methods
// ...
}
Then something like the following would be possible:
let timeInterval: TimeInterval = .hours(6)
timeInterval.days // => 0.25
timeInterval.hours // => 6
timeInterval.minutes // => 360
timeInterval.seconds // => 21600
timeInterval.milliseconds // => 21600000
timeInterval.microseconds // => 21600000000
timeInterval.nanoseconds // => 21600000000000
Framework available
The above suggested extension is actually taken from my open source library HandySwift on GitHub, the extension code can be found here, tests for it are written there. Feel free to include HandySwift into one of your projects to try out how this change would feel in the real world. We are using it since quite a while now and feel like it should be part of Swift – which is why I'm posting this thread.
What do you think?