[PHP-DEV] [RFC] Duration class

Hi

Am 2026-06-22 09:21, schrieb marc@mabe.berlin:

I would go with `divideBy(int|float|self $divisor): self|int|float` where the argument type defines the return type:
* `divideBy(int|float $divisor): self`
* `divideBy(self $divisor): int|float`

PHP doesn't support “method overloading” and that suggestion requires method overloading. At least in spirit: It would require special - static-analysis only - definitions for this signature to make sense to a developer. With the “Deprecate functions with overloaded signatures” RFC (PHP: rfc:deprecate_functions_with_overloaded_signatures) we have specifically deprecated the use of overloaded signatures.

About integer division - I don't think this is the usual case so I would keep that separate (for later) just like `$int / $int = int|float` is the usual case and integer division `intdiv(): int` needs to be handled more explicitly is needed.

“Integer division“ as you call it is the natural inverse of multiplication. I don't think breaking up a Duration into multiple parts of equal length is something unusual. Looking at the other languages we took inspiration from, it's also the only division operation that Java supports.

Best regards
Tim Düsterhus

Hi

Am 2026-06-22 01:07, schrieb Morgan:

Oof. Naming.
The only term I can find that is specifically about dividing one duration by another is "beat", but that's definitely domain-specific.

'periodCount' or 'countPeriods' come to mind. Since in 'n/d' we're counting how many times duration 'd' occurs during duration 'n' we're basically multiplying the duration by frequency '1/d'; the inverse of the frequency - 'd' itself - is called the period. Which word is also used for "named subdivision of historical time", "class-sized subdivision of a school day", and so on.

The biggest hangup with that name is that it doesn't explicitly state that it returns the remainder as well. periodParts? periodDivide?

Thank you for the suggestions. I quite like period. I'll also check with Derick if he has any good ideas (and regarding the Duration divided by Duration bit in general).

Best regards
Tim Düsterhus

On Mon, Jun 22, 2026, at 12:25 PM, Tim Düsterhus wrote:

Hi

Am 2026-06-22 17:11, schrieb ignace nyamagana butera:

I concur with Marc remarks especially if we want to add a divmod like
method this simple example shows the issue one would run into with the
current constraints.

$duration = Duration::fromHours(3)->negate();

$factor = Duration::fromHours(1);
[$count, $remainder] = $duration->divmod($factor);
// using BCMath implementation for the example !
//[-3, Duration::fromSeconds(0)] is returned in the tuple

Duration::sum(
    $factor->multiplyBy($count), // with the current constraints this
will throw
    $remainder,
);

I would personally find [3, Duration::fromSeconds(0)] (i.e. ignoring the
sign) to be a reasonable reply to the question “how often can I fit this
duration into the other duration”.

In fact the output of a `divmod()` would be very confusing when the
divisor doesn't divide the dividend, since the result of the modulo
operator is always positive (see also:
https://stackoverflow.com/a/13683709):

     $duration = Duration::fromMinutes(3 * 60 + 20)->negate(); //
negative 3:20 hours
     $factor = Duration::fromMinutes(60); // 1 hour

     $duration->divmod($factor);
     // [-4, Duration::fromMinutes(40)] = (-4 * 60 minutes) + 40 minutes
= negative 3:20 hours

A `divrem()` (“remainder”) would be:

     $duration = Duration::fromMinutes(3 * 60 + 20)->negate(); //
negative 3:10 hours
     $factor = Duration::fromMinutes(60); // 1 hour

     $duration->divrem($factor);
     // [-3, Duration::fromMinutes(20)->negate()] = (-3 * 60 minutes) -
20 minutes = negative 3:20 hours

In my opinion, implementing "Duration divided by Duration" as a
remainder operation is the only reasonable choice here and “ignoring the
sign” to avoid the entire “modulo vs remainder” discussion would be
reasonable when the method is appropriately named.

Staying with the IEEE-754 comparison for the sign-magnitude
representation, I could also imagine adding a “copysign” method matching
the `copysign()` function
(copysign(3) - Linux manual page).

The example would then be:

     Duration::sum($factor->multiplyBy($count),
$remainder)->copysign($duration);

If we feel that the “Duration divided by Duration” *should* be able to
return negative values, then lifting the `>= 0` restriction of
`multiplyBy()` would also easily be possible.

Best regards
Tim Düsterhus

To be clear, I absolutely hate using a static method here. Even if we're just using methods and not operators, $dur3 = $dur1->add($dur2) is the way to go.

--Larry Garfield

Hi

Am 2026-06-22 20:08, schrieb Larry Garfield:

To be clear, I absolutely hate using a static method here. Even if we're just using methods and not operators, $dur3 = $dur1->add($dur2) is the way to go.

Given the amount of “subthreads” and to keep things organized for as the RFC author: This remark better fits into the php.internals: Re: [RFC] Duration class subthread and I would appreciate you resending it there if it's important to you - and not continue discussion in this subthread about “dividing durations by durations”.

Best regards
Tim Düsterhus

On Sat, Jun 20, 2026, at 6:45 AM, ignace nyamagana butera wrote:

On Sat, Jun 20, 2026 at 1:01 PM Tim Düsterhus <tim@bastelstu.be> wrote:

Hi

Am 2026-06-19 12:25, schrieb ignace nyamagana butera:
> However, I would advise against introducing Duration::add and
> Duration::sub
> methods. Instead, I would recommend providing a single method,
> Duration::sum(Duration
> ...$durations): self.
>
> Given that a Duration already carries a sign and may therefore be
> either
> positive or negative, the presence of separate add and sub methods
> could
> create an implicit and potentially misleading notion of directional
> behavior.
>
> In contrast, a sum method using variadic arguments would allow multiple
> Duration instances to be combined in a natural and consistent manner.
> It
> would also avoid implying any expectation regarding the resulting sign,
> which may legitimately be either positive or negative depending on the
> input values.

Can you clarify if you expect the `sum()` method to be a static method
or an instance method? If it's a static method, what would you expect
`Duration::sum()` (with an empty list of durations) to result in? Should
it be an Error, `Duration::fromSeconds(0)`, or perhaps something
entirely different?

Best regards
Tim Düsterhus

PS: Please don't forget to reply below the quoted parts; and to cut the
quoted parts to the relevant portion.

Hi Tim,

I would make the method static and if no argument is given I would
expect an ArgumentCountError to be thrown just like with array_sum.

Best regards,
Ignace

(Repeating here per Tim's request.)

I am 100% against a static method for adding durations. That's entirely pointless. Even if we're just using methods and not operators, $dur3 = $dur1->add($dur2) is the way to go.

--Larry Garfield

Hello,

On Mon, 22 Jun 2026 at 09:42, <marc@mabe.berlin> wrote:

I have looked up the implementations of other Duration classes you mentioned - Go, Java, Rust - none of them have such kind of negative flag.

For reference, brick/date-time's Duration class also allows seconds to
be negative instead of storing a separate flag:

The nanoseconds are always non-negative, so a duration of negative
half a second would be stored as -1 seconds and 500,000,000
nanoseconds.

Best regards,
Paul

Hi Paul

I don't think I've seen your name on the list before. If this indeed your first email here: Welcome and thank you for contributing!

Am 2026-06-22 20:52, schrieb Paweł Kraśnicki:

For reference, brick/date-time's Duration class also allows seconds to
be negative instead of storing a separate flag:

Note that brick/date-time is as per the README (very) closely modeled after Java’s implementation, except for necessary differences to fit the API in PHP:

This component follows an important part of the JSR 310 (Date and Time API) specification from Java. Don't expect an exact match of class and method names though, as a number of differences exist for technical or practical reasons.

So taking inspiration from that implementation is effectively taking inspiration from Java’s implementation.

The nanoseconds are always non-negative, so a duration of negative
half a second would be stored as -1 seconds and 500,000,000
nanoseconds.

Yes, this matches Java’s implementation. I've explained in php.internals: Re: [RFC] Duration class, why I don’t consider this representation to be intuitive or easy to use.

Best regards
Tim Düsterhus

Hi

On 6/22/26 17:34, Andreas Heigl wrote:

I just think we do not need a separate "Duration"
and later a "Time" class when they both - in essence - hold the same
information.

The Internals mailing list is not the correct location to debate philosophical questions. Humans consider `Duration`s and `Time`s to be different concepts. The difference between “meeting is (in/at) 10:00” is the difference between `Duration` and `Time`. If you conflate those, you end up being late. Similarly if I call `sleep(new Time(0, 10, 0))`, I'm expecting the process to sleep until 10 past whatever midnight comes next, not for 10 minutes.

Best regards
Tim Düsterhus

On Thu, Jun 18, 2026, 06:49 Tim Düsterhus <tim@bastelstu.be> wrote:

Hi

Derick and I are proposing the introduction of a new Time\Duration
class to represent “stop-watch” or “egg-timer” durations to improve the
developer experience for APIs taking a timeout. We are specifically
targeting PHP 8.6 for this RFC, since part of the motivation is
improving the API of the new “Polling API” that already landed in PHP
8.6 (https://wiki.php.net/rfc/poll_api) before the “backwards
compatibility” door closes with the feature freeze in two months.

This RFC is also intended to be a first part of a modernized date and
time API in PHP, while being useful on its own. To that extent and given
the deadline we hope to make, the proposed API is intentionally minimal
and focused on functionality that we are relatively certain to:

  1. Be correct, or
  2. be requirement for future additions that cannot later be added
    without breaking compatibility.

We would therefore ask to keep the discussion focused on actual issues
rather than additional “convenience functionality” that might require
extensive discussion or thought.

All that said, you can find the RFC at:
https://wiki.php.net/rfc/duration_class. It hopefully includes all the
important explanation and also provides a rationale as to why we made
the design decisions we made.

Best regards
Tim Düsterhus

Hi Tim and Derick, I like where you’re going with this, but I have a couple of notes:

  1. Are there other classes planned for the \Time namespace? I think we should avoid using root namespaces and instead decide on a common namespace, so something like \Std\Time or \Php\Time. Although I realized that topic is a bit above this particular RFC, but worth considering for internals as more root namespaces are added.

  2. Since the class is marked final (why?), I would recommend we also create a DurationInterface and use that as the type wherever it is used. This would alleviate some of the concerns people have with making it final. For example, see popular packages like \Carbon\Carbon extends \DateTime and implements \DateTimeInterface.

Thanks,
Peter

Hi Tim, hey all

On 22.06.26 22:31, Tim Düsterhus wrote:

Hi

On 6/22/26 17:34, Andreas Heigl wrote:

I just think we do not need a separate "Duration"
and later a "Time" class when they both - in essence - hold the same
information.

The Internals mailing list is not the correct location to debate philosophical questions. Humans consider `Duration`s and `Time`s to be different concepts. The difference between “meeting is (in/at) 10:00” is the difference between `Duration` and `Time`. If you conflate those, you end up being late. Similarly if I call `sleep(new Time(0, 10, 0))`, I'm expecting the process to sleep until 10 past whatever midnight comes next, not for 10 minutes.

I don't see this as a philosophical question but a question of Naming.

To take your example: A meeting is never at 10:00. But at 10:00 on date X at location Y. The 10:00 part is merely one part of a meeting-time.

*If* you want philosophical questions (and yes, I believe the internals mailinglist is also the place to tackle these - as uncomfortable as they might be) then I would argue that the "Duration" you have in mind is actually not a duration but merely a ValueObject for SI-Seconds with a nanosecond resolution. After all a duration can also be "5 years". But that is explicitly excluded. Why? After all the second was in 1956 defined as 1/31,556,925.9747th of a year.

So from a philosophical point of view: Why should a *duration* only be definable in units that can (more or less relaibly) be converted into seconds?

But `sleep(new Seconds(12, 123456789))` or `sleep(Seconds::fromHours(12,34,45,123456789))` sounds like exactly what one expects.

That this `Seconds` class can then be reused later in an improved DateTime library is only a sidenote.

The idea that `Time` is midnight-based is but a western european assumption based on the gregorian calendaring system. As long as the reference-point isn't defined, a `Time` is just a collection of hours, minutes and seconds. Similar to a Money object that is just a collection of numbers and units that is in itself totally value-less as long as you don't add a currency. And in both cases the number of units that make up the next level can be different depending on the reference-point.

*That* is for me a philosophical question.

Anyhow.

It's not a hill I'll die upon.

It's just my 0.02€

Cheers

Andreas

Best regards
Tim Düsterhus

--
                                                               ,
                                                              (o o)
+---------------------------------------------------------ooO-(_)-Ooo-+
| Andreas Heigl |
| mailto:andreas@heigl.org N 50°22'59.5" E 08°23'58" |
| https://andreas.heigl.org |
+---------------------------------------------------------------------+
| https://hei.gl/appointmentwithandreas |
+---------------------------------------------------------------------+
| GPG-Key: https://hei.gl/keyandreasheiglorg |
+---------------------------------------------------------------------+

On 22.6.2026 19:04 CEST Tim Düsterhus <tim@bastelstu.be> wrote:

Hi

Am 2026-06-22 09:01, schrieb marc@mabe.berlin:
> * Go (time package - time - Go Packages)
> Just int64 of nanoseconds - negative durations are a negative number

A single integer doesn't work for PHP (see my reply to John).

> * Java
> (jdk8u-jdk/src/share/classes/java/time/Duration.java at master · frohoff/jdk8u-jdk · GitHub)
> Seconds + nanoseconds approachwithout negative flag. Negative durations
> are represented by a negative number of seconds while nanoseconds are
> guarantied between >= 0 and < NANOS_PER SECOND

I tested their implementation and consider their representation of
negative durations to be unintuitive. Using milliseconds instead of
nanoseconds for readability of this example:

A duration of negative 500 milliseconds is effectively represented as
(seconds: -1, milliseconds: 500), which can easily be confused with
negative 1.5 seconds when “naively” using each component. One needs to
calculate `$seconds + 1_000 * $milliseconds` to obtain the correct
meaning, which in my opinion is an indicator that the components
shouldn't have been split in the first place. Or one always needs to
remember that negative seconds are “off by one”.

What you feel unintuitive is just addition of two numbers in different units "$seconds + $nanoseconds * 1_000_000_000", which for me feels very natural.

With the negative flag it's more complicated "($negative ? -1 : 1) * $seconds + $nanoseconds * 1_000_000_000" to obtain the correct meaning.

I could not find any wide used implementation of durations that has such a design decision of separating out the negativity. I don't think it's a good approach adding it as-is directly to PHP core instead of taking one of the well established approaches.

In the RFC you say

Negative durations are represented by an explicit $negative property. This makes it easy to deal with absolute values by just ignoring the value of $negative.

That's just not true - you can't simply ignore the sign you have to deal with it no matter what. it's changing your calculations, it'g getting rejected on passing it to other functions.
On the same time the current API makes it harder to deal with negative durations as they can not be constructed directly and are not allowed as operator arguments.

I totally get the reasoning behind Rust's choice to make it fully unsigned - no negative durations.

Thinking more about that I see only two cases for negative durations:
1. result of calculation
2. difference of two points in times with direction

If we agree on "negative durations are meaningless" then we can disallow them (throw) and for the second case we calculate the distance.

> Or we support negative durations but than it should not be special and
> following "normal" math. If you need an absolute duration make sure
> it's positive ... Adding a `abs()` / `absolute()` method (like in
> Java).

The proposal in the RFC *is* following “normal math”. All the methods
correctly take negative values into account. A sign-magnitude
representation is also by no means uncommon, IEEE-754 floating points
are also using a sign-magnitude representation. Personally I find
sign-magnitude to be the leak awkward given the (necessary) second +
subsecond split.

The sign bit already exists in signed integers - PHP does not have unsigned int's.

> Another small note in the naming of `$seconds` and `$nanoseconds` ...
> The one is the total number, while the other one is the fraction of the
> unit. If we later on want to add more helper like total number of x and
> more fraction of different units we have ambiguity naming here.
> -> I would rename `$seconds` into `$totalSeconds` so maybe later on we
> can add `$seconds { get => $totalSeconds % 60 }

I haven't yet checked with Derick on this, but given the third paragraph
of the “Design Considerations” and Rust’s / Java’s implementation which
also use $seconds, I would disagree here. The same issue would also
exist when $milliseconds and $microsecond accessors would be added,
because $nanoseconds would then need to be restricted to 1_000 using the
same argument. In my opinion this kind of “convenience” accessors are
better left to explicit and clearly named methods.

Java does not have property hooks (as far as I know) but PHP now has.
That's very powerful and makes it possible to hide implementation details without exposing everything as getters.

$duration->seconds // 0-59
$duration->totalSeconds
$duration->nanoseconds // 0 - 999_999_999
$duration->totalNanoseconds
$duration->milliseconds // 0 - 999
$duration->totalMilliseconds
// maybe later
$duration->picoseconds // 0 - 999_999_999_999
$duration->totalPicosecond

As a user you don't need to care that much of the internal representation.

Best regards
Tim Düsterhus

Hi

Am 2026-06-23 02:36, schrieb Peter Stalman:

1. Are there other classes planned for the `\Time` namespace? I think we

Yes, see also the “Future Scope” section.

should avoid using root namespaces and instead decide on a common
namespace, so something like `\Std\Time` or `\Php\Time`. Although I
realized that topic is a bit above this particular RFC, but worth
considering for internals as more root namespaces are added.

The use of the namespace is in line with the namespace policy that you can find at policies/coding-standards-and-naming.rst at main · php/policies · GitHub. It was (almost) unanimously accepted in 2021: PHP: rfc:namespaces_in_bundled_extensions.

This policy has been the basis of Random\* (8.2), Dom\* (8.4), Uri\* (8.5).

2. Since the class is marked final (why?), I would recommend we also create
a `DurationInterface` and use _that_ as the type wherever it is used. This
would alleviate some of the concerns people have with making it final. For
example, see popular packages like `\Carbon\Carbon` extends `\DateTime` and
implements `\DateTimeInterface`.

It's a value object, there is only one reasonable implementation that preserves all the invariants that users expect. This topic has previously also been discussed as part of the URI extension (somewhere here: php.internals: [RFC] [Discussion] Add WHATWG compliant URL parsing API).

Best regards
Tim Düsterhus

Hi

Am 2026-06-18 15:47, schrieb Tim Düsterhus:

All that said, you can find the RFC at: PHP: rfc:duration_class. It hopefully includes all the important explanation and also provides a rationale as to why we made the design decisions we made.

Derick and I had a first discussion about the feedback so far and I have made the following changes to the RFC earlier today.

1. Derick looked up the official name of the `PT30M` format in ISO-8601. It's called “Duration”. We have thus renamed the ->fromIso8601String() method to ->fromIso8601DurationString(), given the discussion in this subthread: php.internals: Re: [RFC] Duration class

2. We added an `->absolute()` method to return a non-negative Duration object with the same magnitude.

3. We limited the duration to 9223372035999999999 nanoseconds in order to enable a possible future `->getTotalNanoseconds()` method if / when PHP is not limited to 32-bit integers. This is sufficient to represent a duration of 292 years, which matches the range of Golang and hopefully should be plenty. As with any other restriction: It's always possible to lift it later if it turns out that the future scope will not actually be relevant and if it causes issues.

4. Tying into (3), but also a pre-existing issue when using `::fromHours(PHP_INT_MAX)`: We defined that overflowing the allowed range will result in a Time\TimeException instead of using saturating math (i.e. clamping to the maximum).

5. And to enable (4) we added the Time\TimeException base exception for the Time namespace. Additional child exceptions are planned when adding more classes to the “new date and time API”, but for now we didn't want to decide on whether:

DurationOverflowException extends DurationException extends TimeException extends Exception
or
DurationOverflowException extends OverflowException extends TimeException extends Exception

would be the correct choice (i.e. if we should group by “component” or by “type of Exception”).

--------------

There are some more things where we haven't yet made a decision. I'll follow up on those later.

Best regards
Tim Düsterhus

Hi

Am 2026-06-22 20:52, schrieb Larry Garfield:

> However, I would advise against introducing Duration::add and
> Duration::sub
> methods. Instead, I would recommend providing a single method,
> Duration::sum(Duration
> ...$durations): self.
>
> […]

[…]

I am 100% against a static method for adding durations. That's entirely pointless. Even if we're just using methods and not operators, $dur3 = $dur1->add($dur2) is the way to go.

Both Derick and I were receptive to the `Duration::sum()` suggestion.

One big benefit I am personally seeing is that it would allow to rename the `negate()`, `multiplyBy()`, and `divideBy()` methods to use “past tense” (is that the correct term?) to indicate that they return a copy, without requiring awkward naming like `added()`. `->sub()` is also technically redundant due to the existence of negative Durations, thus there is no need for a mirrored pair like multiplication / division, since the information can be encoded in the Duration object itself.

To avoid readers making assumptions, can you spell out your arguments against replacing `->add()` and `->sub()` by a unified `::sum()` method?

Best regards
Tim Düsterhus

Hi

Am 2026-06-23 08:19, schrieb marc@mabe.berlin:

What you feel unintuitive is just addition of two numbers in different units "$seconds + $nanoseconds * 1_000_000_000", which for me feels very natural.

No, what I feel is unintuitive is that the magnitude of the Duration will because *smaller* when the magnitude of the nanosecond value becomes *larger*. You could phrase it as “the representation of a single value as two values with different signs is unintuitive”. The different units are not the issue, the different sign is.

With the negative flag it's more complicated "($negative ? -1 : 1) * $seconds + $nanoseconds * 1_000_000_000" to obtain the correct meaning.

This example snippet is incorrect. The correct version would be: `($negative ? -1 : 1) * ($seconds + $nanoseconds * 1_000_000_000)` (with parentheses). In fact this snippet includes the snippet you consider to be “very natural”, it just makes the application of the sign explicit and effectively applies it to both components (instead of one component like Java does), which means that the magnitude of `$seconds` is a correct lower bound for the magnitude of the entire Duration.

To give a specific example in Java (Online Java Compiler - Programiz):

     import java.time.Duration;

     class Main {
         public static void main(String[] args) {
             Duration d = Duration.parse("PT-59.5S");
             System.out.println(d.getSeconds());
             System.out.println(d);
         }
     }

This will print:

     -60
     PT-59.5S

Just by looking at the “seconds” component it looks like the Duration is at least 1 minute long. But it isn't, because if the second component is negative, the larger the value of the nanosecond component is, the shorter the duration will be.

In the RFC you say

Negative durations are represented by an explicit $negative property. This makes it easy to deal with absolute values by just ignoring the value of $negative.

That's just not true - you can't simply ignore the sign you have to deal with it no matter what. it's changing your calculations, it'g getting rejected on passing it to other functions.
On the same time the current API makes it harder to deal with negative durations as they can not be constructed directly and are not allowed as operator arguments.

The statement is correct, if you are interested in absolute values, you have the magnitude right there and can ignore the `$negative` flag. But to make it even easier, we added the `->absolute()` method that just clears the sign. Simply replacing seconds by abs(seconds) in Java's representation would *not* be correct. In fact the OpenJDK implementation defers to a BigDecimal calculation to flip the sign, which is not an option for PHP, because the minimal PHP build doesn't include either GMP or bcmath.

I totally get the reasoning behind Rust's choice to make it fully unsigned - no negative durations.

Thinking more about that I see only two cases for negative durations:
1. result of calculation
2. difference of two points in times with direction

If we agree on "negative durations are meaningless" then we can disallow them (throw) and for the second case we calculate the distance.

I would generally agree that negative durations are conceptionally meaningless, but at the same think I consider it important that calculating the difference between two Instants should make the direction available as part of the same operation so that users don't also have to compare the the Instants separately.

Similarly, negative Durations would enable the `Duration::sum()` method suggested by Ignace, and would avoid making intermediate results fallible. As an example:

     $d = Duration::fromHours(1)->sub(Duration::fromSeconds(1)); // 59 minutes and 59 seconds
     $d->sub(Duration::fromMinutes(60))->add(Duration::fromSeconds(30)); // subtract 59 minutes and 30 seconds

Representing a subtraction of 59 minutes and 30 seconds as a subtraction of 60 minutes and an addition of 30 seconds would not reliably be possible without negative Durations, which I would also consider to be unexpected. Users would need to write:

     $d->sub(Duration::fromMinutes(60)->sub(Duration::fromSeconds(30)); // subtract 59 minutes and 30 seconds

for it to work, which might not match their intuition of tackling the problem.

Java does not have property hooks (as far as I know) but PHP now has.
That's very powerful and makes it possible to hide implementation details without exposing everything as getters.

$duration->seconds // 0-59
$duration->totalSeconds
$duration->nanoseconds // 0 - 999_999_999
$duration->totalNanoseconds
$duration->milliseconds // 0 - 999
$duration->totalMilliseconds
// maybe later
$duration->picoseconds // 0 - 999_999_999_999
$duration->totalPicosecond

As a user you don't need to care that much of the internal representation.

The currently proposed properties are intended to be used by the user directly. That's why they are public.

Best regards
Tim Düsterhus

Hi,

On Mon, 22 Jun 2026 at 21:03, Tim Düsterhus <tim@bastelstu.be> wrote:

I don't think I've seen your name on the list before. If this indeed
your first email here: Welcome and thank you for contributing!

Thank you for the warm welcome!

I have a suggestion for the RFC: treating negative durations as normal
rather than unusual; specifically, allowing negative integers in the
constructors.

While it's true that waiting for a negative amount of time is
meaningless, the RFC notes that the Duration class will eventually do
double duty: it will be used both as a timeout and as a "time delta"
for time arithmetic. In the latter context, negative durations are
mathematically sound and practically useful. For example, here's a use
case from one of my past projects, translated to PHP from memory:

/**
* Truncates `$events` so that they fit within the daylight window.
* The allowed time window (dawn to dusk) is expanded on both ends
* by `$daytimePadding`, which can be negative to shrink the window.
* Events that would fall entirely outside this window are discarded.
*
* @param list<Event> $events
* @return list<Event>
*/
function clampEventsToDaylight(
    DayAstronomicalData $day,
    Duration $daytimePadding,
    array $events,
): array

Instead of preventing the construction of some invalid timeouts at the
cost of making time arithmetic more clunky, it seems better to keep
the constructors flexible and let functions that take Duration as an
argument enforce their own rules when appropriate. That's the way
other languages tend to handle the issue. For example,
Duration.ofHours(-2) is legal in Java and Temporal.Duration.from({
hours: -2 }) is legal in JavaScript. I'm less familiar with Rust, but
from what I've read, its standard Duration type forbids negative
values specifically because Rust doesn't come with a full date/time
API and Duration is used for timeouts and system timers. The most
popular Rust library for complex date/time handling, chrono, provides
a TimeDelta type (renamed from Duration) that accepts negative values
in its constructors.

Best regards,
Paul

Hi

Am 2026-06-25 18:26, schrieb Paweł Kraśnicki:

/**
* Truncates `$events` so that they fit within the daylight window.
* The allowed time window (dawn to dusk) is expanded on both ends
* by `$daytimePadding`, which can be negative to shrink the window.
* Events that would fall entirely outside this window are discarded.
*
* @param list<Event> $events
* @return list<Event>
*/
function clampEventsToDaylight(
    DayAstronomicalData $day,
    Duration $daytimePadding,
    array $events,
): array

Thank you for providing that example. The restriction was primarily added as a safety: Removing restrictions later is always possible, adding them is not. So absent a good use-case choosing the “error” option is safer. Given you have provided an example, it makes sense to me to lift the restriction.

For the `fromSeconds()` constructor it's not completely obvious how to handle negative values there (see the discussion with Marc). I think requiring both parameters to have the same sign would be the correct solution here, since this will provide for an intuitive representation of magnitude (see also below). What do you think?

I'll also discuss this with Derick before adjusting the RFC.

popular Rust library for complex date/time handling, chrono, provides
a TimeDelta type (renamed from Duration) that accepts negative values
in its constructors.

Thank you for that reference. I have also tested how that implementation represents negative values: Internally it represents the value like the Java implementation, thus negative 1_500 milliseconds is (-2) seconds + 500 milliseconds. But this internal representation is not accessible. Using the getters negative 1_500 milliseconds is represented as (-1) seconds + (-500) milliseconds:

     use chrono; // 0.4.44

     fn main() {
         use chrono::TimeDelta;

         let duration = TimeDelta::milliseconds(-1500);
         println!("{:#?}", duration);
         println!("{:#?}", duration.num_seconds());
         println!("{:#?}", duration.subsec_nanos());
     }

Best regards
Tim Düsterhus

Hi

Am 2026-06-22 19:37, schrieb Tim Düsterhus:

Oof. Naming.
The only term I can find that is specifically about dividing one duration by another is "beat", but that's definitely domain-specific.

'periodCount' or 'countPeriods' come to mind. Since in 'n/d' we're counting how many times duration 'd' occurs during duration 'n' we're basically multiplying the duration by frequency '1/d'; the inverse of the frequency - 'd' itself - is called the period. Which word is also used for "named subdivision of historical time", "class-sized subdivision of a school day", and so on.

The biggest hangup with that name is that it doesn't explicitly state that it returns the remainder as well. periodParts? periodDivide?

Thank you for the suggestions. I quite like period. I'll also check with Derick if he has any good ideas (and regarding the Duration divided by Duration bit in general).

Derick mentioned how “period” might not be an ideal term, because it's something that the ISO standard moved away from. After having chatted with him about this, I would agree that it would possibly be confusing to mix “technical terminology” with “colloquial terminology” and might raise questions about what the difference between “Duration” (class name) and “Period” (method name) is when other languages call “Period” what we call “Duration”.

For `chunked…()` as suggested elsewhere in the thread, the implied return type would be `list<Duration>` (i.e. Duration::fromMinutes(50)->chunkedBy(Duration::fromMinutes(20)) === [Duration::fromMinutes(20), Duration::fromMinutes(20), Duration::fromMinutes(10)]`), since that would be consistent with `array_chunk()` and chunking in other programming languages.

Unless we come up with a genius idea, we might defer both divisions to later and ship PHP 8.6 with just addition, subtraction and multiplication - for that the question of add/sub vs sum() would still be open (see: php.internals: Re: [RFC] Duration class).

Best regards
Tim Düsterhus

On Mon, Jun 29, 2026 at 10:16 PM Tim Düsterhus <tim@bastelstu.be> wrote:

Hi

Am 2026-06-22 19:37, schrieb Tim Düsterhus:

Oof. Naming.
The only term I can find that is specifically about dividing one
duration by another is “beat”, but that’s definitely domain-specific.

‘periodCount’ or ‘countPeriods’ come to mind. Since in ‘n/d’ we’re
counting how many times duration ‘d’ occurs during duration ‘n’ we’re
basically multiplying the duration by frequency ‘1/d’; the inverse of
the frequency - ‘d’ itself - is called the period. Which word is also
used for “named subdivision of historical time”, “class-sized
subdivision of a school day”, and so on.

The biggest hangup with that name is that it doesn’t explicitly state
that it returns the remainder as well. periodParts? periodDivide?

Thank you for the suggestions. I quite like period. I’ll also check
with Derick if he has any good ideas (and regarding the Duration
divided by Duration bit in general).

Derick mentioned how “period” might not be an ideal term, because it’s
something that the ISO standard moved away from. After having chatted
with him about this, I would agree that it would possibly be confusing
to mix “technical terminology” with “colloquial terminology” and might
raise questions about what the difference between “Duration” (class
name) and “Period” (method name) is when other languages call “Period”
what we call “Duration”.

For chunked…() as suggested elsewhere in the thread, the implied
return type would be list<Duration> (i.e.
Duration::fromMinutes(50)->chunkedBy(Duration::fromMinutes(20)) ===
[Duration::fromMinutes(20), Duration::fromMinutes(20),
Duration::fromMinutes(10)]), since that would be consistent with array_chunk()` and chunking in other programming languages.

Unless we come up with a genius idea, we might defer both divisions to
later and ship PHP 8.6 with just addition, subtraction and
multiplication - for that the question of add/sub vs sum() would still
be open (see: https://news-web.php.net/php.internals/131504).

Best regards
Tim Düsterhus

How about this

Duration::fromHours(5)->dividedBy(5); //returns Duration::fromHours(1);
and
Duration::fromHours(50->dividedInto(Duration::fromHours(1)); // returns [‘factor’ => 5, ‘remainder’ => Duration::fromSeconds(0)];

Duration::dividedInto in my example returns an array but I would have no issue if it would return a DTO instead.
This way the naming seems easy to remember IMHO

  • “by” → integer,
  • “into” → duration

and it reads naturally I think.

Best regards,
Ignace

Hi

Am 2026-06-29 22:38, schrieb ignace nyamagana butera:

How about this

Duration::fromHours(5)->dividedBy(5); //returns Duration::fromHours(1);
and
Duration::fromHours(50->dividedInto(Duration::fromHours(1)); // returns
['factor' => 5, 'remainder' => Duration::fromSeconds(0)];

Duration::dividedInto in my example returns an array but I would have no
issue if it would return a DTO instead.
This way the naming seems easy to remember IMHO

- "by" -> integer,
- "into" -> duration

and it reads naturally I think.

That is a great suggestion, thank you. I agree this reads pretty naturally, because the “divide into” requires the presence of a “unit” to feel complete, whereas the “divide by” doesn't. Derick also confirmed that the suggestion sounds reasonable to him.

To remain consistent with `bcdivmod()`, the function should return a 2-tuple (i.e. `array{0: int, 1: Duration}`). This makes destructuring very convenient:

     [$fullDurations, $remainder] = $oneHour->divideInto($sevenMinutes);

see php.internals: Re: [RFC] [Discussion] Add bcdivmod to BCMath for the previous discussion.

I don't plan to add `divideInto()` to the initial set of methods in PHP 8.6, but would be open to it if anyone feels having it right away is useful or necessary. Please speak up in that case. And please also do if you disagree with the divideBy() + divideInto() pair.

Best regards
Tim Düsterhus