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
<?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
<?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

Reply via email to