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

[email protected] ("Marc B.")
Newsgroups php.internals
Message-ID <[email protected]>
Hi Tim,

On 03.07.26 19:14, Tim Düsterhus wrote:
> Hi
>
> On 7/3/26 18:29, Pierre Joye wrote:
>> I like that new Duration, good new APIs. I was following the
>> discussion and one thing keeps coming to my mind as an API mistake:
>>
>>    public static function fromSeconds(int $seconds, int $nanoseconds 
>> = 0)
>>
>> Let me break down my thinking:
>>
>> fromSeconds implies I'm setting a duration in seconds. But there are
>> two arguments — the second is nanoseconds, and milliseconds isn't
>> representable at all. The name promises one unit and the signature
>> quietly provides another.
>
> Please see https://news-web.php.net/php.internals/131419; and the 
> fourth paragraph in the “design considerations”.
>
> The $nanoseconds argument in the `fromSeconds()` constructor is 
> intentionally limited to “the number of nanoseconds in a second” which 
> means that the constructor can be reasoned about taking (fractional) 
> seconds - the base unit of Durations - as a fixed point decimal. I do 
> not consider this a break in the promise.
>
>> That's correct, but I don't think it applies here. The fromSeconds
>> issue is a naming defect regardless of language: the method name
>> promises "seconds" and the signature quietly also takes nanoseconds.
>
> As mentioned above, the $nanoseconds parameter is intentionally 
> limited, making the constructor consistent with the other constructors 
> in that the “unit that is mentioned in the name” may overflow into the 
> larger units to allow construction with the desired precision in a way 
> that makes sense for the given use case.
>
> Extending this pattern to e.g. `fromMinutes()` would result in 
> `fromMinutes($minutes, $seconds = 0)` with $seconds being limited to 59.
>
> However in the other direction, `fromMilliseconds($milliseconds, 
> $microseconds = 0)`, with $microseconds <= 999 would feel weird, 
> because one wouldn’t say 5 milliseconds and 500 microseconds, when 
> they can just move the comma and say 5500 microseconds.
>
> The seconds boundary is where the units shift from a “metric” factor 
> of 1000 to a factor of 60. Thus the special handling.
>
>> PHP already supports named arguments, giving us the same clarity as
>> Temporal object literal without borrowing any JS syntax:
>>
>>    Duration::from(seconds: -30, nanoseconds: -500);
>>    Duration::from(seconds: -30, nanoseconds: 500); // error
>>    Duration::from(seconds: 30, nanoseconds: 500).negate();
>>
>> A single ::from() with named arguments would be unambiguous, easy to
>> remember, and remove the need for the whole family of from<Unit>
>
> A design goal of the constructors was that they are injective 
> functions, i.e. 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.
>
> i.e. Duration::from(seconds: 1, milliseconds: 1500) would misleadingly 
> look “between 1 and 2 seconds” at a glance. But when each of the 
> “subunits” are restricted in magnitude, it would be very inconvenient 
> to construct values with the appropriate precision, because it would 
> require splitting inputs across the different parameters.

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).

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
* using JS (even Temporal) exposing integer values as "number" often 
ends up as float in PHP

I totally understand that you want to keep it simple for now but the 
$nanoseconds second argument makes it impossible to change in the future.

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

public static function from(
     int $hours = 0,
     int $minutes = 0,
     int $seconds = 0,
     int $milliseconds = 0,
     int $microseconds = 0,
     int $nanoseconds = 0,
) : Duration

This makes it much more flexible, simpler to work with and extensible if 
we want to increase the precision any time in the future.
It's intentional to start with the most uncommon duration unit of 
$hours. That forces you to use named arguments which reads nicely and 
handling floats or not can be discussed at a later time.

Duration::from(seconds: 100, microseconds: 500);
Duration::from(hours: 5, minutes: 30);

$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

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

Regards,
Marc
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.