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

[email protected] (Tim Düsterhus) Tue, 07 Jul 2026 16:49:52 +0200
Newsgroups php.internals
Message-ID <[email protected]>
Hi

On 2026-07-04 02:42, Pierre Joye wrote:
>> 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.
> 
> I do consider it breaks the promise. Additionally, it forces the user
> to do mental calculation and remember an arbitrary bound of
> 999,999,999 that doesn't exist in any of the APIs you cited as
> inspiration. Java's Duration.ofSeconds(long, long) explicitly
> documents carrying excess nanoseconds into seconds with no cap,
> Duration.ofSeconds(2, 1_000_000_001) produces the same value as
> Duration.ofSeconds(3, 1). Rust's Duration::new does the same. Temporal
> doesn't even carry by default. Temporal.Duration.from({ hours: 200,
> minutes: 17 }) is valid and preserves both values as given. None of
> the three reject or restrict a sub-unit's magnitude the way this
> proposal does.

See my reply to Marc as to how the `fromSeconds()` constructor is 
consistent with the other constructors in taking a single semantic 
value. If you want the overflow behavior, use an addition.

For the Rust comparison, I could imagine that this is a deliberate 
choice to make the constructor infallible. Requiring users to 
`Duration::new(seconds, nanos).expect("success")` every time they want 
to create a Duration from (known-good) values would be pretty 
unergonomic. And similarly panicking for nanosecond overflow would be 
unreasonable in case user input makes it into the function. PHP’s 
(unchecked) exceptions provide a reasonable middle-ground here, because 
users would be able to cleanly handle the ValueError if they desire to 
do so.

Java would again be closer to PHP here, but I'm not as familiar with how 
the Exception hierarchy is typically structured there.

>> 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.
> 
> If injectivity were the real goal, none of Java, Rust, or Temporal
> would be usable APIs by that standard, they're explicitly
> non-injective by design, and that hasn't stopped them from being the
> reference implementations you're citing as inspiration.

Yes, *inspiration*. We looked at those to see how others approach the 
problem, but of course built our own list of requirements and/or goals 
based on constraints imposed by PHP and also based on our experience of 
working with dates and times both in PHP and other programming 
languages.

Best regards
Tim Düsterhus