[PHP-DEV] [RFC] Duration class

Hi

Am 2026-06-19 19:12, schrieb Marc B.:

Omg, I wrote in a wrong way - sorry.
I mean the RFC just mentions "The Time\Duration class will also implement internal “comparison handlers”, which means that direct comparisons with operators such as < will work".
So there are no other operators defined but there are methods defined for it. I would expect these defined as operators as well.

See my reply to Larry.

Actually, for the division, there are two use cases but the method supports only one.
6s / 2 = 3s
6s / 2s = 3

This is a good point, though. While we're probably not going to implement Duration/Duration from the start, we should account for that in the naming of the Duration/int method. Do you have any suggestions? Rust’s std::time::Duration calls these `div_duration_<variant>`. Java doesn't have it, but could technically just use method overloading. And in Golang it will just implicitly work, because `time.Duration` there is just a 64-bit integer without any “unit”.

To support Duration/Duration->int it would also be necessary to restrict the entire range to a signed 64-bit integer of nanoseconds, because overwise `Duration/Duration->int` might overflow on 64-bit systems. This is already something I considered doing based on off-list feedback I've received (and lifting this restrictions later is always possible, so better play safe). I'll discuss this with Derick. We'll also need to define the overflow behavior in general (e.g. `Duration::fromHours(PHP_INT_MAX)->multiplyBy(PHP_INT_MAX)`).

5. Did you thought of supporting float on the initializer methods?

Floats introduce all the complexity and lack of precision of, well, floats. Best to avoid them.

If you have a float at hand you already have the lack of precision. Forcing the user to cast to int before does not make anything better. It's even another source of complexity the user have to deal with. If this gets handled on the initializer it will reduce the complexity for the user.

See my reply to Larry and Seifeddine.

Best regards
Tim Düsterhus

On Fri, Jun 19, 2026 at 7:15 PM Marc B. marc@mabe.berlin wrote:

Omg, I wrote in a wrong way - sorry.
I mean the RFC just mentions “The Time\Duration class will also implement internal “comparison handlers”, which means that direct comparisons with operators such as < will work”.
So there are no other operators defined but there are methods defined for it. I would expect these defined as operators as well.

Actually, for the division, there are two use cases but the method supports only one.
6s / 2 = 3s
6s / 2s = 3

Hi Tim and Marc

I would go with two methods if possible :

Duration::countOf(Duration $duration): int; // which would act a bit like intdiv
Duration::remainder(Duration $duration): Duration; // which would return the remainder as a Duration instance

But that’s just a thought.

Also why are there some restrictions on the factor argument of divideBy and multiplyBy to only accept positive integers ?
At least that is what I understood when I quickly checked the proof of concept. Since Duration are signed instances that restriction seems strange ?

Best regards,
Ignace

On 2026-06-21 03:50, ignace nyamagana butera wrote:

Hi Tim and Marc

I would go with two methods if possible :

Duration::countOf(Duration $duration): int; // which would act a bit like intdiv
Duration::remainder(Duration $duration): Duration; // which would return the remainder as a Duration instance

But that's just a thought.

And BCMath just recently picked up a function to do both of those in a single call, since calculating one gives you the other for free and frequently users want both.

Le dim. 21 juin 2026, 01:34, Morgan <weedpacket@varteg.nz> a écrit :

On 2026-06-21 03:50, ignace nyamagana butera wrote:

Hi Tim and Marc

I would go with two methods if possible :

Duration::countOf(Duration $duration): int; // which would act a bit
like intdiv
Duration::remainder(Duration $duration): Duration; // which would
return the remainder as a Duration instance

But that’s just a thought.

And BCMath just recently picked up a function to do both of those in a
single call, since calculating one gives you the other for free and
frequently users want both.

Indeed Duration::divmod is the way forward just wondering about the return type. Should it be an array or a immutable DTO with .2 properties quotient and remainder?

Best regards,
Ignace

Hi

On 6/20/26 17:50, ignace nyamagana butera wrote:

Also why are there some restrictions on the factor argument of divideBy and
multiplyBy to only accept positive integers ?
At least that is what I understood when I quickly checked the proof of
concept. Since Duration are signed instances that restriction seems strange
?

Enforcing positive values for multiplication and division means that flipping the sign of a Duration is a deliberate action and not just something that happens as part of another operation.

See also the last paragraph in the “Design considerations” section.

Best regards
Tim Düsterhus

Hi

On 6/21/26 11:18, ignace nyamagana butera wrote:

Indeed Duration::divmod is the way forward just wondering about the return
type. Should it be an array or a immutable DTO with .2 properties quotient
and remainder?

For bcdivmod it was a deliberate choice to make the result a 2-tuple, with the expectation that array destructuring is used to consume the return value.

Operation-wise I would agree that “div mod” is the correct thing to do, but I don't think that calling Duration/Duration->(int, Duration) literally divmod is an intuitive API when there's also the other division that does Duration/int->Duration.

Best regards
Tim Düsterhus

On Sun, Jun 21, 2026 at 3:57 PM Tim Düsterhus <tim@bastelstu.be> wrote:

Hi

On 6/21/26 11:18, ignace nyamagana butera wrote:

Indeed Duration::divmod is the way forward just wondering about the return
type. Should it be an array or a immutable DTO with .2 properties quotient
and remainder?

For bcdivmod it was a deliberate choice to make the result a 2-tuple,
with the expectation that array destructuring is used to consume the
return value.

Operation-wise I would agree that “div mod” is the correct thing to do,
but I don’t think that calling Duration/Duration->(int, Duration)
literally divmod is an intuitive API when there’s also the other
division that does Duration/int->Duration.

Best regards
Tim Düsterhus

Then maybe we could go with chunkedBy or decomposedBy ?
These are just propositions, naming things is hard

Best regards
Ignace

On 2026-06-22 01:57, Tim Düsterhus wrote:

Hi

Operation-wise I would agree that “div mod” is the correct thing to do, but I don't think that calling Duration/Duration->(int, Duration) literally divmod is an intuitive API when there's also the other division that does Duration/int->Duration.

Best regards
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?

Hi Tim,

On 21.6.2026 15:52 CEST Tim Düsterhus <tim@bastelstu.be> wrote:

Hi

On 6/20/26 17:50, ignace nyamagana butera wrote:
> Also why are there some restrictions on the factor argument of divideBy and
> multiplyBy to only accept positive integers ?
> At least that is what I understood when I quickly checked the proof of
> concept. Since Duration are signed instances that restriction seems strange
> ?

Enforcing positive values for multiplication and division means that
flipping the sign of a Duration is a deliberate action and not just
something that happens as part of another operation.

See also the last paragraph in the “Design considerations” section.

I don't agree on this part of the "Design considerations".

The split between seconds and nanoseconds is a reasonable approach but the negative flag is not. Especially that it's not just an implementation detail. This makes the Duration class a very special thing from any other "number of unit" that needs special care on using it.

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

* Go (time package - time - Go Packages)
Just int64 of nanoseconds - negative durations are a negative number

* 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

* Rust (time.rs - source)
Seconds + nanoseconds approach without negative flag. Both numbers are unsigned - so no support of negative durations.

So either we follow Rust's approach by defining "Negative durations are meaningless" then they should not be supported at all - Means no negative flag and guarantied positive values.
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).

Currently it looks like you tried a mix of "negative durations are meaningless" but on the same time you want to support negative durations resulting wired middle ground.

To define which way to go I think we need to be clear on what the use cases are and how other use cases will be handled later on. So is that class only for stop-watch & timeouts cases then maybe Rust's approach makes more sense but if we want this class to be used more generically like "diff in seconds of point in time X and Y with direction" a more general duration make more sense. Looking at Java again - The Period class does not support times - just calendar units.

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 }

Regards,
Marc

Best regards
Tim Düsterhus

On 22.6.2026 01:07 CEST Morgan <weedpacket@varteg.nz> wrote:

On 2026-06-22 01:57, Tim Düsterhus wrote:
> Hi
>
> Operation-wise I would agree that “div mod” is the correct thing to do,
> but I don't think that calling Duration/Duration->(int, Duration)
> literally divmod is an intuitive API when there's also the other
> division that does Duration/int->Duration.
>
> Best regards
> 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?

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`

Together with a modulo operator method (not needed as first MVP) `moduloBy(int|float|self $divisor): self`:
* `THIS(duration) modulo OTHER(duration)`: What is the rest of the integer division of this duration by another duration?
  - e.g. `5.5s mod 1.2s = 0.7s` as 1.2s fits 4 times into 5.5s and 0.7s is the remainder.
* `THIS(duration) modulo NUMBER`: What is the remainder after dividing this duration into pieces?
  - e.g. `5.5s mod 750 = 250ns` as 5.5s divided into 750 pieces gives you 7,333,333ns for each piece with a reminder of 250ns. Means `5.5s = 750 * 7333333ns + 250ns`.

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.

Regards,
Marc

Hi Tim

Thank you and Derick for the work on this proposal.

On 18.06.26 15:47, Tim Düsterhus wrote:

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.

Some thoughts:

The Time\Duration class will also implement internal “comparison handlers”, which means that direct comparisons with operators such as < will work.

Can you explicitly spell out that this does not apply to arithmetic operators?

One thing worth specifying is how you're planning on handling the passing of Duration objects to APIs that do not support certain precisions. For example, PHPs sleep() is a thin wrapper around system sleep(), which only accepts seconds. This would internally need to be re-routed to usleep(), but even then usleep() only supports microseconds. What happens when we pass a duration of that scale to sleep()? Throw? Truncate? Round? (Let's disregard the existence of nanosleep() for the sake of the argument.)

The biggest concern for me is having to instantiate an object for all calls that take a duration, which seems like mostly unnecessary overhead. This is partially resolvable by adding support for storing immutable objects in shared memory, which works here because the class is readonly.

Ilija

Hi

Am 2026-06-22 10:42, schrieb Ilija Tovilo:

The Time\Duration class will also implement internal “comparison handlers”, which means that direct comparisons with operators such as < will work.

Can you explicitly spell out that this does not apply to arithmetic operators?

Yes. I added “Other operators (such as + for addition) will not be overloaded.”.

One thing worth specifying is how you're planning on handling the passing of Duration objects to APIs that do not support certain precisions. For example, PHPs sleep() is a thin wrapper around system sleep(), which only accepts seconds. This would internally need to be re-routed to usleep(), but even then usleep() only supports microseconds. What happens when we pass a duration of that scale to sleep()? Throw? Truncate? Round? (Let's disregard the existence of nanosleep() for the sake of the argument.)

The behavior would be left up to the API in question, because it is in the best position to determine which makes the most sense. As an example, a function like `sleep()` cannot sleep for exact durations anyways, because the computer might be busy running another process when it is supposed to wake up. Here it might make sense to always round *up* to the nearest microsecond, which would be in line with existing expectations of sleeping for *at least* the given amount of time.

If something is fundamentally limited to a second-granularity - which I expect to be comparatively rare nowadays - it might make sense to just not accept a `Time\Duration` object and instead keep using a plain integer - which still in a way be compatible with `Time\Duration` by the developer explicitly only retrieving `$duration->seconds` and passing it along.

In other situations throwing might be appropriate, particularly when the `Time\Duration` is “too long” to be meaningfully handled (e.g. `Time\Duration::fromSeconds(PHP_INT_MAX)`).

Within the internal API we plan to make it easy to turn a `Time\Duration` object into a `struct timespec`, which is a “lossless” conversion and thus it would immediately compatible with anything taking a `struct timespec`.

In the future I could imagine a userland method that takes a `RoundingMode` and allow to round a Duration to millis / micros / seconds.

I have added a short “Passing Duration objects to APIs unable to handle nanosecond precision” section summarizing the above.

The biggest concern for me is having to instantiate an object for all calls that take a duration, which seems like mostly unnecessary overhead. This is partially resolvable by adding support for storing immutable objects in shared memory, which works here because the class is readonly.

Given the class only exposes named constructors, it should be feasible to include a small LRU cache to efficiently handle situations like the following:

     for (;:wink: {
         $watchers = $poll->wait(Duration::fromMilliseconds(500));

         foreach ($watchers as $watcher) {
             /* … */
         }
     }

where the same timeout is repeatedly being used. I consider this kind of optimization to be a implementation detail that is not worth spelling out in the RFC, but it is good that you raised it for discussion.

Best regards
Tim Düsterhus

On Sat, Jun 20, 2026, at 5:52 AM, Tim Düsterhus wrote:

My main pushback, I think, is the fromIso8601String() method, which is
not at all self-descriptive. Presuming it means the format accepted by
DatePeriod (give or take bugs), that's not self-evident anywhere in the
RFC, or the method name.

The doc comment for the method explains “Parse a ISO-8601 period.” and
even the automated AI summary in Google (almost) correctly tells right
away “An ISO-8601 period (or duration) represents an amount of time
using the format P[n]Y[n]M[n]W[n]T[n]H[n]M[n]S.” There is a small
mistake in there in that `T` doesn't have a preceding `[n]`, but it
gives the correct idea of what is being expected there. And of course
all the search results point towards the same thing.

I can easily see people who aren't familiar with that period format
(which is, I suspect, most people) trying to use "5:23:44" or similar
human time formats instead, which will break. But that format actually
feels more useful, and is also defined somewhere in ISO8601 (as part of
the full date/time string if nothing else), so the method name is
ambiguous.

“break” is a strong word there. It will be rejected with a `ValueError`
as an invalid input.

I think we do need to come up with a better, more self-documenting name
for that operation, and consider if we also want a
fromHumanTimeString() that accepts "5:23"44" type strings.

Without checking with Derick, I would be open to renaming the method to
`fromIso8601PeriodString()`. While that would be quite long, (fuzzy)
auto-completion should handle that. And I suspect that constructor would
also be used comparatively rarely.

If that name makes sense to you, I'll discuss all the feedback (also
from the other mails) that I didn't outright reject myself with Derick
in bulk in the next days :slight_smile:

It's the Iso8601 in there that feels confusing. fromTimePeriod() or fromPeriodString() or something like that seems like it would be more reader-friendly.

I know we used RFC names in the Uri component, but in that case there's two competing specifications that we're trying to support so using those technical names in namespaces made sense. However, that's not generally true; including random spec IDs in method names is not helpful, and as far as I am aware there's only one type of "time period string" on the market that we need to worry about so there is no risk of the same type of confusion. (If there are others, please let me know.)

The “from human time” one I would reject outright, because of the
ambiguity with regard to how fractional seconds are represented. In the
wild I've sometime seen `.` as the separator between seconds and
fractional seconds, but also `:`, which means that the format would be
ambiguous. And I'm sure there are also going to be differences between
languages, just like some countries use 24h formats and others use 12h.
And then of course, what even is a “human time”. And with regard to the
flexibility in inputs that `strtotime()` accepts, is `"12 hours and 45
seconds"` a “human time”? Is `"12.5h"` a “human time”? Is `"500µs"` a
“human time” (notably the µ character is not in 7-bit ASCII)?

I can see this being punt-able to future scope, though I think an argument could be made for supporting "12 hours and 45 seconds" as a constructor type. Especially as it is supported in other parts of the current date/time API.

--Larry Garfield

On Mon, Jun 22, 2026 at 9:01 AM marc@mabe.berlin wrote:

Hi Tim,

On 21.6.2026 15:52 CEST Tim Düsterhus <tim@bastelstu.be> wrote:

Hi

On 6/20/26 17:50, ignace nyamagana butera wrote:

Also why are there some restrictions on the factor argument of divideBy and
multiplyBy to only accept positive integers ?
At least that is what I understood when I quickly checked the proof of
concept. Since Duration are signed instances that restriction seems strange
?

Enforcing positive values for multiplication and division means that
flipping the sign of a Duration is a deliberate action and not just
something that happens as part of another operation.

See also the last paragraph in the “Design considerations” section.

I don’t agree on this part of the “Design considerations”.

The split between seconds and nanoseconds is a reasonable approach but the negative flag is not. Especially that it’s not just an implementation detail. This makes the Duration class a very special thing from any other “number of unit” that needs special care on using it.

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

So either we follow Rust’s approach by defining “Negative durations are meaningless” then they should not be supported at all - Means no negative flag and guarantied positive values.
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).

Currently it looks like you tried a mix of “negative durations are meaningless” but on the same time you want to support negative durations resulting wired middle ground.

To define which way to go I think we need to be clear on what the use cases are and how other use cases will be handled later on. So is that class only for stop-watch & timeouts cases then maybe Rust’s approach makes more sense but if we want this class to be used more generically like “diff in seconds of point in time X and Y with direction” a more general duration make more sense. Looking at Java again - The Period class does not support times - just calendar units.

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 }

Regards,
Marc

Best regards
Tim Düsterhus

Hi Tim and Marc,

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,
);

Best regards,

Ignace

Hi

Am 2026-06-22 17:00, schrieb Larry Garfield:

I know we used RFC names in the Uri component, but in that case there's two competing specifications that we're trying to support so using those technical names in namespaces made sense. However, that's not generally true; including random spec IDs in method names is not helpful, and as far as I am aware there's only one type of "time period string" on the market that we need to worry about so there is no risk of the same type of confusion. (If there are others, please let me know.)

Golang’s `time.ParseDuration()` accepts stuff like `"1µs"`: time package - time - Go Packages. The `sleep` binary from GNU coreutils also accepts a (single) suffix. You can find the man page here: sleep(1) — coreutils — Debian testing — Debian Manpages

I would expect this kind of “informal format” to be much more common in use than an ISO-8601 period string, which will lead to users trying to do `Duration::fromPeriodString('5s')`. And then of course for those there is the ambiguity between “minute” and “month” with `Duration` only being able to support “minute”, but perhaps folks might want to try `5min` instead to disambiguate.

The `Iso8601` in the proposed method name *is* meaningful and important to steer users towards the correct specification.

Especially as it is supported in other parts of the current date/time API.

… and these “free-form human language” inputs are a common source of confusion in the current ext/date API. In any case, that's definitely not something for PHP 8.6.

Best regards
Tim Düsterhus

Hi All

On 22.06.26 16:36, Tim Düsterhus wrote:

Hi

Am 2026-06-22 10:42, schrieb Ilija Tovilo:

The Time\Duration class will also implement internal “comparison handlers”, which means that direct comparisons with operators such as < will work.

While reading this I asked myself why we need a specific Duration class...

What is the difference between

new Time(12, 23, 34.567890);

and

new Duration::fromIso8601String('PT12H23M34.567890S');

Both define a number of hours, minutes etc. The only "difference" is that we came to see a time as starting from a certain point in time. But that is in essence just a convention and the time component of a DateTime just represents the a duration.

Also the "limitation" to 24 hours is a convention (that gets blown away in DST situations. As do the 59 seconds with leapseconds)

If we want to think about an improved way of handling datetimes, why not introduce a Time-class that can later be used in combination with a Date class and a Timezone class to define a point in time (or without the Timezone to define a local DateTime).

After all we can already add more than 24 hours to a DateTimeInterface via `setTime` (see Online PHP editor | output for QZnZ2) - so there should be no issue with creating Time-objects with more than 24 hours...

Don't get me wrong! I like the idea of a dedicated class that holds a Time-based duration. I just think we do not need a separate "Duration" and later a "Time" class when they both - in essence - hold the same information.

Just my 0.02€

Cheers

Andreas
--
                                                               ,
                                                              (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 |
+---------------------------------------------------------------------+

Hi all,

On Jun 22, 2026, at 03:01, 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.

* Go (time package - time - Go Packages)
Just int64 of nanoseconds - negative durations are a negative number

* 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

* Rust (time.rs - source)
Seconds + nanoseconds approach without negative flag. Both numbers are unsigned - so no support of negative durations.

For additional point of reference, in Swift, Duration is internally represented as a 128-bit integer count of attoseconds (!), with accessors that return an Int128 attoseconds, or a (seconds, attoseconds) Int64 pair. With the pair accessor, negative durations are represented by both the seconds and attoseconds components being negative when nonzero. There are static helpers that provide initialization in terms of int and float seconds/milliseconds/microseconds/nanoseconds, but there is no float output; if one wants a float, one gets one of the int versions and does the relevant conversion.

Personally, I'm on the fence about negative durations, since it's an unphysical concept, but allowing negative values makes calculations easier. For example, by allowing (allowedDuration - actualDuration) where allowed < actual. The relevant Swift Evolution proposal says nothing explicitly about negative values, except through implication they're allowed since durations can be added and subtracted (and multiplied and divided).

Given that prior art, I would propose that the Duration class be changed to store 128-bit attosecond counts as well. Although no specific rationale is given in the Swift Evolution proposal, I expect that it's because modern cpus are capable of sub-nanosecond precision, which means nanosecond-precision is _already_ lossy today, and attoseconds are such a hilariously small unit of time that they allow for quite ample future-proofing. (1 attosecond == 10^-18 s; if I did the math right, a signed 128-bit attosecond count gives a time range of ±5.39 trillion years, over 750x the age of the universe.)

Even if we deem attoseconds are too small and go for some unit between nanoseconds and attoseconds, it should still be possible to represent negative values.

-John

Hi

Am 2026-06-22 17:35, schrieb John Bafford:

Personally, I'm on the fence about negative durations, since it's an unphysical concept, but allowing negative values makes calculations easier. For example, by allowing (allowedDuration - actualDuration) where allowed < actual.

Yes, this matches my thoughts on the matter exactly. Negative durations don't make sense as a concept, but they will inevitably come up in the future when calculating differences between instants. See also the “design considerations” section of the RFC.

Given that prior art, I would propose that the Duration class be changed to store 128-bit attosecond counts as well. Although no specific rationale is given in the Swift Evolution proposal, I expect that it's because modern cpus are capable of sub-nanosecond precision, which means nanosecond-precision is _already_ lossy today, and attoseconds are such a hilariously small unit of time that they allow for quite ample future-proofing. (1 attosecond == 10^-18 s; if I did the math right, a signed 128-bit attosecond count gives a time range of ±5.39 trillion years, over 750x the age of the universe.)

Attoseconds will not work for PHP, because you need 60 bits to represent the number of attoseconds in a second, which would *just* work for 64-bit versions of PHP, but not work for 32-bit versions of PHP. And of course even for 64-bit versions of PHP, we would need to preserve the seconds + subseconds split. See also the third paragraph of the “design considerations”.

Given that the representation exposed to userland necessarily needs to be split into two fields, using a sign-magnitude representation was a deliberate choice that makes working with Durations nicely symmetric.

Best regards
Tim Düsterhus

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

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.

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.

Best regards
Tim Düsterhus

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