Introducing Time

Type safe time calculations for Swift

Oleg Dreyman
AnySuggestion

--

In Cocoa, TimeInterval (ex NSTimeInterval) is the only thing that defines time. But, actually, it’s just a typealias for Double, and so it doesn’t have any descriptive meaning. Just a number. It’s not that obvious that Double means seconds in the world of Cocoa development. And that’s not very convenient too.

There are a lot of solution that aims to solve the convenience problem for you, providing the clear syntax like this:

let interval = 10.minutes + 15.seconds

And most of the time that’s just the extensions on Double with a few calculations:

extension Double {
var seconds: Double {
return self
}
var minutes: Double {
return self * 60
}
// so on
}

And while it’s okay, it only reassures TimeInterval inability to be descriptive.

Time solves that. You may think that’s not a problem that really needs a solution.

You’re mostly right.

But at least that’s interesting, so hop on.

Meet Interval

Time is built around the generic type called Interval. The main reason it’s so way better than TimeInterval is that it’s very self-describing:

let tenMinutes = Interval<Minute>(10)

And there are several interval types available:

Interval<Nanosecond>
Interval<Microsecond>
Interval<Millisecond>
Interval<Second>
Interval<Minute>
Interval<Hour>
Interval<Day>

And those are all different types, it’s almost impossible to confuse them.

The idea behind it is that having these different types contributes to the descriptive quality of your APIs. So, for example, if some function takes Interval<Hour> as a parameter, you can guess that it’s expected to work with time interval of that altitude. Same way, Interval<Nanosecond> means you need very, very small numbers there. Type system helps you give a clear intent to the users of your APIs about what’s expected.

Actually, let’s use an example here.

Imagine you have a function that shows a reminder to a user after some time, taking a number of hours as a parameter. With plain Foundation, its declaration would look something like this:

func notifyUser(afterHours hours: Double)

The problem is that Double doesn’t really mean anything here. Type itself doesn’t give a message that you need an amount of hours. So if the user of your code misses that “afterHours” label, he could write something like this:

let interval: TimeInterval = 3 * 60 * 60 // three hours
notifyUser(afterHours: interval) // no error!

And here we are, trying to notify a user after 10800 hours, which is probably not what the developer wanted.

But it gets much better with Time.

func notifyUser(after hours: Interval<Hour>)

See how the type system clearly states that we need the amount of hours here. And the programmer’s mistake from the previous example:

let interval = 3.seconds * 60 * 60 // three hours in Interval<Second>
notifyUser(after: interval) // error!

Will simply not compile, because Interval<Second> and Interval<Hour> are different types! This compile-time error will most likely illuminate the coder and he would write:

let interval = 3.seconds * 60 * 60
notifyUser(after: interval.inHours)

or even better:

notifyUser(after: 3.hours)

Type-safety just rescued our day.

Convenience-friendly

Of course, all the sweet convenience is present in Time:

let interval = 10.minutes - 15.seconds // Interval<Minute>10.minutes > 500.seconds // truelet fiveMinutesFromNow = Date() + 5.minutes // Date

The idea: generic type as a descriptor

If you look at how Interval is implemented, it’s actually quite simple:

public struct Interval<Unit : TimeUnit> {

public var value: Double

public init(_ value: Double) {
self.value = value
}

}

But wait, what’s that TimeUnit? It’s a protocol with a very small requirement:

public protocol TimeUnit {

static var toTimeIntervalRatio: Double { get }

}

And time units are defined like this:

public enum Minute : TimeUnit {

public static var toTimeIntervalRatio: Double {
return 60
}
}

The idea here is that you can’t create Minute. Minute is simply a description, a rule by which calculations should be made. Minute doesn’t have any value. Interval<Minute> does.

Using types simply as a description or a rule for something is new for me, but it makes so much sense. In my experience, it helps to reduce boilerplate dramatically while also helping with clarity.

Time is not a library every developer would need, but it showcases how Swift strong type system and generics can really improve reliability of one’s code.

The other lesson I’ve learned while developing Time is that in ideal world every type should have a meaning. TimeInterval aka Double doesn’t really have a meaning, it’s just a number. But Interval<Second> does.

If you have any questions about Time (not space-time though) — ask me a question in “Responses” section below, or ping me on Twitter. Or you can also open an issue (or a pull request!) on GitHub:

Thanks for reading!

--

--

Oleg Dreyman
AnySuggestion

iOS development know-it-all. Talk to me about Swift, coffee, photography & motorsports.