Re: [RFC] Time\Instant and Time\Clock

From: Date: Wed, 30 Sep 2026 19:48:51 +0000
Subject: Re: [RFC] Time\Instant and Time\Clock
References: 1 2  Groups: php.internals 
Request: Send a blank email to internals+get-132729@lists.php.net to get a copy of this message
Hi On 2026-09-30 19:40, Mirco Babin wrote:
========= Prologue ========= Well, I do have 8 comments on the Request For Comments (RFC). This is a lengthy post, be prepared for a long reading time.
Thank you! I'm fully prepared for lengthy emails and am happy to answer any clarifying questions to make sure we're all on the same page with regard to what is being proposed. Your email is also nicely structured, which makes it easy to work through it.
========= Comment 1 ========= Is \Time\Instant meant to be immutable? If so, please state that in the RFC.
Yes. This was already implied since the stub specifies that it's a readonly class, but I've just adjusted the RFC text to explicitly include the word “immutable”.
========= Comment 2 ========= The RFC states:
An Time\Instant carries no timezone. It represents a unique point on the timeline of the universe.
Why is there no explicit or implicit timezone attached? The RFC reads to me like there is an implicit timezone attached, namely the UTC timezone. Especially the toIso8601DateTimeString() function which always returns the 'Z' timezone identifier seems to imply an implicit UTC timezone?
Timezones are a concept to translate a point in time (“Instant”) into date and time as shown on a clock. Both 2026-09-30T20:13:30+02:00 and 2026-09-30T18:13:30+00:00 refer to the same point in time. The point in time itself does not have a timezone. The ISO-8601 getter chooses to represent the point in time with the UTC / Zulu timezone, because specifying an offset is required for the “date and time” representation to be unambiguous. It would be equally valid for it to select a random offset each time you use it. Using UTC / a zero offset is just a pragmatic choice for best interoperability.
========= Comment 3 ========= Why is it called \Time\Instant? I find \Time\UtcInstant a better name, which embeds the timeline unit used. \Time\UtcInstant leaves room for future different timeline Instants like: - \Time\TaiInstant (International Atomic Time) - \Time\Ut1Instant (Universal Time) - \Time\TtInstant (Terrestrial Time)
UTC, TAI and the others are just different projections of a “point on the timeline of the universe” and the Instant class is largely agnostic to them. It differs from a perfect model of the timeline of the universe in that leap seconds are ignored (see my reply to Andreas https://news-web.php.net/php.internals/132600), since that is what the computing industry converged on. Nevertheless, this brings us back to comment (2): The differences between UTC, TAI and the others concern the *clock* time. The point on the timeline of the universe remains the same. And of course Time\Instant is just the ergonomic name, matching the precedent set in Java (java.time.Instant) and JavaScript (Temporal.Instant).
========= Comment 4 ========= The \Time\Clock interface conflicts with PSR-20 Clock interface, see https://www.php-fig.org/psr/psr-20/ . A class can never implement both interfaces at the same time. This will hinder adoption.
The RFC acknowledges that. Writing an adapter is easy, particularly since the addition of the DateTimeImmutable::toInstant() method to the proposal.
Consider renaming the function now(): \Time\Instant function to function nowAsInstant(): \Time\Instant.
now() is the obvious name for the method. Using something else would mean that the proposed API would still suffer from a decision made for compatibility after everyone has migrated to it and PSR-20 is long forgotten. The horizon we are planning with is *at least* the next 15 years, but we're hoping that the API holds up for even longer. For the same reason the compatibility layer with DateTimeImmutable is explicitly added to the *old API* so that it is possible to deprecate and remove the old API without affecting the new one.
========= Comment 5 ========= The RFC introduces a SystemClock class:
<?php

final readonly class SystemClock implements \Time\Clock
{
    public function __construct() {}

    /**
     * The current instant, at the resolution offered by the platform
     * clock,
     * which is generally coarser than nanoseconds.
     *
     * Returned values are not guaranteed to be monotonically
     * increasing across calls.
     */
    public function now(): \Time\Instant {}
}

?>
Why is SystemClock not a Singleton? Can there be multiple different system clocks in practice? I believe a System Clock, with the emphasis on "System" is a System Wide clock. This clock is not bound to PHP, but to all processes running on the system, including Apache, shell-scripts, NGINX and others.
Conceptually there is only one SystemClock and internally each SystemClock object accesses the same clock source provided by the operating system. But you are making a good point here: Replacing the constructor with a static ::get() method or similar would make that explicit and would also allow for reducing memory usage when SystemClock objects are obtained at different places in the code (instead of being pulled from a dependency injection container). I'll put this on my list to discuss with Derick (and am happy to receive naming suggestions for such a static getter).
The RFC also states a FrozenClock for testing purposes. Well this
The FrozenClock is *not* part of the RFC. It is provided in the non-normative part of the RFC as an example to showcase how the Clock interface can be useful for testing purposes. Whether or not a FrozenClock will be added to PHP itself is to be decided as part of a future RFC (by different authors, as indicated in my email from yesterday).
pattern is not always going to work. Especially when an application (the main program) uses Composer libraries and wants to freeze time for each and everyone, including the vendor directory. Because not every library in the vendor directory uses SystemClock. They all use time(), because that is currently the standard way to obtain the System Clock. Time freezing must involve the time() function!
It is debatable whether time() is the standard way to access the system clock. It lacks support for fractional seconds and has terrible ergonomics due to returning a bare integer. It is certainly still in common use, but I'd argue the current best practice is a DateTimeImmutable object, ideally one you obtained from a PSR-20 clock.
I think SystemClock should be removed from this RFC, it deserves a seperate RFC. That RFC should also explore a frozen time() function. Meaning the output of time() can be frozen recursively. Because of this probably the \Time\Clock interface should also be moved to that new RFC.
The point of the new date and time API is to completely supersede the existing date and time API with a proper API design matching best practices. Superseding the existing API includes the time() function - which I would say is already a legacy API as of *today*, since DateTimeImmutable does everything time() does and it does it better (except maybe for brevity).
========= Comment 6 ========= Why does \Time\Instant not have a named constructor public function fromSystemClock(): \Time\Instant ? Why all the complexity of constructing a \Time\SystemClock and calling now()? The System Clock is a Singleton and is always available, as the time() function shows.
See https://news-web.php.net/php.internals/132594. “Injecting” a clock would be the correct solution. If SystemClock was made a singleton with a static getter as mentioned above (while still implementing the interface), this would allow for:
    SystemClock::get()->now()
for simple use cases and one-off scripts, which would be even shorter than your proposed:
    Instant::fromSystemClock()
========= Comment 7 ========= The RFC states:
PHP currently offers two ways to represent such a point, and both are unsatisfying for the general case. A plain int as returned by time() discards all sub-second precision and carries no type information, leaving the question of “is this seconds, milliseconds or something entirely different” to documentation instead of the type system.
I want to bring something else to the table. What if one wants *less* precision? What if one does not want seconds, milliseconds, microseconds, nanoseconds? How is that going to fit into the type system?
The Instant represents a point in time as precisely as possible. If for some reason you need less precision, you can adjust it using the ->add() and ->sub() methods as desired. Please also see https://news-web.php.net/php.internals/132621 for a possible future scope ->truncateTo() method, but …
As a practical example, take a public transportation coach. The coach has a schedule which is expressed in *minutes*, never in *seconds*. The same argument, only *minutes* precision, goes for flight departures and arrivals. Also ferries express their times in *minutes*.
… using an Instant to represent a public transportation schedule is the wrong abstraction: Public transportation works on “date and time” as shown on a calendar and a clock and only becomes meaningful once you involve timezones. This is particularly important when a DST changeover happens or your jurisdiction changes timezone rules: Now the point on the timeline of the universe where your bus departs is different, but the time shown on your wristwatch has remained the same.
========= Comment 8 ========= The RFC states:
Extensions may want to adjust their API to take Time\Instant in addition to an int representing a Unix timestamp or an object implementing DateTimeInterface.
With regard to the DateTimeInterface, I want to share that this interface is an internal interface and can't be implemented by user-space classes. See also issue #23543 at https://github.com/php/php-src/issues/23543 To clarify, this does not work:
<?php

class MyTime implements DateTimeInterface
{
}

// Fatal error: DateTimeInterface can't be implemented by user classes
?>
That is an accurate observation, but not relevant to the quoted part. I think there might be a misunderstanding: What the quote is saying is that if there is a function foo(DateTimeInterface $when) it should be adjusted to foo(DateTimeInterface|Instant $when) so that it is possible to use it with the new API without needing to first convert the Instant into a DateTimeImmutable. Best regards Tim Düsterhus

« previous php.internals (#132729) next »