[PHP-DEV] [RFC] Time\Instant and Time\Clock

Hi

On 2026-10-05 17:29, Rob Landers wrote:

I'm just now getting around to reading over the RFC, and I've read most messages but not all of them. So, I apologize if I'm reopening any old discussions here.

The only duplication was the JSON serialization that had also been mentioned by Ilia (who I'm Ccing here).

After spending much of the last 4-5 years working on a database that needs to deal with relativity, I'd recommend dropping the physics framing in the introduction. Time doesn't move at the same speed for everyone, even on Earth, and you don't specify a frame of reference for the instant. It would also make it easier to explain `until` as "computes the difference between two represented time values. It does not establish how much physical time elapsed between the events that produced those values."

Thanks for the physics discussion in Discord. To loop in the list: Rob is correct here, I'll check what I can do to satisfy the physics experts without leading other folks down the “I need a timezone” track.

This also raises a question about uncertainty. I appreciate that the RFC distinguishes the nanosecond precision of the representation from the resolution of the underlying clock. However, `Clock::now()` returns only an `Instant`, with no way through that contract to convey the source resolution or any available uncertainty information. I've had servers lose access to their hardware clock due to hardware malfunctions, so this is not just a theoretical concern. Where that information is available, it should be preserved alongside the reading. Where it is unknown, it should remain explicitly unknown. Printing nine fractional digits cannot supply that information.

Uncertainty is likely not of interest to the average user or something they understand: They just trust that their operating system provides them with the correct time. For applications where this matters adding a custom extension with a “ClockWithUncertainty” clock that returns the Instant + a Duration would be the right choice. The current design is in line with what virtually all the other programming languages do for their standard library. Marc also suggested exposing `clock_getres()` on the `SystemClock`, I'll reply there. In any case, I don't think this is something that belongs on the `Clock` interface, since it increases the API complexity for little benefit.

I think there are some things worth addressing in the current RFC:
1. `Instant` should throw when serialized to JSON. I understand the argument is that applications should choose their own representation, but the choice should be enforced rather than silently doing the wrong thing. Or provide an opaque serialization that can restore the `Instant`.

I discussed this with Derick and it's our position that:

1. As you mentioned and as I said in php.internals: Re: [RFC] Time\Instant and Time\Clock that there is no single canonical representation for JSON and that users need to make a choice themselves.
2. That implementing `JsonSerializable` just to always throw an exception would be misleading, because from the API surface it very much looks like JSON serialization would be supported.

That `json_encode()` includes a useless default serialization for arbitrary objects instead of requiring an opt-in is something that should be fixed in ext/json and not worked around for every (value) object we implement. Since there is no “json decode to object”, an opaque serialization that can restore the `Instant` is not really a thing. If users need to manually call methods, they can just go via ISO-8601.

2. `DateTimeInterface` is missing the `toInstant()` method, is that by design?

No. I just forgot that the interface exists. Now added.

3. The backward-compatibility section still says this RFC only introduces three new namespaced symbols. It should also cover the new methods on `DateTime` and `DateTimeImmutable`, including potential conflicts with existing subclass methods named `toInstant()`.

Did some preliminary adjustments there. Might tune the phrasing later.

Best regards
Tim Düsterhus

Hi

On 2026-10-06 11:17, marc@mabe.berlin wrote:

I think the following points still need some discussion. Some of
them may overlap with things others have already raised in this thread. Rather than replying to each of those messages separately, I've collected
everything in this one mail and mention who brought a point up where it
applies.

Thank you for the review. Derick and I already discussed some, but not all of the points. For now I'll just reply to those that are resolved to get them out of the way.

4. Configurable clock resolution

I'd like a way to configure the resolution,
e.g. a SystemClock that only ever returns whole seconds. Many consumers (JWT iat/exp, HTTP dates, database columns) never want fractions.
A clock configured that way guarantees whole seconds to every consumer it is injected into, instead of each consumer having to truncate on its own. It also makes test expectations simpler, because the values never carry
fractions.

We believe this is the job of a “Clock decorator”, e.g. `CoarseClock` that takes in some clock and appropriately rounds or truncates the Instant to the desired precision. Such a decorator would automatically be compatible with any type of clock (e.g. a `FrozenClock` for testing).

As with the testing clocks, we consider a `CoarseClock` out of scope for this RFC. With a possible future `truncateTo()` that I already mentioned elsewhere in the discussion, a userland implementation would become trivial for the use cases where a coarser clock is required.

7. get vs to

The RFC explicitly states that the Unix timestamp is not the internal
representation. getUnixTimestamp() is therefore a conversion, and by the RFC's own convention (toIso8601DateTimeString(), DateTime::toInstant()) it should be toUnixTimestamp(). Duration exposes its components as properties, so with get*() we'd end up with a third style within the same family.

Good catch, we renamed the methods to use the `to` prefix.

9. getNanoseconds()

It isn't obvious whether this returns the nanoseconds since epoch or the
nanoseconds within the current second. getNanoOfSecond() (Java's
NANO_OF_SECOND) would remove that ambiguity.

We believe that with the renaming of the “Unix timestamp” converter to use the `to` prefix and the lack of “Unix” in the name of `getNanoseconds()` the naming is clear enough. It is also easy to see that the result of `getNanoseconds()` can't be the number of nanoseconds elapsed since the Unix epoch, because the value is guaranteed to be less than 1_000_000_000 (i.e. less than 1 second). Using `nanoseconds` as the fractional part is consistent with `Duration::$nanoseconds`.

For completeness I'd like to note that the epoch of the Instant is intentionally left unspecified and that the Unix epoch is just one of several possible epochs.

Best regards
Tim Düsterhus

Hi,

On 7.10.2026 12:45 CEST Tim Düsterhus <tim@bastelstu.be> wrote:

Hi

On 2026-10-06 11:17, marc@mabe.berlin wrote:
> I think the following points still need some discussion. Some of
> them may overlap with things others have already raised in this thread.
> Rather than replying to each of those messages separately, I've
> collected
> everything in this one mail and mention who brought a point up where it
> applies.

Thank you for the review. Derick and I already discussed some, but not
all of the points. For now I'll just reply to those that are resolved to
get them out of the way.

> 4. Configurable clock resolution
>
> I'd like a way to configure the resolution,
> e.g. a SystemClock that only ever returns whole seconds. Many consumers
> (JWT iat/exp, HTTP dates, database columns) never want fractions.
> A clock configured that way guarantees whole seconds to every consumer
> it is injected into, instead of each consumer having to truncate on its
> own. It also makes test expectations simpler, because the values never
> carry
> fractions.

We believe this is the job of a “Clock decorator”, e.g. `CoarseClock`
that takes in some clock and appropriately rounds or truncates the
Instant to the desired precision. Such a decorator would automatically
be compatible with any type of clock (e.g. a `FrozenClock` for testing).

As with the testing clocks, we consider a `CoarseClock` out of scope for
this RFC. With a possible future `truncateTo()` that I already mentioned
elsewhere in the discussion, a userland implementation would become
trivial for the use cases where a coarser clock is required.

Fine by me to keep it out of scope for this RFC. For a future RFC, though, I'd prefer a built-in CoarseSystemClock over a generic decorator. A decorator always has to read the precise clock first and only then truncates, so it can never be cheaper than the clock it wraps. A built-in system clock configured for second (or millisecond) resolution could read a coarse clock source directly instead, such as CLOCK_REALTIME_COARSE on Linux.

On Linux arm64 I measured:

  clock_gettime(CLOCK_REALTIME) 13.3 ns
  clock_gettime(CLOCK_REALTIME_COARSE) 3.3 ns

> 9. getNanoseconds()
>
> It isn't obvious whether this returns the nanoseconds since epoch or
> the
> nanoseconds within the current second. getNanoOfSecond() (Java's
> NANO_OF_SECOND) would remove that ambiguity.

We believe that with the renaming of the “Unix timestamp” converter to
use the `to` prefix and the lack of “Unix” in the name of
`getNanoseconds()` the naming is clear enough. It is also easy to see
that the result of `getNanoseconds()` can't be the number of nanoseconds
elapsed since the Unix epoch, because the value is guaranteed to be less
than 1_000_000_000 (i.e. less than 1 second). Using `nanoseconds` as the
fractional part is consistent with `Duration::$nanoseconds`.

I'm not convinced yet

The value range is only "easy to see" after reading the documentation. The name alone should tell you what you get. Within the same class, the plural unit is used as a unit marker: toUnixTimestampMilliseconds() is "the timestamp in milliseconds". Read the same way, getNanoseconds() is "<something> in nanoseconds", and the only <something> an Instant has is its position on the timeline.

Duration is the wrong reference. A Duration is an amount, so "N nanoseconds" is correct there. An Instant is a point in time, and its sub-second part is a position within the second, not an amount. PEven the current DateTime* API already makes this distinction, using singular names:

     DateTime::getMicrosecond(): int
     DateTime::setMicrosecond(int $microsecond)
     DateTime::setTime(int $hour, int $minute, int $second = 0, int $microsecond = 0)

while the plural is used for amounts:

     Duration::fromMicroseconds(int $microseconds)
     Duration::fromMilliseconds(int $milliseconds)

getNanoOfSecond() removes the ambiguity entirely, at the cost of a few characters. If that's too far from the existing naming, getNanosecond() would at least match DateTime::getMicrosecond().

For completeness I'd like to note that the epoch of the Instant is
intentionally left unspecified and that the Unix epoch is just one of
several possible epochs.

Agreed, and that's exactly the point I made about "follows POSIX time": if
the epoch and the representation are unspecified, the class definition
shouldn't state that Instants follow POSIX time. That belongs to the Unix
timestamp conversion methods.

Best regards,
Marc

Best regards
Tim Düsterhus

Hi

On 2026-10-06 18:56, Mirco Babin wrote:

(Mirco Babin) There is a misconception. The application does not
choose the abstracted clock truth for the Composer installed
libraries. It only chooses the clock for its internal parts. Each
piece of software, internal application and each different external
library, chooses its own source of abstracted clock truth, like:

Of course. If the library provides no way to inject a clock, then you can't inject a clock. What I meant to say is that if the libraries you use allow injecting a clock (e.g. PSR-20 or the new Time\Clock, or something else of course), then you can use the adapters to translate between the different interfaces, while having a single “canonical” clock as a source of truth.

If the library doesn't allow injecting a clock right now, then the introduction of the new interface is no worse than the status quo.

(Mirco Babin) Every effort, however small, however little, that eases
this migration path for applications, should be provided.

Also because Packagist psr/clock - Packagist.org shows
*439 458 545* (439 million) installations of the PSR-20 clock. This is
an *impure number*, because it also includes CI/test installations,
and it also includes nesbot/carbon and laravel/framework
installations.

*I agree to disagree.*

I'm happy to leave it at “agree to disagree”. I nevertheless thank you for raising your concern here.

Can the RFC be updated? Under rejected features can something be added
like:

A hybrid clock, both implementing \Time\Clock from this RFC and the
PSR-20 \Psr\Clock\ClockInterface is rejected. Because ...(reasons)...

I have added a short note to the rejected features section. For the full reasoning readers can read the mailing list discussion (linked at the top of the RFC). This also avoids accidentally misrepresenting the nuance provided in the discussion due to a lack of context.

Best regards
Tim Düsterhus