[RFC] Duration class

php.internals

Tim Düsterhus

75 days ago
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

Seifeddine Gmati

75 days ago
On Thu, 18 Jun 2026 at 14:48, 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, Thank you for proposing this! I think it would be a good addition, and a move in the right direction for a better built-in date-time stuff. I do have some questions: - Is there a reason the highest unit is seconds not hours? - The RFC specifies `Duration::fromIso8601String`, but there does not seem to be a method to turn the duration itself back into an ISO 8601 string. is there a reason for that? - I think it would be nice to have methods that return the total duration as a specific unit as a floating point, e.e.g., `getTotalSeconds()`, was this considered? Also, i wanted to point out `Psl\DateTime\Duration` that is basically the same thing: https://github.com/php-standard-library/php-standard-library/blob/next/packages/date-time/src/Psl/DateTime/Duration.php#L37 which was modeled after Rust's `std::time::Duration` ( https://doc.rust-lang.org/beta/std/time/struct.Duration.html ) and Java's `Duration` ( https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html ). Cheers, Seifeddine.

Tim Düsterhus

75 days ago
Hi Am 2026-06-18 16:18, schrieb Seifeddine Gmati:
> - Is there a reason the highest unit is seconds not hours?
The highest unit supported by the constructors is hours (`Duration::fromHours()`). The internal representation uses a seconds + nanoseconds pair for the reasons outlined in the design considerations, similarly to Rust’s and Java’s Duration. 68 years on 32-bit systems and 21× the age of the universe on 64-bit systems should be plenty.
> - The RFC specifies `Duration::fromIso8601String`, but there does not > seem to be a method to turn the duration itself back into an ISO 8601 > string. is there a reason for that?
The best reason there probably is “the proposed API is intentionally minimal”. There are probably also some decisions to be made for the re-stringification into ISO-8601: Should it stringify straight into `sprintf("PT%d.%09dS", $seconds, $nanoseconds)` or should it try to use “as large units as possible” (i.e. minutes and hours). The correct answer might depend on the use case and thus it's probably better to move this decision to the PHP 8.7 cycle :-)
> - I think it would be nice to have methods that return the total > duration as a specific unit as a floating point, e.e.g., > `getTotalSeconds()`, was this considered?
Derick and I haven't discussed this, but the “minimal API” applies here as well. Floating point versions we specifically left out (for now), because we didn't want to go into the precision / rounding question. As an example how many nanoseconds should `1.4` be? The internal representation of that float is `1.3999999999999999`. Best regards Tim Düsterhus

Seifeddine Gmati

75 days ago
On Thu, Jun 18, 2026, 4:02 PM Tim Düsterhus <tim@bastelstu.be> wrote:
> Derick and I haven't discussed this, but the “minimal API” applies here > as well. Floating point versions we specifically left out (for now), > because we didn't want to go into the precision / rounding question. As > an example how many nanoseconds should `1.4` be? The internal > representation of that float is `1.3999999999999999`. >
I think precision doesn't matter here. The primary reason we added float total methods in Psl was to be able to bridge a Duration object to other APIs that accepted float $seconds ( mainly Revolt ), Rust and Java also both offer the floating point methods, and i think they make sense. Could be added in the future though. Another nitpick: I think the namespace should be `DateTime` if this is supposed to be start of a new API that covers both date and time, so we don't end up with two namespace later (which will share alot of things back and forth). Cheers, Seifeddine.

Tim Düsterhus

75 days ago
Hi Am 2026-06-18 17:20, schrieb Seifeddine Gmati:
> […] float […] Could be added in the future though.
Yes, my reply was not intended to be a “we are definitely against floats”, but more a “floats are not completely obvious to handle, so we rather think about the consequences later”.
> Another nitpick: I think the namespace should be `DateTime` if this is > supposed to be start of a new API that covers both date and time, so we > don't end up with two namespace later (which will share alot of things > back > and forth).
The `Time` namespace matches that of most references we looked at. `std::time` in Rust, `java.time` in Java, `time` in Golang. JavaScript calling it Temporal is the “odd person out”. Personally I also think of “Time” as the generic concept that also includes “Dates”. Thus the upcoming classes to also handle “calendar dates” and “time as shown on a clock” would be appropriately located in the `Time` namespace. Java uses `java.time.ZonedDateTime` for the closest equivalent to PHP’s existing `DateTimeImmutable` (and `java.time.Instant` for timezone-less points in time). See https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html#package.description for a description of the class hierarchy and relationships. Best regards Tim Düsterhus

Joerg Sowa

75 days ago
Hi Tim, The only one concern I see is the method `fromIso8601String`. Unfortunately the PHP has this weird constant that is called DATE_ISO8601 https://www.php.net/manual/en/class.datetimeinterface.php#datetimeinterface.constants.iso8601, that is not in fact proper standard. I wanted to deprecate it few years ago, but the push back from community was strong, so I did suggest only deprecation of DATE_RFC7231. So what kind of format this method will except, standard ISO, or PHP's version of ISO? Maybe it's worth to find another name? Kind regards, Jorg

Tim Düsterhus

75 days ago
Hi Am 2026-06-18 18:33, schrieb Jorg Sowa:
> So what kind of format this method will except, standard ISO, or PHP's > version of ISO? Maybe it's worth to find another name?
The method will accept a ISO-8601 “Period” string, just like `DateInterval::__construct()` does. The only restriction is that period strings accepted by the Time\Duration class may only contain the “time” components. In simplified terms: The given string must start with `PT`. The format strings you are thinking about are those to format date-times, which is a different concern and which will become relevant when classes to represent dates and times are added in the future. Given those are intended to fix the issues of the current API, I expect them to exactly follow the relevant standards when referring to a standard. Just like the URI parsers in the new URI extension follow the WHATWG and RFC 3986 standards - and any difference is considered a bug. Given that namespaces for PHP’s standard library are a relatively new thing, this luckily allows us to fix issues by building an entirely new, well-designed API without breaking backwards compatibility and without needing to come up with weird names to avoid conflicts. This worked well for the new random API in 8.2, the new DOM API in 8.4 and the new URI API in 8.5 and we hope to continue that with the new date API starting in 8.6. Best regards Tim Düsterhus

Frederik Bosch | Genkgo

74 days ago
Hi Tim On 6/18/26 15:47, Tim Düsterhus 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, Rather than building a whole new date/time library, did you consider using the temporal_rs library instead? This library is used for the new Temporal API that is now available in Firefox and Chrome and is a browser standard. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal The temporal_rs library is already used in V8, Boa and Kiesel. This means the library has a large user-base, that is actively developed, but also stable because it needs to adhere to the standards. Regards, Frederik

Tim Düsterhus

74 days ago
Hi Am 2026-06-18 22:25, schrieb Frederik Bosch:
> Rather than building a whole new date/time library, did you consider > using the temporal_rs library instead? This library is used for the new > Temporal API that is now available in Firefox and Chrome and is > a browser standard.
We did not look specifically at temporal_rs (I wasn't aware of its existing until now), but we looked at JavaScript’s Temporal API - amongst others. I'm not speaking for Derick here, but I do not believe it is possible to just take an API that was built for a different programming language and plug it into a different programming language without making any changes. Different programming languages have different capabilities, ecosystems and code styles and without adjusting the API to the respective language, it will just stick out like a sore thumb. As an example, JavaScript’s Temporal.Duration.prototype.round is an overloaded function with one overload taking an “options object”. This is something that works well with JavaScript’s dynamic nature, but is something we are moving away from in PHP, because it doesn't interact well with explicit type-checked signatures that are a first-class citizen in PHP. Semantically PHP is much closer to Java than it is to JavaScript and I believe that Java’s `java.time.*` is a comparatively better fit than JavaScript’s Temporal, but even that cannot be taken as-is, because Java supports overloaded methods (e.g. java.time.Duration.minus()) whereas PHP does not. The complicated part here is the API design, not the internal implementation, particularly since PHP already has a date API that supports all the relevant operations. It's just that the userland API really starts to show its age. So using temporal_rs under the hood, while exposing a PHP-specific API will not provide any meaningful benefit - and would on the contrary add a new dependency on Rust. I'm not saying that including Rust is a bad idea, but integrating Rust libraries nicely into the Zend Engine, for example to make them work with PHP’s `memory_limit` would be a significant undertaking by itself and not be something we can just do as a “don’t worry about it” implementation detail. Best regards Tim Düsterhus

nyamsprod the funky webmaster

74 days ago
Hi Tim and Derick, I appreciate the way the Duration class has been introduced. It is intentionally minimal, which is a sensible approach, while still leaving room for future extensions. 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. On Thu, Jun 18, 2026 at 11:22 PM Tim Düsterhus <tim@bastelstu.be> wrote:

Tim Düsterhus

73 days ago
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.

nyamsprod the funky webmaster

73 days ago
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

Tim Düsterhus

73 days ago
Hi Am 2026-06-20 13:45, schrieb ignace nyamagana butera:
> if no argument is given I would expect > an ArgumentCountError to be thrown just like with array_sum.
This statement is not accurate (it's also not a recent change: https://3v4l.org/Ia8Mc#v): php > var_dump(array_sum([])); int(0) But following the implied argument, `Duration::fromSeconds(0)` would be the expected answer. I'll discuss this with Derick. Best regards Tim Düsterhus

Larry Garfield

70 days ago
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

Tim Düsterhus

70 days ago
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

Tim Düsterhus

62 days ago
Hi Am 2026-06-23 15:04, schrieb Tim Düsterhus:
> 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?
To close this out ourselves: While having a static `sum()` method works for `Duration`s, since the Duration with addition forms an abelian group, this doesn't work for the other future-scope classes where addition doesn't form an (abelian) group. More specifically: Adding two Instants together is not a meaningful operation. Adding a Duration to an Instant however is. Using something like: $epoch = Instant::fromUnixtime(0); $onePointFiveHoursLater = Instant::sum($epoch, Duration::fromMinutes(30), Duration::fromHours(1)); would be very weird. Instead `->add()` is the obvious operation here: $epoch = Instant::fromUnixtime(0); $onePointFiveHoursLater = $epoch ->add(Duration::fromMinutes(30)) ->add(Duration::fromHours(1)); notably this would also work then: $epoch = Instant::fromUnixtime(0); $onePointFiveHoursLater = $epoch ->add( Duration::fromMinutes(30) ->add(Duration::fromHours(1) ); making the Instant + Duration -> Instant operation a “right group action”. We will thus keep instance methods for “addition” and “subtraction”. If someone has a suggestion regarding naming (particularly with regard to negate() vs negated() and multiplyBy() and multipliedBy()), I'll be happy to discuss that. Java uses plus, minus, multipliedBy, dividedBy and negated. Golang is just using an integer (meaning you just use the operators). Rust’s std::time::Duration uses add, sub, mul, div (with saturating and checked variants), but also overloads operators. Rust’s chrono::TimeDelta is similar to std::time::Duration, but doesn't have the saturating variants. JavaScript’s Temporal uses add, sub, negated and doesn't support multiplication and division. Please keep the `divide**By**()` (Duration / int -> Duration) vs `divide**Into**()` (Duration / Duration -> (int, Duration)) suggestion in mind when having opinions about the naming. See: https://news-web.php.net/php.internals/131634 Best regards Tim Düsterhus

Larry Garfield

62 days ago
On Wed, Jul 1, 2026, at 12:22 PM, Tim Düsterhus wrote:
> Hi > > Am 2026-06-23 15:04, schrieb Tim Düsterhus: >> 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? > > To close this out ourselves: While having a static `sum()` method works > for `Duration`s, since the Duration with addition forms an abelian > group, this doesn't work for the other future-scope classes where > addition doesn't form an (abelian) group. More specifically: Adding two > Instants together is not a meaningful operation. Adding a Duration to an > Instant however is. Using something like: > > $epoch = Instant::fromUnixtime(0); > $onePointFiveHoursLater = Instant::sum($epoch, > Duration::fromMinutes(30), Duration::fromHours(1)); > > would be very weird. Instead `->add()` is the obvious operation here: > > $epoch = Instant::fromUnixtime(0); > $onePointFiveHoursLater = $epoch > ->add(Duration::fromMinutes(30)) > ->add(Duration::fromHours(1)); > > notably this would also work then: > > $epoch = Instant::fromUnixtime(0); > $onePointFiveHoursLater = $epoch > ->add( > Duration::fromMinutes(30) > ->add(Duration::fromHours(1) > ); > > making the Instant + Duration -> Instant operation a “right group > action”.
How about that. Forgetting to respond to this thread meant you gave a good response for me. Thanks, Tim. :-)
> We will thus keep instance methods for “addition” and “subtraction”. If > someone has a suggestion regarding naming (particularly with regard to > negate() vs negated() and multiplyBy() and multipliedBy()), I'll be > happy to discuss that. Java uses plus, minus, multipliedBy, dividedBy > and negated. Golang is just using an integer (meaning you just use the > operators). Rust’s std::time::Duration uses add, sub, mul, div (with > saturating and checked variants), but also overloads operators. Rust’s > chrono::TimeDelta is similar to std::time::Duration, but doesn't have > the saturating variants. JavaScript’s Temporal uses add, sub, negated > and doesn't support multiplication and division. > > Please keep the `divide**By**()` (Duration / int -> Duration) vs > `divide**Into**()` (Duration / Duration -> (int, Duration)) suggestion > in mind when having opinions about the naming. See: > https://news-web.php.net/php.internals/131634
negated() would be consistent with the "ed means return new" pattern found in many languages, so I would strongly recommend following that pattern. For the others, my inclination is to go short (add, sub) for easier typing and a more compact result, but that's not a make-or-break issue for me. --Larry Garfield

Weedpacket

61 days ago
On 2026-07-02 05:22, Tim Düsterhus wrote:
> > To close this out ourselves: While having a static `sum()` method works > for `Duration`s, since the Duration with addition forms an abelian > group, this doesn't work for the other future-scope classes where > addition doesn't form an (abelian) group. More specifically: Adding two > Instants together is not a meaningful operation. Adding a Duration to an > Instant however is. >
If you want the appropriate mathematical structure, the timeline models a one-dimensional real affine space, in which Durations are vectors and Instants are points; it's a vector space that has forgotten its origin (the choice of epoch is arbitrary and not a distinguished point of the space - as evidenced by how many of them there are).

Larry Garfield

74 days ago
On Thu, Jun 18, 2026, at 8:47 AM, Tim Düsterhus 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
I support this proposal in general. I especially like the public readonly properties. :-) My main suggestions would be for more constructors, though I'm sure that's going to get a response of "MVP, add later." :-P 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. 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. 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. --Larry Garfield

Tim Düsterhus

73 days ago
Hi Am 2026-06-19 18:17, schrieb Larry Garfield:
> My main suggestions would be for more constructors, though I'm sure > that's going to get a response of "MVP, add later." :-P
Well, you would need to explain what constructors you are missing out on. If it's something “obviously correct and we are not going to regret it” I wouldn't necessarily rule out adding them straight away. But otherwise your suspicion would be correct.
> 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 :-) 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)? Best regards Tim Düsterhus

Larry Garfield

71 days ago
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 :-)
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

Tim Düsterhus

71 days ago
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"`: https://pkg.go.dev/time#ParseDuration. The `sleep` binary from GNU coreutils also accepts a (single) suffix. You can find the man page here: https://manpages.debian.org/testing/coreutils/sleep.1.en.html 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

Marc

74 days ago
Hi Tim, Am 18. Juni 2026 15:47:57 MESZ schrieb "Tim Düsterhus" <tim@bastelstu.be>:
>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.
Thanks for being this up. I think that's a sensible approach and can be used as a starting point for further improvements to the date/time API. Some comments though: 1. Why `seconds` is always positive with separate `negative` boolean? I don't get the reason behind that logic and it puts additional effort on the user reading these values. A negative Duration should just be negative seconds. Nanoseconds on the other hand should always between 0 and 999_999_999. 2. fromIso8601String should just be fromIso8601. The name implies a string already and the argument type defines it as well. 3. fromSeconds is the only initiator with a second nanoseconds argument. That feels a bit inconsistent from consistency POV. Also, it's not just applying these values because, to be valid, there needs to be some calculation be done. 4. You have some operator methods defined (+add, -sum, /multiplyBy, <>=compare, what about other operators? 5. Did you thought of supporting float on the initializer methods? 6. You should at least mention that this class works with a fixed definition of e.g. how long a minute is - no leaps tz handling here. Which is the correct approach for it. 7. Naming consistency ... add/sub vs. multiplyBy. Why not addBy/sub[tract]By or multiply to be more consistent? 8. negative vs. isNegative 9. negate() I would expect to get back a negative Duration. Also, it sounds like modifying the duration but I'm assuming a new instance gets returned. What about inverted()? 10. On assuming the duration is immutable. Does the medifier function still return the same object for the same resulting durations? I have spend quite some time experimenting with building a good date/time API matching PHP. What I came up with went into an experimental but already working library in PHP without using the available date/calendar extension. <https://github.com/marc-mabe/php-timelib> PS: don't use in product PS 2: A working group for that would be great 😃 Regards, Marc

Larry Garfield

74 days ago
On Fri, Jun 19, 2026, at 11:26 AM, Marc B. wrote:
> Thanks for being this up. I think that's a sensible approach and can be > used as a starting point for further improvements to the date/time API. > > Some comments though:
> 4. You have some operator methods defined (+add, -sum, /multiplyBy, > <>=compare, what about other operators?
What would those be? Personally I'd want to see operators for add and sub, at least, but I'm not sure what else would be useful.
> 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.
> 6. You should at least mention that this class works with a fixed > definition of e.g. how long a minute is - no leaps tz handling here. > Which is the correct approach for it. > > 7. Naming consistency ... add/sub vs. multiplyBy. Why not > addBy/sub[tract]By or multiply to be more consistent?
I concur here.
> 8. negative vs. isNegative > > 9. negate() I would expect to get back a negative Duration. Also, it > sounds like modifying the duration but I'm assuming a new instance gets > returned. What about inverted()?
Good point. negated() would be fine, I think. (As noted elsewhere, sort() vs sorted() is a very common pattern for "modify in place" vs. "make new", across a number of languages.) --Larry Garfield

Marc

74 days ago
Am 19. Juni 2026 18:35:03 MESZ schrieb Larry Garfield <larry@garfieldtech.com>:
>On Fri, Jun 19, 2026, at 11:26 AM, Marc B. wrote: > >> Thanks for being this up. I think that's a sensible approach and can be >> used as a starting point for further improvements to the date/time API. >> >> Some comments though: > >> 4. You have some operator methods defined (+add, -sum, /multiplyBy, >> <>=compare, what about other operators? > >What would those be? Personally I'd want to see operators for add and sub, at least, but I'm not sure what else would be useful.
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
> >> 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.

Tim Düsterhus

73 days ago
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

nyamsprod the funky webmaster

73 days ago
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

Weedpacket

72 days ago
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.

nyamsprod the funky webmaster

72 days ago
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

Tim Düsterhus

72 days ago
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

nyamsprod the funky webmaster

72 days ago
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

Weedpacket

71 days ago
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?

Marc

71 days ago
> 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

Tim Düsterhus

71 days ago
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

Tim Düsterhus

72 days ago
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

Marc

71 days ago
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 (https://pkg.go.dev/time#Duration) Just int64 of nanoseconds - negative durations are a negative number * Java (https://github.com/frohoff/jdk8u-jdk/blob/master/src/share/classes/java/time/Duration.java) 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 (https://doc.rust-lang.org/src/core/time.rs.html) 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

nyamsprod the funky webmaster

71 days ago
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. > > * Go (https://pkg.go.dev/time#Duration) > Just int64 of nanoseconds - negative durations are a negative number > > * Java ( > https://github.com/frohoff/jdk8u-jdk/blob/master/src/share/classes/java/time/Duration.java > ) > 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 (https://doc.rust-lang.org/src/core/time.rs.html) > 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 >
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

Tim Düsterhus

71 days ago
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 (https://man7.org/linux/man-pages/man3/copysign.3.html). 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

Larry Garfield

71 days ago
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 > (https://man7.org/linux/man-pages/man3/copysign.3.html). > > 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

John Bafford

71 days ago
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 (https://pkg.go.dev/time#Duration) > Just int64 of nanoseconds - negative durations are a negative number > > * Java (https://github.com/frohoff/jdk8u-jdk/blob/master/src/share/classes/java/time/Duration.java) > 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 (https://doc.rust-lang.org/src/core/time.rs.html) > 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

Tim Düsterhus

71 days ago
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

Tim Düsterhus

71 days ago
Hi Am 2026-06-22 09:01, schrieb marc@mabe.berlin:
> * Go (https://pkg.go.dev/time#Duration) > Just int64 of nanoseconds - negative durations are a negative number
A single integer doesn't work for PHP (see my reply to John).
> * Java > (https://github.com/frohoff/jdk8u-jdk/blob/master/src/share/classes/java/time/Duration.java) > 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

Marc

70 days ago
> 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 (https://pkg.go.dev/time#Duration) > > Just int64 of nanoseconds - negative durations are a negative number > > A single integer doesn't work for PHP (see my reply to John). > > > * Java > > (https://github.com/frohoff/jdk8u-jdk/blob/master/src/share/classes/java/time/Duration.java) > > 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.

Tim Düsterhus

69 days ago
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 (https://www.programiz.com/online-compiler/1Iilpy8ncbwRS): 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

Paweł Kraśnicki

70 days ago
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: https://github.com/brick/date-time/blob/8a41dbf7c7a63b21fa511dfcb3794a50d2a3dbaa/src/Duration.php 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

Tim Düsterhus

70 days ago
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 https://news-web.php.net/php.internals/131466, why I don’t consider this representation to be intuitive or easy to use. Best regards Tim Düsterhus

Paweł Kraśnicki

68 days ago
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

Tim Düsterhus

73 days ago
Hi Am 2026-06-19 18:35, schrieb Larry Garfield:
> What would those be? Personally I'd want to see operators for add and > sub, at least, but I'm not sure what else would be useful.
I find the current behavior of operator overloading very unintuitive, because there is no obvious way of defining an expected signature, it's not in the stub, and if the overloaded operator throws, the stack trace will not provide any indication to what has happened either, see: php > var_dump(new GMP(5) + new stdClass()); PHP Warning: Uncaught TypeError: Number must be of type GMP|string|int, stdClass given in php shell code:1 Stack trace: #0 {main} thrown in php shell code on line 1 I believe the overloaded comparison handler is a reasonable middle-ground and I wouldn't necessarily be against adding the other overloads when the entire user-story is better. But `Duration::fromSeconds(5) + 6` throwing with a `TypeError` (Duration + int is not meaningful) without being able to look up which operations are supported is not helping anyone.
>> 7. Naming consistency ... add/sub vs. multiplyBy. Why not >> addBy/sub[tract]By or multiply to be more consistent? > > I concur here.
See my reply to Marc.
>> 9. negate() I would expect to get back a negative Duration. Also, it >> sounds like modifying the duration but I'm assuming a new instance >> gets >> returned. What about inverted()? > > Good point. negated() would be fine, I think. (As noted elsewhere, > sort() vs sorted() is a very common pattern for "modify in place" vs. > "make new", across a number of languages.)
See my reply to Marc. Best regards Tim Düsterhus

Tim Düsterhus

73 days ago
Hi Am 2026-06-19 18:26, schrieb Marc B.:
> 1. Why `seconds` is always positive with separate `negative` boolean? I > don't get the reason behind that logic and it puts additional effort on > the user reading these values. A negative Duration should just be > negative seconds. Nanoseconds on the other hand should always between 0 > and 999_999_999.
This is answered in the “design considerations” section of the RFC. Did you miss that one? With regard to your specific suggestion of only the seconds should be negative: How do you represent a Duration of negative 0.5 seconds then?
> 2. fromIso8601String should just be fromIso8601. The name implies a > string already and the argument type defines it as well.
See my sibling reply to Larry with regard a possible rename. With regard to the `String` suffix specifically. I find it important for several reasons: - Horizontal consistency with `Dom\HTMLDocument::createFromString()` (which also has `createFromFile()` with an identical signature). - The lack of method overloading. There could possibly be an ISO-8601 object that might require a slightly different signature so that a simple union type is insufficient. - Just “from ISO-8601” sounds like an incomplete sentence (and “from ISO-8601 period” does as well). I would add the “string” part in my spoken communication as well, so I feel it should also be there in the method name.
> 3. fromSeconds is the only initiator with a second nanoseconds > argument. That feels a bit inconsistent from consistency POV.
See “design considerations”.
> Also, it's not just applying these values because, to be valid, there > needs to be some calculation be done.
This is incorrect, as demonstrated by the proof-of-concept userland implementation.
> 4. You have some operator methods defined (+add, -sum, /multiplyBy, > <>=compare, what about other operators?
What are you looking for?
> 5. Did you thought of supporting float on the initializer methods?
See the sub-thread started by Seifeddine.
> 6. You should at least mention that this class works with a fixed > definition of e.g. how long a minute is - no leaps tz handling here. > Which is the correct approach for it.
I believe the “Introduction” section (and the non-normative userland implementation) was pretty clear on that the class represents “stop-watch time”, not something related to clocks. But for completeness I have just added a PHPDoc comment to that effect to the class itself (in addition to the existing PHPDoc on the methods).
> 7. Naming consistency ... add/sub vs. multiplyBy. Why not > addBy/sub[tract]By or multiply to be more consistent?
I don't think “add by” and “sub(tract) by” is correct grammar. The `By` on `divideBy` was a deliberate choice to make it clear that the Duration is the dividend and the given integer is the divisor, because this isn't entirely clear using use `divide`. For multiplication as the inverse of division we added it for consistency (even though it doesn't matter there, because multiplication is commutative). This also roughly matches Java's naming of `multipliedBy` / `dividedBy` and `plus` / `minus`.
> 8. negative vs. isNegative
Please provide a concrete suggestion.
> 9. negate() I would expect to get back a negative Duration. Also, it > sounds like modifying the duration but I'm assuming a new instance gets > returned. What about inverted()?
The class is `readonly`, so yes, a new instance gets returned. `negated()` is also something I thought of, which would also work with `multipliedBy()` and `dividedBy()` as in Java, but (as not a native speaker of English) I believe this naming pattern breaks down for the addition / subtraction. Ignace's suggestion of just having `sum()` might solve this, though.
> 10. On assuming the duration is immutable. Does the medifier function > still return the same object for the same resulting durations?
I don't understand the question.
> I have spend quite some time experimenting with building a good > date/time API matching PHP. What I came up with went into an > experimental but already working library in PHP without using the > available date/calendar extension. > <https://github.com/marc-mabe/php-timelib>
Yes, that repository is referenced in the backwards incompatible changes section. Best regards Tim Düsterhus

Ilija Tovilo

71 days ago
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: > 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.
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

Tim Düsterhus

71 days ago
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 (;;) { $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

Andreas Heigl

71 days ago
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 https://3v4l.org/QZnZ2#v) - 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 | +---------------------------------------------------------------------+

Tim Düsterhus

70 days ago
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

Andreas Heigl

70 days ago
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 | +---------------------------------------------------------------------+

Peter Stalman

70 days ago
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

Tim Düsterhus

70 days ago
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 https://github.com/php/policies/blob/main/coding-standards-and-naming.rst#namespaces. It was (almost) unanimously accepted in 2021: https://wiki.php.net/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: https://news-web.php.net/php.internals/123997). Best regards Tim Düsterhus

Tim Düsterhus

70 days ago
Hi Am 2026-06-18 15:47, schrieb Tim Düsterhus:
> 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.
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: https://news-web.php.net/php.internals/131409 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

Tim Düsterhus

60 days ago
Hi On 6/23/26 14:12, Tim Düsterhus wrote:
> Am 2026-06-18 15:47, schrieb Tim Düsterhus: >> 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. > > Derick and I had a first discussion about the feedback so far and I have > made the following changes to the RFC earlier today.
Regarding the “method naming question” as the last remaining issue, I've also made a quick informal poll in PHP Foundation Slack and then discussed the topic with Derick: `add` and `sub` were in unanimous agreement. So we go with those. For the other “mathematical operations”, there was a non-trivial number of folks in favor of abbreviated names (for consistency with BCMath and GMP). In favor of the -ed suffix was only Larry, who also replied on list. Given that the class is clearly marked as `readonly`, and that, following Larry’s reasoning, mathematical operations are generally not working in-place, we decided not to consider the -ed suffix, since it would not consistently work (e.g. ->absoluted() as a companion to ->negated() doesn't make sense). Since the naming policy says that abbreviations should be avoided, but can be okay if accepted by RFC vote, we decided to actually make it a vote: We added a secondary vote between the current “full” names (multiplyBy, divideBy, negate, absolute) and abbreviated names (mul, divBy, neg, abs). In accordance with policy (https://github.com/php/policies/blob/main/feature-proposals.rst#required-majority), this secondary vote will be “biased” in favor of the full names, matching the original and current proposal (and the non-abbreviation naming suggested by the naming policy). Specifically, the abbreviated names will require a 2/3 majority. If 4 folks vote in favor of full names, and 8 in favor of abbreviations, it will be abbreviations. If it's 4 in favor of full names, and 7 in favor of abbreviations it will be full names. ----------- From what we see all open questions for a “minimally viable API” in PHP 8.6 should be resolved with this change and all possible “Future Scope” be properly taken into account so we won’t regret anything in the future. Fingers crossed! With the feature freeze in the middle of August and the required 14 day cooldown after making major changes to RFCs, we have a few more days to make any finishing touches, but we would consider the RFC to be complete and don’t plan to make any more changes. Personally I'm super happy with how insightful and productive the discussion was. I'm really looking forward to seeing you all again for the upcoming “Future Scope” discussions for PHP 8.7 :-) Best regards Tim Düsterhus

Tim Düsterhus

60 days ago
Hi On 7/3/26 17:52, Tim Düsterhus wrote:
> We added a secondary vote between the current “full” names (multiplyBy, > divideBy, negate, absolute) and abbreviated names (mul, divBy, neg, abs).
What I planned to mention, but forgot: I've also added a full “Naming of the mathematical operations” section providing background information and arguments regarding the secondary vote: https://wiki.php.net/rfc/duration_class#naming_of_the_mathematical_operations Best regards Tim Düsterhus

Tim Düsterhus

49 days ago
Hi On 2026-07-03 17:52, Tim Düsterhus wrote:
> With the feature freeze in the middle of August and the required 14 day > cooldown after making major changes to RFCs, we have a few more days to > make any finishing touches, but we would consider the RFC to be > complete and don’t plan to make any more changes.
No more changes have been made since that email, which means that the cooldown period will expire on Friday evening (European time). All discussions have been resolved (some of them with an “agree to disagree”), so we plan to open voting at the end of this or early next week. Best regards Tim Düsterhus