Re: [PHP-DEV] [RFC] Duration class

[email protected] (Tim Düsterhus) Fri, 3 Jul 2026 23:51:43 +0200
Newsgroups php.internals
Message-ID <[email protected]>
Hi

On 7/3/26 22:17, Marc B. wrote:
> The `fromSeconds` constructor breaks the flow of all other constructors
> as this is not only about seconds.
> Also, this directly exposes how it's handled internally to the API which
> makes it hard to extend later on (maybe we want to support picoseconds
> on 10 years).

“Nanosecond precision” is an integral part of the design of the class 
and folks will build assumptions around this. Changing the precision 
later will be something between hard to impossible, even if the 
`fromSeconds()` constructor wouldn't take a fractional part.

This is a property shared between all the other languages we looked at 
(except for Swift as mentioned by John Bafford): Everyone models their 
Durations with nanosecond precision and documents it as such.

> PHP should make things simpler - not more complex. There are plenty of
> different duration systems out there a such values could come from as
> input to a PHP application. The current Duration API forces you to
> manually correctly calculate the value before passing it the constructor.
> 
> E.g.:
> * time measurement using microtime(true)
> * using hrtime(true) returns float on 32bit

For both of these, the `false` variant returns the value as a 
fixed-point decimal of seconds + fractional seconds, matching the 
“seconds with a fractional part” design of this RFC.

For hrtime() specifically, you can even do the following:

     $duration = Duration::fromSeconds(...hrtime());

which will do the right thing (except for the fact that `hrtime()` 
*technically* returns an Instant [1], not a Duration).

And of course the new date and time API would not be complete without 
also providing access to a high-precision (monotonic) clock in a way 
that cleanly interoperates with the new API.

[1] An instant with an unknown origin.

> I like the idea of a general "from" constructor - but not restricted to
> seconds + nanos but ALL supported units

This one could still be provided in future scope, but given my previous 
replies, you can probably assume that I don't consider it a good idea.

> $seconds = 1;
> $millis = 1_000;
> $micros = 1_000_000;
> $nanos = 1_000_000_000;
> Duration::from(seconds: $seconds, milliseconds: $millis, microseconds:
> $micros, nanoseconds: $nanos); // 4 seconds - yes please

I'd argue that if I have the use case of constructing a duration from a 
number of seconds *and* multiple different fractions of a second that 
each could overflow into larger scales, I should likely stop and 
consider whether what I'm doing is a good idea.

But even then:

     Duration::fromSeconds($seconds)
         ->add(Duration::fromMilliseconds($millis))
         ->add(Duration::fromMicroseconds($micros))
         ->add(Duration::fromNanoseconds($nanos));

will work and do the right thing.

For the common case of “seconds + fractional seconds”, the 
`fromSeconds()` constructor works (and going from fractional 
milliseconds to fractional nanoseconds is a `*1_000_000` operation that 
should be easy and obvious enough).

>   > ... there must be at most one way of creating a Duration with a
> specific length from any given constructor. This is in order to make it
> easy to reason about the resulting value, without needing to perform
> (mental) calculations.
> 
> It makes it simple to reason about for you as implementing it - it
> forces the burden to the one using the API.
> 

The quoted paragraph is speaking from the perspective of a user using 
the API. In the internal implementation just ignoring component overflow 
would be easier than checking for it.

Best regards
Tim Düsterhus