Hi
following Time\Duration in PHP 8.6, Derick and I created an RFC for
Time\Instant and Time\Clock as the next part of the new date and time
API:
https://wiki.php.net/rfc/time_instant_class
This time we have several months until the next feature freeze, so
there’s place to discuss additional functionality that we missed.
Nevertheless we would like to keep the RFC focused so that every
decision gets the attention it deserves and some bits are already listed
as “Future Scope” to that effect.
Best regards
Tim Düsterhus
Hey Tim, Hey Derick
Hi
following Time\Duration in PHP 8.6, Derick and I created an RFC for Time\Instant and Time\Clock as the next part of the new date and time API:
https://wiki.php.net/rfc/time_instant_class
This time we have several months until the next feature freeze, so there’s place to discuss additional functionality that we missed. Nevertheless we would like to keep the RFC focused so that every decision gets the attention it deserves and some bits are already listed as “Future Scope” to that effect.
Thanks for the proposal.
I do see some clashes within the proposal though:
Right at the start you are mentioning that "An Time\Instant carries no timezone. It represents a unique point on the timeline of the universe. This point is the same for everyone, no matter where they are located on the Earth (or not on Earth at all), i.e. no matter their timezone."
I am totally in favour of that!
Later on though you are mentioning that "Following the lead of virtually all programming languages and operating systems, leap seconds are ignored."
Is that only with relation to the Iso8601 strings? Or is that a general shortcoming?
The other point I find irritating is the naming of the Clock interface. While I can understand the reasoning behind it I see irritation in the community upcoming as the CLockInterface now suddenly returns 2 different things, depending on which ClockInterface people are talking about.
Is there a way to Unify the ideas behind Time\Clock and Psr\Clock\CLockInterface?
As right now the Time\Clock is not a direct replacement of Psr\Clock\ClockInterface as - at the current time - there is no way to generate a DateTimeImmutable directly from a Time\Instant.
Just my 0.02€
Cheers
Andreas
--
,,,
(o o)
+---------------------------------------------------------ooO-(_)-Ooo-+
| Andreas Heigl |
| mailto:andreas@heigl.org N 50°22'59.5" E 08°23'58" |
| https://andreas.heigl.org |
+---------------------------------------------------------------------+
| https://hei.gl/appointmentwithandreas |
+---------------------------------------------------------------------+
| GPG-Key: https://hei.gl/keyandreasheiglorg |
+---------------------------------------------------------------------+
Hi
following Time\Duration in PHP 8.6, Derick and I created an RFC for
Time\Instant and Time\Clock as the next part of the new date and time
API:https://wiki.php.net/rfc/time_instant_class
This time we have several months until the next feature freeze, so
there’s place to discuss additional functionality that we missed.
Nevertheless we would like to keep the RFC focused so that every
decision gets the attention it deserves and some bits are already listed
as “Future Scope” to that effect.Best regards
Tim Düsterhus
Hi Tim,
Thanks for putting this together. I have a few suggestions about
naming, obtaining the current time, and the clock implementations.
Could we name the proposed class Time\SystemTime and reserve
Time\Instant for a future high-resolution monotonic clock API? Rust
uses this distinction, and I find it helpful:
SystemTime
represents wall-clock time, which can change following clock
corrections, while
Instant
represents monotonic time, suitable for measuring elapsed time,
benchmarking, etc.
I appreciate that monotonic time is explicitly left to future scope.
I’m suggesting we settle the naming with both concepts in mind now,
without necessarily introducing both in this RFC. This is also the
distinction I use in Whim's
Instant
and SystemTime
types for reference.
I would also like to see a static SystemTime::now() factory, and
eventually Instant::now() for monotonic time. I understand the
rationale for an injectable clock, but I think the two approaches can
coexist. When I simply need the current time, I would prefer:
$time = SystemTime::now();
Having to instantiate a clock, or pass one through several layers of
an existing application for an occasional time read, feels
unnecessarily cumbersome. People who need control over time can use an
injected clock; callers using the static factory would knowingly give
up that control. Is there a reason to exclude the convenience API?
Also, If we are adding now static method, I think adding
elapsed()
would also be helpful. ( a shorter way of doing
InstantOrSystemTime::now()->durationSince($this) ).
Finally, I would reconsider including FrozenClock and OffsetClock.
The RFC notes that specialised clocks can have different behaviours,
but these two seem useful and sufficiently well-defined:
FrozenClocktakes a time value and always returns that value. (
potentionally withadvance(Duration $duration): voidand
travelTo(SystemTime $moment): void)OffsetClocktakes another clock and a duration, and returns the
wrapped clock’s current time plus that duration.
More elaborate behaviour could remain in userland. For these basic
implementations, though, I don’t see much ambiguity in either the API
or the semantics. Including them would save users from repeatedly
implementing the same small classes or adding a Packagist dependency
for them, and would make the clock abstraction useful out of the box
for testing.
Best regards,
Seifeddine
Hi
Replying with my personal opinion, not as the RFC author (since I didn't
discuss this reply with Derick).
Could we name the proposed class
Time\SystemTimeand reserve
Time\Instantfor a future high-resolution monotonic clock API? Rust
uses this distinction, and I find it helpful:
SystemTime
represents wall-clock time, which can change following clock
corrections, while
Instant
represents monotonic time, suitable for measuring elapsed time,
benchmarking, etc.
The proposed naming is consistent with JavaScript’s Temporal
(https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant)
and Java’s java.time
(https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html),
which are the primary sources of reference we used.
I also believe that “real world time” and instead of “monotonic” time
will be the primary use case and would thus prefer the simpler (and more
widely used) name for that.
I appreciate that monotonic time is explicitly left to future scope.
I’m suggesting we settle the naming with both concepts in mind now,
without necessarily introducing both in this RFC. This is also the
I agree that naming should be taken into account as part of this RFC,
similarly how we already decided on Duration::divideInto(), despite not
yet shipping it.
I would also like to see a static
SystemTime::now()factory, and
eventuallyInstant::now()for monotonic time. I understand the
rationale for an injectable clock, but I think the two approaches can
coexist. When I simply need the current time, I would prefer:$time = SystemTime::now();Having to instantiate a clock, or pass one through several layers of
an existing application for an occasional time read, feels
unnecessarily cumbersome. People who need control over time can use an
injected clock; callers using the static factory would knowingly give
up that control. Is there a reason to exclude the convenience API?
For the monotonic timer having a single static constructor definitely
makes sense to me, because the use cases are much different and you
can't meaningfully mock it anyway.
For the real world clock, I think I would want to nudge users towards
“doing the right thing” and use an injectable factory and therefore now
provide the static factory, but I don't have particularly strong
opinions regarding that. I don't think it should be a static method on
the Instant class, though, because that would intimately tie it to the
SystemClock, which feels wrong. The correct approach would probably be
a Time\now(): Instant function.
(Note: Please keep the RFC terminology in discussion of topics that are
not about changing the terminology, seeing SystemTime in your example
was very confusing since the RFC also has SystemClock.)
Also, If we are adding
nowstatic method, I think adding
elapsed()
would also be helpful. ( a shorter way of doing
InstantOrSystemTime::now()->durationSince($this)).
Similarly, I can see that being a first class citizen for the monotonic
timer (since the prime use case is measuring elapsed time), but not so
much for the real world clock, where measuring elapsed time is just one
operation of many.
Finally, I would reconsider including
FrozenClockandOffsetClock.
The RFC notes that specialised clocks can have different behaviours,
but these two seem useful and sufficiently well-defined:
FrozenClocktakes a time value and always returns that value. (
potentionally withadvance(Duration $duration): voidand
travelTo(SystemTime $moment): void)OffsetClocktakes another clock and a duration, and returns the
wrapped clock’s current time plus that duration.
I would be open to doing those two as an (immediate) follow-up, since I
feel they have plenty of bike-shedding potential on their own. So they
would also land in PHP 8.7, but not distract from the main API design.
Best regards
Tim Düsterhus
Hi Tim,
Thanks for the response.
The proposed naming is consistent with JavaScript’s Temporal
(https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant)
and Java’s java.time
(https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html),
which are the primary sources of reference we used.I also believe that “real world time” and instead of “monotonic” time
will be the primary use case and would thus prefer the simpler (and more
widely used) name for that.
I think the JavaScript and Java background explains why I found the
naming confusing. Instant may feel natural for wall-clock time to
developers familiar with Java, and perhaps JavaScript, although
Temporal is still relatively new. Because i work more with Rust,
where Instant specifically represents monotonic time and
SystemTime represents wall-clock time.
I agree that wall-clock time will be used more often, but I’m not
convinced that Instant is necessarily simpler or clearer than
SystemTime. Arguably, the latter communicates more directly that the
value comes from the system clock and is subject to clock adjustments.
That said, let’s see what Derick and others think about the naming.
For the real world clock, I think I would want to nudge users towards
“doing the right thing” and use an injectable factory and therefore now
provide the static factory, but I don't have particularly strong
opinions regarding that. I don't think it should be a static method on
theInstantclass, though, because that would intimately tie it to the
SystemClock, which feels wrong. The correct approach would probably be
aTime\now(): Instantfunction.
Regarding nudging users towards “doing the right thing,” I don’t think
injecting a clock is always the right choice. It is valuable when the
code needs deterministic control over time, but sometimes keeping
things simple is preferable. For an occasional time read, passing a
clock through several layers can add ceremony without providing much
practical benefit. I think a direct API and injectable clocks can
coexist without suggesting that either should be used universally.
A Time\now() function would address the convenience issue, although
I still think discoverability is better when the operation is
available directly on the relevant type. But I don't have a strong
opinion here.
Also, If we are adding
nowstatic method, I think adding
elapsed()
would also be helpful. ( a shorter way of doing
InstantOrSystemTime::now()->durationSince($this)).Similarly, I can see that being a first class citizen for the monotonic
timer (since the prime use case is measuring elapsed time), but not so
much for the real world clock, where measuring elapsed time is just one
operation of many.
I also see some value in elapsed() for wall-clock instants, even
though I agree that it is more useful and reliable with a monotonic
clock. For example, I might use something like this for quick
debugging or logging:
$startedAt = Instant::now();
$result = evaluate_expression($expr);
fwrite(STDERR, 'Took ' . $startedAt->elapsed());
For this kind of informal measurement, I may not need the stronger
guarantees of a monotonic clock. The operation would, of course,
remain subject to wall-clock adjustments. Rust returns an error if the
clock moves backwards; PHP could throw an exception in that case. I’m
not strongly attached to including this method for wall-clock time,
but I do think it has plausible uses.
FrozenClocktakes a time value and always returns that value. (
potentionally withadvance(Duration $duration): voidand
travelTo(SystemTime $moment): void)OffsetClocktakes another clock and a duration, and returns the
wrapped clock’s current time plus that duration.I would be open to doing those two as an (immediate) follow-up, since I
feel they have plenty of bike-shedding potential on their own. So they
would also land in PHP 8.7, but not distract from the main API design.
I’m fine with leaving FrozenClock and OffsetClock to an immediate
follow-up RFC. My only concern is the current wording that test clocks
are better left to userland because their behaviour varies widely.
That argument could later be used against adding even these
well-defined clocks to the standard library. Perhaps they could
instead be mentioned explicitly under “Future Scope,” without
committing this RFC to their exact APIs.
Best regards,
Seifeddine
Hi
I agree that wall-clock time will be used more often, but I’m not
convinced thatInstantis necessarily simpler or clearer than
SystemTime. Arguably, the latter communicates more directly that the
value comes from the system clock and is subject to clock adjustments.
That said, let’s see what Derick and others think about the naming.
Yes, Instant is clearer than SystemTime, because of the Time
suffix that implies “time as shown on a clock”, which is not what the
proposed Instant represents: Namely a point on the timeline of the
universe. Other names that I've seen used for this concept are
“Timestamp” or “Moment”, with “Instant” being the most widely used in
the references we looked at.
Specifically, as part of future scope we're also planning for “Date” and
“Time” classes that represent a calendar date (year, month, day) and a
“clock time” (hour, minute, second, fractional second) respectively.
Java calls these LocalDate and LocalTime. Calling the “point in time”
class “SystemTime” would invite confusion with whatever the “clock time”
object would be.
A
Time\now()function would address the convenience issue, although
I still think discoverability is better when the operation is
available directly on the relevant type. But I don't have a strong
opinion here.
Java has an overloaded Instant.now() and Instant.now(Clock) where the
parameter-less version defaults to the system clock. That would
theoretically work for PHP, but I feel that a free-standing function
matches PHP’s existing API design and capabilities better (Java doesn't
have bare functions, unless that changed since I last looked at it).
I also see some value in
elapsed()for wall-clock instants, even
though I agree that it is more useful and reliable with a monotonic
clock. For example, I might use something like this for quick
debugging or logging:$startedAt = Instant::now(); $result = evaluate_expression($expr); fwrite(STDERR, 'Took ' . $startedAt->elapsed());For this kind of informal measurement, I may not need the stronger
guarantees of a monotonic clock. The operation would, of course,
remain subject to wall-clock adjustments. Rust returns an error if the
clock moves backwards; PHP could throw an exception in that case. I’m
not strongly attached to including this method for wall-clock time,
but I do think it has plausible uses.
The monotonic clock would not be much more complicated. Using the
hypothetical name “Stopwatch”, it could be:
$watch = Stopwatch::start();
// …
echo "Took ", $watch->elapsed();
In fact using Stopwatch as the name might allow for a nice API where a
method could store “named round timers” as an array. Something like:
$start = Stopwatch::start();
prepare();
$start->snapshot('preparation');
execute();
$start->snapshot('execution');
var_dump($start->snapshots);
Where the 'execution' snapshot would be measured starting from the
'preparation' snapshot. Or even:
$duration = Stopwatch::measure(execute(...));
to measure a single function call.
I would be open to doing those two as an (immediate) follow-up, since
I
feel they have plenty of bike-shedding potential on their own. So they
would also land in PHP 8.7, but not distract from the main API design.I’m fine with leaving
FrozenClockandOffsetClockto an immediate
follow-up RFC. My only concern is the current wording that test clocks
are better left to userland because their behaviour varies widely.
That argument could later be used against adding even these
well-defined clocks to the standard library. Perhaps they could
instead be mentioned explicitly under “Future Scope,” without
committing this RFC to their exact APIs.
Fair enough. I'll put rephrasing that paragraph on my list to discuss
with Derick for the next RFC update.
Best regards
Tim Düsterhus