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.
btw, also you mentioned Java's time package as the closest thing to
DatetimeImmutable etc. I find that package a pain to use for 99% of
usages, if not 100%. It is not that I want to say php is not java.
However, in the current specific situation, I would totally mention
that :)
The Duration class is taking inspiration from Java, Rust, Golang, and
JavaScript, but is not a direct copy of any of these. I would expect the
same from any future scope additions to the new “time and date” API.
I'm also confident that we will manage to build an API that stay true to
PHP being a pragmatic language, while at the same time being correct and
powerful enough to correctly model complex use cases.
Best regards
Tim Düsterhus