Okay, I thought this over and—inspired by @GarthSnyder's answer—here's what I came up with:
extension DispatchTimeInterval {
var seconds: Int {
switch self {
case .seconds(let s): return s
case .milliseconds(let ms): return ms / 1_000
case .microseconds(let μs): return μs / 1_000_000
case .nanoseconds(let ns): return ns / 1_000_000_000
case .never: return 0
}
}
var milliseconds: Int {
switch self {
case .seconds(let s): return s * 1_000
case .milliseconds(let ms): return ms
case .microseconds(let μs): return μs / 1_000
case .nanoseconds(let ns): return ns / 1_000_000
case .never: return 0
}
}
var microseconds: Int {
switch self {
case .seconds(let s): return s * 1_000_000
case .milliseconds(let ms): return ms * 1_000
case .microseconds(let μs): return μs
case .nanoseconds(let ns): return ns / 1_000
case .never: return 0
}
}
var nanoseconds: Int {
switch self {
case .seconds(let s): return s * 1_000_000_000
case .milliseconds(let ms): return ms * 1_000_000
case .microseconds(let μs): return μs * 1_000
case .nanoseconds(let ns): return ns
case .never: return 0
}
}
static func seconds(from interval: DispatchTimeInterval) -> DispatchTimeInterval {
return DispatchTimeInterval.seconds(interval.seconds)
}
static func milliseconds(from interval: DispatchTimeInterval) -> DispatchTimeInterval {
return DispatchTimeInterval.milliseconds(interval.milliseconds)
}
static func microseconds(from interval: DispatchTimeInterval) -> DispatchTimeInterval {
return DispatchTimeInterval.microseconds(interval.microseconds)
}
static func nanoseconds(from interval: DispatchTimeInterval) -> DispatchTimeInterval {
return DispatchTimeInterval.nanoseconds(interval.nanoseconds)
}
}
A few things to note:
-
I did away with the default
case statement in your original code. If it were me and Apple decided to add more enumeration cases to DispatchTimeInterval
in the future, I'd want the compiler to warn me that I'm no longer covering them all instead of silently falling back to a default implementation.
-
Even though there are now three different versions of microseconds
et al., they all have different signatures:
case microseconds(Int)
static func microseconds(from interval: DispatchTimeInterval) -> DispatchTimeInterval
var microseconds: Int
This should (hopefully) mitigate any possible confusion about which one is being called.
-
When returning the number of e.g. microseconds in an interval as an integer value, all fractional components will be truncated:
// 999999999 nanoseconds
let nanoseconds = DispatchTimeInterval.nanoseconds(999999999)
// 0 seconds
let seconds = DispatchTimeInterval.seconds(from: nanoseconds)
This may or may not be the behavior you intend, especially if you were to convert a high-precision value (like nanoseconds) to a low-precision value (like seconds) and then back to a high-precision value (like microseconds) again.
-
It may be more semantically consistent to return Int.max
instead of 0 for .never
. Put another way, I would have used a value of 0 to represent a DispatchTimeInterval
of .now
—were it to exist—to indicate that something should happen as soon as possible. Similarly, I would probably use a value of Int.max
to indicate that something should happen as far in the future as possible.
Honestly, though? The more I think about it, the more I think the following approach may be better:
extension DispatchTimeInterval {
static func seconds(from interval: DispatchTimeInterval) -> DispatchTimeInterval {
switch interval {
case .seconds(let s): return .seconds(s)
case .milliseconds(let ms): return .seconds(ms / 1_000)
case .microseconds(let μs): return .seconds(μs / 1_000_000)
case .nanoseconds(let ns): return .seconds(ns / 1_000_000_000)
case .never: return interval
}
}
static func milliseconds(from interval: DispatchTimeInterval) -> DispatchTimeInterval {
switch interval {
case .seconds(let s): return .milliseconds(s * 1_000)
case .milliseconds(let ms): return .milliseconds(ms)
case .microseconds(let μs): return .milliseconds(μs / 1_000)
case .nanoseconds(let ns): return .milliseconds(ns / 1_000_000)
case .never: return interval
}
}
static func microseconds(from interval: DispatchTimeInterval) -> DispatchTimeInterval {
switch interval {
case .seconds(let s): return .microseconds(s * 1_000_000)
case .milliseconds(let ms): return .microseconds(ms * 1_000)
case .microseconds(let μs): return .microseconds(μs)
case .nanoseconds(let ns): return .microseconds(ns / 1_000)
case .never: return interval
}
}
static func nanoseconds(from interval: DispatchTimeInterval) -> DispatchTimeInterval {
switch interval {
case .seconds(let s): return .nanoseconds(s * 1_000_000_000)
case .milliseconds(let ms): return .nanoseconds(ms * 1_000_000)
case .microseconds(let μs): return .nanoseconds(μs * 1_000)
case .nanoseconds(let ns): return .nanoseconds(ns)
case .never: return interval
}
}
}
This is nice because it allows you to easily convert between units while still preserving .never
semantics. If you didn't want to use enumeration case patterns—and deal with their sometimes inscrutable syntax—to extract the associated values, you could try adding an initializer to Int
:
extension Int {
init?(_ interval: DispatchTimeInterval) {
switch interval {
case .seconds(let s): self.init(s)
case .milliseconds(let ms): self.init(ms)
case .microseconds(let μs): self.init(μs)
case .nanoseconds(let ns): self.init(ns)
case .never: return nil
}
}
}
Note that, because this is failable, it still preserves .never
semantics.
Now consider this call site:
let seconds = DispatchTimeInterval.seconds(1) // seconds(1)
seconds.microseconds // 1000000
Versus this one:
let seconds = DispatchTimeInterval.seconds(1) // seconds(1)
let microseconds = DispatchTimeInterval.microseconds(from: seconds) // microseconds(1000000)
Int(microseconds) // 1000000
I personally prefer the latter given the variable naming convention here, but I recognize that it could be a bit confusing given less descriptive variable names:
let interval = DispatchTimeInterval.microseconds(1000000) // microseconds(1000000)
Int(interval) // 1000000
Of course, we could always just use an if case let
statement instead:
let interval = DispatchTimeInterval.seconds(1)
if case let .microseconds(μs) = DispatchTimeInterval.microseconds(from: interval) {
μs // 1000000
}
This may be a bit verbose, but it's not too bad and I think it works regardless of how your variables are named.
Hope this helps! :)