It came up in @armcknight's excellent "Surveying how Swift evolves" thread that "Date.now()" might be a reasonable addition to the date/time APIs of Foundation.
I am vehemently opposed to this idea, but I think we should have this discussion in a new thread.
Date()
is one of the great hidden dependencies we inject in to our code. It relies on a host of external things, including the system's connection to the network time servers. Using Date()
to retrieve the current time means that it's almost impossible to accurately test how your app will behave at other times. Usually when you want to test that, you create your own static func now() → Date
extension on Date
that you can then have reach in to other global state to know if you should fake the time to be something else instead.
As a concrete example of this, I used to work on an app that was only really used a few days a year (the WWDC app). In order to accurately test how the app would behave at key moments (pre-keynote, post-keynote, post-lunch, etc), we created our own static func now() → Date
method and tried to used that everywhere. It worked, but only in a roundabout way. It was extremely difficult to see progression through the week, because manually advancing the time meant stopping and recompiling and re-running.
The proposed Date.now()
method suffers from all of these same problems. It only slightly de-obfuscates how to get the current time, but it does nothing to break the hidden dependency on global state, nor does it make it easy to affect the current time.
Instead of a Date.now()
method, I propose a whole new addition to Foundation: Clock
.
In the real world, when we want to know what time it is, we look at a clock. The clock tells us the date and time of day. If we want to pretend it's a different time, we alter the clock.
Foundation, IMO, should follow this same pattern, and our code should be updated to use Clock
instead of Date
.
An example of what the API might look like is this:
public struct Clock {
public static let system: Clock
public func now() → Date
}
You could then take this further by making clocks that start at different points in time, or even tick faster or slower than the system clock:
extension Clock {
init(startingAt: Date, flowRate: Double)
}
If you'd like to see an implementation of this, please check out the date-and-time library I'm working on, called Chronology. In your code, you'd pass around a Clock
instance and ask it for the value of now()
in order to get the current time.
Doing it this way means you can create a clock that runs 100x faster than real time, or that starts in the past, or the future, or even only runs fast during certain intervals (so you can speed through the boring bits and focus on the good bits)... Just about the only thing you couldn't do is make time flow backwards.
This idea doesn't begin to address some of the other fundamental problems of Foundation's date and time API, but I don't think we should encourage more hidden dependencies by introducing Date.now()
.