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:
- Be correct, or
- 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
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:
- Be correct, or
- 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.
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
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 should1.4be? The internal
representation of that float is1.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.
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
DateTimeif 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
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
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
Hi Tim
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:
- Be correct, or
- 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
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
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.
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’sjava.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’smemory_limitwould 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
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
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üsterhusPS: 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
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
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üsterhusPS: 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
Hi
Am 2026-06-22 20:52, schrieb Larry Garfield:
However, I would advise against introducing Duration::add and
Duration::sub
methods. Instead, I would recommend providing a single method,
Duration::sum(Duration
...$durations): self.[…]
[…]I am 100% against a static method for adding durations. That's
entirely pointless. Even if we're just using methods and not
operators, $dur3 = $dur1->add($dur2) is the way to go.
Both Derick and I were receptive to the Duration::sum() suggestion.
One big benefit I am personally seeing is that it would allow to rename
the negate(), multiplyBy(), and divideBy() methods to use “past
tense” (is that the correct term?) to indicate that they return a copy,
without requiring awkward naming like added(). ->sub() is also
technically redundant due to the existence of negative Durations, thus
there is no need for a mirrored pair like multiplication / division,
since the information can be encoded in the Duration object itself.
To avoid readers making assumptions, can you spell out your arguments
against replacing ->add() and ->sub() by a unified ::sum() method?
Best regards
Tim Düsterhus
Hi
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:
- Be correct, or
- 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
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
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 thatTdoesn'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 thatstrtotime()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
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
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\Durationclass 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:
- Be correct, or
- 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:
-
Why
secondsis always positive with separatenegativeboolean? 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. -
fromIso8601String should just be fromIso8601. The name implies a string already and the argument type defines it as well.
-
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.
-
You have some operator methods defined (+add, -sum, /multiplyBy, <>=compare, what about other operators?
-
Did you thought of supporting float on the initializer methods?
-
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.
-
Naming consistency ... add/sub vs. multiplyBy. Why not addBy/sub[tract]By or multiply to be more consistent?
-
negative vs. isNegative
-
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()?
-
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
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:
- 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.
- 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.
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.Naming consistency ... add/sub vs. multiplyBy. Why not
addBy/sub[tract]By or multiply to be more consistent?
I concur here.
negative vs. isNegative
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
Am 19. Juni 2026 18:35:03 MESZ schrieb Larry Garfield larry@garfieldtech.com:
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:
- 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
- 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.
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.Naming consistency ... add/sub vs. multiplyBy. Why not
addBy/sub[tract]By or multiply to be more consistent?I concur here.
negative vs. isNegative
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
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)).
- 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
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
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 instanceBut that's just a thought.
And BCMath just recently picked up a function to do both of those in a
single call, since calculating one gives you the other for free and
frequently users want both.
Le dim. 21 juin 2026, 01:34, Morgan weedpacket@varteg.nz a écrit :
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 instanceBut that's just a thought.
And BCMath just recently picked up a function to do both of those in a
single call, since calculating one gives you the other for free and
frequently users want both.
Indeed Duration::divmod is the way forward just wondering about the return
type. Should it be an array or a immutable DTO with .2 properties quotient
and remainder?
Best regards,
Ignace
Hi
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
Hi
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
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?
On 22.6.2026 01:07 CEST Morgan weedpacket@varteg.nz 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üsterhusOof. 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): selfdivideBy(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.7sas 1.2s fits 4 times into 5.5s and 0.7s is the remainder.
- e.g.
THIS(duration) modulo NUMBER: What is the remainder after dividing this duration into pieces?- e.g.
5.5s mod 750 = 250nsas 5.5s divided into 750 pieces gives you 7,333,333ns for each piece with a reminder of 250ns. Means5.5s = 750 * 7333333ns + 250ns.
- e.g.
About integer division - I don't think this is the usual case so I would keep that separate (for later) just like $int / $int = int|float is the usual case and integer division intdiv(): int needs to be handled more explicitly is needed.
Regards,
Marc
Hi
Am 2026-06-22 09:21, schrieb marc@mabe.berlin:
I would go with
divideBy(int|float|self $divisor): self|int|float
where the argument type defines the return type:
divideBy(int|float $divisor): selfdivideBy(self $divisor): int|float
PHP doesn't support “method overloading” and that suggestion requires
method overloading. At least in spirit: It would require special -
static-analysis only - definitions for this signature to make sense to a
developer. With the “Deprecate functions with overloaded signatures” RFC
(https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures)
we have specifically deprecated the use of overloaded signatures.
About integer division - I don't think this is the usual case so I
would keep that separate (for later) just like$int / $int = int|floatis the usual case and integer divisionintdiv(): intneeds
to be handled more explicitly is needed.
“Integer division“ as you call it is the natural inverse of
multiplication. I don't think breaking up a Duration into multiple parts
of equal length is something unusual. Looking at the other languages we
took inspiration from, it's also the only division operation that Java
supports.
Best regards
Tim Düsterhus
Hi
Am 2026-06-22 01:07, schrieb Morgan:
Oof. Naming.
The only term I can find that is specifically about dividing one
duration by another is "beat", but that's definitely domain-specific.'periodCount' or 'countPeriods' come to mind. Since in 'n/d' we're
counting how many times duration 'd' occurs during duration 'n' we're
basically multiplying the duration by frequency '1/d'; the inverse of
the frequency - 'd' itself - is called the period. Which word is also
used for "named subdivision of historical time", "class-sized
subdivision of a school day", and so on.The biggest hangup with that name is that it doesn't explicitly state
that it returns the remainder as well. periodParts? periodDivide?
Thank you for the suggestions. I quite like period. I'll also check with
Derick if he has any good ideas (and regarding the Duration divided by
Duration bit in general).
Best regards
Tim Düsterhus
Hi
Also why are there some restrictions on the factor argument of divideBy and
multiplyBy to only accept positive integers ?
At least that is what I understood when I quickly checked the proof of
concept. Since Duration are signed instances that restriction seems strange
?
Enforcing positive values for multiplication and division means that
flipping the sign of a Duration is a deliberate action and not just
something that happens as part of another operation.
See also the last paragraph in the “Design considerations” section.
Best regards
Tim Düsterhus
Hi Tim,
On 21.6.2026 15:52 CEST Tim Düsterhus tim@bastelstu.be wrote:
Hi
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,
On 21.6.2026 15:52 CEST Tim Düsterhus tim@bastelstu.be wrote:
Hi
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 numberJava (
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 SECONDRust (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 aabs()/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
$secondsand$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$secondsinto$totalSecondsso maybe later on we can
add `$seconds { get => $totalSeconds % 60 }Regards,
MarcBest regards
Tim Düsterhus
Hi Tim and Marc,
I concur with Marc remarks especially if we want to add a divmod like
method this simple example shows the issue one would run into with the
current constraints.
$duration = Duration::fromHours(3)->negate();
$factor = Duration::fromHours(1);
[$count, $remainder] = $duration->divmod($factor);
// using BCMath implementation for the example !
//[-3, Duration::fromSeconds(0)] is returned in the tuple
Duration::sum(
$factor->multiplyBy($count), // with the current constraints this will throw
$remainder,
);
Best regards,
Ignace
Hi
Am 2026-06-22 17: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 tupleDuration::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
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 tupleDuration::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
thecopysign()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>= 0restriction of
multiplyBy()would also easily be possible.Best regards
Tim Düsterhus
To be clear, I absolutely hate using a static method here. Even if we're just using methods and not operators, $dur3 = $dur1->add($dur2) is the way to go.
--Larry Garfield
Hi
Am 2026-06-22 20:08, schrieb Larry Garfield:
To be clear, I absolutely hate using a static method here. Even if
we're just using methods and not operators, $dur3 = $dur1->add($dur2)
is the way to go.
Given the amount of “subthreads” and to keep things organized for as the
RFC author: This remark better fits into the
https://news-web.php.net/php.internals/131401 subthread and I would
appreciate you resending it there if it's important to you - and not
continue discussion in this subthread about “dividing durations by
durations”.
Best regards
Tim Düsterhus
Hi all,
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 numberJava (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 SECONDRust (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
Hi
Am 2026-06-22 17:35, schrieb John Bafford:
Personally, I'm on the fence about negative durations, since it's an
unphysical concept, but allowing negative values makes calculations
easier. For example, by allowing (allowedDuration - actualDuration)
where allowed < actual.
Yes, this matches my thoughts on the matter exactly. Negative durations
don't make sense as a concept, but they will inevitably come up in the
future when calculating differences between instants. See also the
“design considerations” section of the RFC.
Given that prior art, I would propose that the Duration class be
changed to store 128-bit attosecond counts as well. Although no
specific rationale is given in the Swift Evolution proposal, I expect
that it's because modern cpus are capable of sub-nanosecond precision,
which means nanosecond-precision is already lossy today, and
attoseconds are such a hilariously small unit of time that they allow
for quite ample future-proofing. (1 attosecond == 10^-18 s; if I did
the math right, a signed 128-bit attosecond count gives a time range of
±5.39 trillion years, over 750x the age of the universe.)
Attoseconds will not work for PHP, because you need 60 bits to represent
the number of attoseconds in a second, which would just work for
64-bit versions of PHP, but not work for 32-bit versions of PHP. And of
course even for 64-bit versions of PHP, we would need to preserve the
seconds + subseconds split. See also the third paragraph of the “design
considerations”.
Given that the representation exposed to userland necessarily needs to
be split into two fields, using a sign-magnitude representation was a
deliberate choice that makes working with Durations nicely symmetric.
Best regards
Tim Düsterhus
Hi
Am 2026-06-22 09:01, schrieb marc@mabe.berlin:
- Go (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 aabs()/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
$secondsand$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$secondsinto$totalSecondsso 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
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 numberA 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 SECONDI 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 * $millisecondsto 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:
- result of calculation
- 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 aabs()/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
$secondsand$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$secondsinto$totalSecondsso maybe later on we
can add `$seconds { get => $totalSeconds % 60 }I haven't yet checked with Derick on this, but given the third paragraph
of the “Design Considerations” and Rust’s / Java’s implementation which
also use $seconds, I would disagree here. The same issue would also
exist when $milliseconds and $microsecond accessors would be added,
because $nanoseconds would then need to be restricted to 1_000 using the
same argument. In my opinion this kind of “convenience” accessors are
better left to explicit and clearly named methods.
Java does not have property hooks (as far as I know) but PHP now has.
That's very powerful and makes it possible to hide implementation details without exposing everything as getters.
$duration->seconds // 0-59
$duration->totalSeconds
$duration->nanoseconds // 0 - 999_999_999
$duration->totalNanoseconds
$duration->milliseconds // 0 - 999
$duration->totalMilliseconds
// maybe later
$duration->picoseconds // 0 - 999_999_999_999
$duration->totalPicosecond
As a user you don't need to care that much of the internal representation.
Best regards
Tim Düsterhus
Hi
Am 2026-06-23 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:
- result of calculation
- 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->totalPicosecondAs 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
Hello,
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
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
Hi,
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
$eventsso that they fit within the daylight window. - The allowed time window (dawn to dusk) is expanded on both ends
- by
$daytimePadding, which can be negative to shrink the window. - Events that would fall entirely outside this window are discarded.
- @param list<Event> $events
- @return list<Event>
*/
function clampEventsToDaylight(
DayAstronomicalData $day,
Duration $daytimePadding,
array $events,
): array
Instead of preventing the construction of some invalid timeouts at the
cost of making time arithmetic more clunky, it seems better to keep
the constructors flexible and let functions that take Duration as an
argument enforce their own rules when appropriate. That's the way
other languages tend to handle the issue. For example,
Duration.ofHours(-2) is legal in Java and Temporal.Duration.from({
hours: -2 }) is legal in JavaScript. I'm less familiar with Rust, but
from what I've read, its standard Duration type forbids negative
values specifically because Rust doesn't come with a full date/time
API and Duration is used for timeouts and system timers. The most
popular Rust library for complex date/time handling, chrono, provides
a TimeDelta type (renamed from Duration) that accepts negative values
in its constructors.
Best regards,
Paul
Hi
Am 2026-06-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.
- 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.
- 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
Hi
Am 2026-06-19 18:26, schrieb Marc B.:
- Why
secondsis always positive with separatenegativeboolean? 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?
- 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 hascreateFromFile()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.
- 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.
- You have some operator methods defined (+add, -sum, /multiplyBy,
<>=compare, what about other operators?
What are you looking for?
- Did you thought of supporting float on the initializer methods?
See the sub-thread started by Seifeddine.
- 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).
- 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.
- negative vs. isNegative
Please provide a concrete suggestion.
- 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.
- 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
Hi Tim
Thank you and Derick for the work on this proposal.
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
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, PHPssleep()is a thin wrapper around system
sleep(), which only accepts seconds. This would internally need to be
re-routed tousleep(), but even thenusleep()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
Hi All
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 |
+---------------------------------------------------------------------+
Hi
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 Durations and Times 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
Hi Tim, hey all
Hi
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 considerDurations andTimes to be
different concepts. The difference between “meeting is (in/at) 10:00” is
the difference betweenDurationandTime. If you conflate those, you
end up being late. Similarly if I callsleep(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 |
+---------------------------------------------------------------------+
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:
- Be correct, or
- 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:
-
Are there other classes planned for the
\Timenamespace? I think we
should avoid using root namespaces and instead decide on a common
namespace, so something like\Std\Timeor\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. -
Since the class is marked final (why?), I would recommend we also create
aDurationInterfaceand 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\Carbonextends\DateTimeand
implements\DateTimeInterface.
Thanks,
Peter
Hi
Am 2026-06-23 02:36, schrieb Peter Stalman:
- Are there other classes planned for the
\Timenamespace? 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\Timeor\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).
- Since the class is marked final (why?), I would recommend we also
create
aDurationInterfaceand 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\Carbonextends\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
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.
-
Derick looked up the official name of the
PT30Mformat 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 -
We added an
->absolute()method to return a non-negative Duration
object with the same magnitude. -
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. -
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). -
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