following Time\Duration in PHP 8.6, Derick and I created an RFC for Time\Instant and Time\Clock as the next part of the new date and time API:
This time we have several months until the next feature freeze, so there’s place to discuss additional functionality that we missed. Nevertheless we would like to keep the RFC focused so that every decision gets the attention it deserves and some bits are already listed as “Future Scope” to that effect.
This time we have several months until the next feature freeze, so there’s place to discuss additional functionality that we missed. Nevertheless we would like to keep the RFC focused so that every decision gets the attention it deserves and some bits are already listed as “Future Scope” to that effect.
Thanks for the proposal.
I do see some clashes within the proposal though:
Right at the start you are mentioning that “An Time\Instant carries no timezone. It represents a unique point on the timeline of the universe. This point is the same for everyone, no matter where they are located on the Earth (or not on Earth at all), i.e. no matter their timezone.”
I am totally in favour of that!
Later on though you are mentioning that “Following the lead of virtually all programming languages and operating systems, leap seconds are ignored.”
Is that only with relation to the Iso8601 strings? Or is that a general shortcoming?
The other point I find irritating is the naming of the Clock interface. While I can understand the reasoning behind it I see irritation in the community upcoming as the CLockInterface now suddenly returns 2 different things, depending on which ClockInterface people are talking about.
Is there a way to Unify the ideas behind Time\Clock and Psr\Clock\CLockInterface?
As right now the Time\Clock is not a direct replacement of Psr\Clock\ClockInterface as - at the current time - there is no way to generate a DateTimeImmutable directly from a Time\Instant.
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 |
±--------------------------------------------------------------------+
This time we have several months until the next feature freeze, so
there’s place to discuss additional functionality that we missed.
Nevertheless we would like to keep the RFC focused so that every
decision gets the attention it deserves and some bits are already listed
as “Future Scope” to that effect.
Best regards
Tim Düsterhus
Hi Tim,
Thanks for putting this together. I have a few suggestions about
naming, obtaining the current time, and the clock implementations.
Could we name the proposed class `Time\SystemTime` and reserve
`Time\Instant` for a future high-resolution monotonic clock API? Rust
uses this distinction, and I find it helpful:
[`SystemTime`](SystemTime in std::time - Rust)
represents wall-clock time, which can change following clock
corrections, while
[`Instant`](Instant in std::time - Rust)
represents monotonic time, suitable for measuring elapsed time,
benchmarking, etc.
I would also like to see a static `SystemTime::now()` factory, and
eventually `Instant::now()` for monotonic time. I understand the
rationale for an injectable clock, but I think the two approaches can
coexist. When I simply need the current time, I would prefer:
$time = SystemTime::now();
Having to instantiate a clock, or pass one through several layers of
an existing application for an occasional time read, feels
unnecessarily cumbersome. People who need control over time can use an
injected clock; callers using the static factory would knowingly give
up that control. Is there a reason to exclude the convenience API?
Also, If we are adding `now` static method, I think adding
[`elapsed()`](time.rs - source)
would also be helpful. ( a shorter way of doing
`InstantOrSystemTime::now()->durationSince($this)` ).
Finally, I would reconsider including `FrozenClock` and `OffsetClock`.
The RFC notes that specialised clocks can have different behaviours,
but these two seem useful and sufficiently well-defined:
- `FrozenClock` takes a time value and always returns that value. (
potentionally with `advance(Duration $duration): void` and
`travelTo(SystemTime $moment): void` )
- `OffsetClock` takes another clock and a duration, and returns the
wrapped clock’s current time plus that duration.
More elaborate behaviour could remain in userland. For these basic
implementations, though, I don’t see much ambiguity in either the API
or the semantics. Including them would save users from repeatedly
implementing the same small classes or adding a Packagist dependency
for them, and would make the clock abstraction useful out of the box
for testing.
Replying with my personal opinion, not as the RFC author (since I didn't discuss this reply with Derick).
On 2026-09-22 17:14, Seifeddine Gmati wrote:
Could we name the proposed class `Time\SystemTime` and reserve
`Time\Instant` for a future high-resolution monotonic clock API? Rust
uses this distinction, and I find it helpful:
[`SystemTime`](SystemTime in std::time - Rust)
represents wall-clock time, which can change following clock
corrections, while
[`Instant`](Instant in std::time - Rust)
represents monotonic time, suitable for measuring elapsed time,
benchmarking, etc.
I also believe that “real world time” and instead of “monotonic” time will be the primary use case and would thus prefer the simpler (and more widely used) name for that.
I appreciate that monotonic time is explicitly left to future scope.
I’m suggesting we settle the naming with both concepts in mind now,
without necessarily introducing both in this RFC. This is also the
I agree that naming should be taken into account as part of this RFC, similarly how we already decided on Duration::divideInto(), despite not yet shipping it.
I would also like to see a static `SystemTime::now()` factory, and
eventually `Instant::now()` for monotonic time. I understand the
rationale for an injectable clock, but I think the two approaches can
coexist. When I simply need the current time, I would prefer:
$time = SystemTime::now();
Having to instantiate a clock, or pass one through several layers of
an existing application for an occasional time read, feels
unnecessarily cumbersome. People who need control over time can use an
injected clock; callers using the static factory would knowingly give
up that control. Is there a reason to exclude the convenience API?
For the monotonic timer having a single static constructor definitely makes sense to me, because the use cases are much different and you can't meaningfully mock it anyway.
For the real world clock, I think I would want to nudge users towards “doing the right thing” and use an injectable factory and therefore now provide the static factory, but I don't have particularly strong opinions regarding that. I don't think it should be a static method on the `Instant` class, though, because that would intimately tie it to the `SystemClock`, which feels wrong. The correct approach would probably be a `Time\now(): Instant` function.
(Note: Please keep the RFC terminology in discussion of topics that are not about changing the terminology, seeing SystemTime in your example was very confusing since the RFC also has SystemClock.)
Also, If we are adding `now` static method, I think adding
[`elapsed()`](time.rs - source)
would also be helpful. ( a shorter way of doing
`InstantOrSystemTime::now()->durationSince($this)` ).
Similarly, I can see that being a first class citizen for the monotonic timer (since the prime use case is measuring elapsed time), but not so much for the real world clock, where measuring elapsed time is just one operation of many.
Finally, I would reconsider including `FrozenClock` and `OffsetClock`.
The RFC notes that specialised clocks can have different behaviours,
but these two seem useful and sufficiently well-defined:
- `FrozenClock` takes a time value and always returns that value. (
potentionally with `advance(Duration $duration): void` and
`travelTo(SystemTime $moment): void` )
- `OffsetClock` takes another clock and a duration, and returns the
wrapped clock’s current time plus that duration.
I would be open to doing those two as an (immediate) follow-up, since I feel they have plenty of bike-shedding potential on their own. So they would also land in PHP 8.7, but not distract from the main API design.
I also believe that “real world time” and instead of “monotonic” time
will be the primary use case and would thus prefer the simpler (and more
widely used) name for that.
I think the JavaScript and Java background explains why I found the
naming confusing. `Instant` may feel natural for wall-clock time to
developers familiar with Java, and perhaps JavaScript, although
`Temporal` is still relatively new. Because i work more with Rust,
where `Instant` specifically represents monotonic time and
`SystemTime` represents wall-clock time.
I agree that wall-clock time will be used more often, but I’m not
convinced that `Instant` is necessarily simpler or clearer than
`SystemTime`. Arguably, the latter communicates more directly that the
value comes from the system clock and is subject to clock adjustments.
That said, let’s see what Derick and others think about the naming.
For the real world clock, I think I would want to nudge users towards
“doing the right thing” and use an injectable factory and therefore now
provide the static factory, but I don't have particularly strong
opinions regarding that. I don't think it should be a static method on
the `Instant` class, though, because that would intimately tie it to the
`SystemClock`, which feels wrong. The correct approach would probably be
a `Time\now(): Instant` function.
Regarding nudging users towards “doing the right thing,” I don’t think
injecting a clock is always the right choice. It is valuable when the
code needs deterministic control over time, but sometimes keeping
things simple is preferable. For an occasional time read, passing a
clock through several layers can add ceremony without providing much
practical benefit. I think a direct API and injectable clocks can
coexist without suggesting that either should be used universally.
A `Time\now()` function would address the convenience issue, although
I still think discoverability is better when the operation is
available directly on the relevant type. But I don't have a strong
opinion here.
> Also, If we are adding `now` static method, I think adding
> [`elapsed()`](time.rs - source)
> would also be helpful. ( a shorter way of doing
> `InstantOrSystemTime::now()->durationSince($this)` ).
Similarly, I can see that being a first class citizen for the monotonic
timer (since the prime use case is measuring elapsed time), but not so
much for the real world clock, where measuring elapsed time is just one
operation of many.
I also see some value in `elapsed()` for wall-clock instants, even
though I agree that it is more useful and reliable with a monotonic
clock. For example, I might use something like this for quick
debugging or logging:
For this kind of informal measurement, I may not need the stronger
guarantees of a monotonic clock. The operation would, of course,
remain subject to wall-clock adjustments. Rust returns an error if the
clock moves backwards; PHP could throw an exception in that case. I’m
not strongly attached to including this method for wall-clock time,
but I do think it has plausible uses.
> - `FrozenClock` takes a time value and always returns that value. (
> potentionally with `advance(Duration $duration): void` and
> `travelTo(SystemTime $moment): void` )
> - `OffsetClock` takes another clock and a duration, and returns the
> wrapped clock’s current time plus that duration.
I would be open to doing those two as an (immediate) follow-up, since I
feel they have plenty of bike-shedding potential on their own. So they
would also land in PHP 8.7, but not distract from the main API design.
I’m fine with leaving `FrozenClock` and `OffsetClock` to an immediate
follow-up RFC. My only concern is the current wording that test clocks
are better left to userland because their behaviour varies widely.
That argument could later be used against adding even these
well-defined clocks to the standard library. Perhaps they could
instead be mentioned explicitly under “Future Scope,” without
committing this RFC to their exact APIs.
I agree that wall-clock time will be used more often, but I’m not
convinced that `Instant` is necessarily simpler or clearer than
`SystemTime`. Arguably, the latter communicates more directly that the
value comes from the system clock and is subject to clock adjustments.
That said, let’s see what Derick and others think about the naming.
Yes, `Instant` is clearer than `SystemTime`, because of the `Time` suffix that implies “time as shown on a clock”, which is *not* what the proposed `Instant` represents: Namely a point on the timeline of the universe. Other names that I've seen used for this concept are “Timestamp” or “Moment”, with “Instant” being the most widely used in the references we looked at.
Specifically, as part of future scope we're also planning for “Date” and “Time” classes that represent a calendar date (year, month, day) and a “clock time” (hour, minute, second, fractional second) respectively. Java calls these LocalDate and LocalTime. Calling the “point in time” class “SystemTime” would invite confusion with whatever the “clock time” object would be.
A `Time\now()` function would address the convenience issue, although
I still think discoverability is better when the operation is
available directly on the relevant type. But I don't have a strong
opinion here.
Java has an overloaded Instant.now() and Instant.now(Clock) where the parameter-less version defaults to the system clock. That would theoretically work for PHP, but I feel that a free-standing function matches PHP’s existing API design and capabilities better (Java doesn't have bare functions, unless that changed since I last looked at it).
I also see some value in `elapsed()` for wall-clock instants, even
though I agree that it is more useful and reliable with a monotonic
clock. For example, I might use something like this for quick
debugging or logging:
For this kind of informal measurement, I may not need the stronger
guarantees of a monotonic clock. The operation would, of course,
remain subject to wall-clock adjustments. Rust returns an error if the
clock moves backwards; PHP could throw an exception in that case. I’m
not strongly attached to including this method for wall-clock time,
but I do think it has plausible uses.
The monotonic clock would not be much more complicated. Using the hypothetical name “Stopwatch”, it could be:
Where the 'execution' snapshot would be measured starting from the 'preparation' snapshot. Or even:
$duration = Stopwatch::measure(execute(...));
to measure a single function call.
I would be open to doing those two as an (immediate) follow-up, since I
feel they have plenty of bike-shedding potential on their own. So they
would also land in PHP 8.7, but not distract from the main API design.
I’m fine with leaving `FrozenClock` and `OffsetClock` to an immediate
follow-up RFC. My only concern is the current wording that test clocks
are better left to userland because their behaviour varies widely.
That argument could later be used against adding even these
well-defined clocks to the standard library. Perhaps they could
instead be mentioned explicitly under “Future Scope,” without
committing this RFC to their exact APIs.
Fair enough. I'll put rephrasing that paragraph on my list to discuss with Derick for the next RFC update.
I agree that wall-clock time will be used more often, but I’m not
convinced that `Instant` is necessarily simpler or clearer than
`SystemTime`. Arguably, the latter communicates more directly that the
value comes from the system clock and is subject to clock adjustments.
That said, let’s see what Derick and others think about the naming.
Hi!
Is this not an argument FOR `Instant` instead of against it?
`SystemTime` sounds more like something that would belong in that `Clock
namespace while `Instant` represents a point in time that might well be
far before or after the existence of the system itself. It does not
advertise any relation to the system or wall clock. It could be the time
of the big bang. Naming it `SystemTime` wouldn't make any sense.
Later on though you are mentioning that "Following the lead of virtually all programming languages and operating systems, leap seconds are ignored."
Is that only with relation to the Iso8601 strings? Or is that a general shortcoming?
Leap seconds are treated as non-existent as an integral part of the class design. Operating systems, in particular Unix-based systems as the arguably most common runtime environment of PHP, don't expose leap seconds in their time retrieval APIs and pretend very hard that leap seconds don't exist, either by “halting the clock” for 1 second, by repeating the second, or by smearing the leap second over a larger time-frame of “slightly more than a second”-seconds (e.g. across the preceding 1000 seconds as proposed by UTC-SLS).
Trying to support leap seconds in PHP when the operating system does not would mean that PHP would need to keep its own list of when a leap second should have occurred and would require users to actually update PHP to get correct information (which is something that most certainly won't happen).
It would necessarily also mean that the difference between two Instant’s ->getUnixTimestamp() values would differ from the duration returned by `until()` and that two Instants with the same Unix timestamps might compare unequal.
Given that virtually everyone ignores leap seconds and given that there hasn't been one for the past 10 years and there are plans to abolish them by 2035, I don't think it is useful to make PHP in any way special. This would just invite bug reports from confused users.
So no shortcoming, intentional and deliberate design decision. The “unique point on the timeline” phrasing is to establish a reasonable mental model of why Instants don’t have a timezone and why this is a useful concept, not as a 100% accurate representation of physics.
The other point I find irritating is the naming of the `Clock` interface. While I can understand the reasoning behind it I see irritation in the community upcoming as the `CLockInterface` now suddenly returns 2 different things, depending on which ClockInterface people are talking about.
While it doesn't seem to be written down as policy - but I could swear this topic came up on Internals at least - PHP doesn't use the `Interface` suffix to indicate interfaces in newly designed APIs. Following the precedent established by ext/random in PHP 8.2 with Random\Engine, the interface is the bare name and the concrete implementations sit in a sub-namespace named after the interface.
Thus there is a clear difference in naming even for the unqualified interface name.
Is there a way to Unify the ideas behind Time\Clock and Psr\Clock\CLockInterface?
The proposed new date/time API is a greenfield design to fix issues with the existing API. We deliberately don't want to “infect” the new API with ideas from the legacy API and returning an object carrying a timezone from a clock, as done in PSR-20, is the wrong design. `now()` is the obvious name for the method and that's why the RFC indicates that an adapter is required to satisfy both interfaces from the same source of truth during the migration phase.
As right now the Time\Clock is not a direct replacement of Psr\Clock\ClockInterface as - at the current time - there is no way to generate a DateTimeImmutable directly from a Time\Instant.
Yesn't: You can bridge the gap by using DateTimeImmutable::createFromTimestamp($instant->getUnixTimestamp())->setMicrosecond(intdiv($instant->getNanoseconds(), 1000)).
That said, I acknowledge that it's not particularly pretty and we discussed adding another fromInstant() constructor and ->toInstant() getter to DateTimeImmutable. The compatibility layer would be sitting on the old API to keep the new API clean.
Thanks for the feedback!
On 23 Sep 2026, at 12:24, Tim Düsterhus wrote:
The other point I find irritating is the naming of the Clock interface. While I can understand the reasoning behind it I see irritation in the community upcoming as the CLockInterface now suddenly returns 2 different things, depending on which ClockInterface people are talking about.
While it doesn’t seem to be written down as policy - but I could swear this topic came up on Internals at least - PHP doesn’t use the Interface suffix to indicate interfaces in newly designed APIs. Following the precedent established by ext/random in PHP 8.2 with Random\Engine, the interface is the bare name and the concrete implementations sit in a sub-namespace named after the interface.
Thus there is a clear difference in naming even for the unqualified interface name.
I am totally in favour of omitting the Interface. Still people will refer to the Clock Interface and the ClockInterface (Hopefully never the ClockInterface Interface). So - while the naming is clearly distinct also due to the different namespaces - People will refer to both using the same “name”.
But I do assume that very soon people will refer either to the PHP Clock interface and the FIG (or PSR) ClockInterface.
Is there a way to Unify the ideas behind Time\Clock and Psr\Clock\CLockInterface?
The proposed new date/time API is a greenfield design to fix issues with the existing API. We deliberately don’t want to “infect” the new API with ideas from the legacy API and returning an object carrying a timezone from a clock, as done in PSR-20, is the wrong design. now() is the obvious name for the method and that’s why the RFC indicates that an adapter is required to satisfy both interfaces from the same source of truth during the migration phase.
As right now the Time\Clock is not a direct replacement of Psr\Clock\ClockInterface as - at the current time - there is no way to generate a DateTimeImmutable directly from a Time\Instant.
Yesn’t: You can bridge the gap by using DateTimeImmutable::createFromTimestamp($instant->getUnixTimestamp())->setMicrosecond(intdiv($instant->getNanoseconds(), 1000)).
That said, I acknowledge that it’s not particularly pretty and we discussed adding another fromInstant() constructor and ->toInstant() getter to DateTimeImmutable. The compatibility layer would be sitting on the old API to keep the new API clean.
Sounds absolutely right.
Thanks for the explanations!
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 |
±--------------------------------------------------------------------+
This time we have several months until the next feature freeze, so
there’s place to discuss additional functionality that we missed.
Nevertheless we would like to keep the RFC focused so that every
decision gets the attention it deserves and some bits are already listed
as “Future Scope” to that effect.
Best regards
Tim Düsterhus
I am broadly in favor of this proposal, though I have some questions/concerns.
(In reading order...)
- Why is the representable range unspecified? Can we at least hint at what it is? If I cannot even guarantee that next week is within range, I basically cannot trust this API at all. (Later on in the prose the text says "the same as DateTimeImmutable", but I don't know what that is off hand. It should be specified more explicitly, and that later captured in documentation, even if it's just a minimum and subject to system details.)
- Why is a compare() method needed if Instant already supports <, >, etc?
- "Serialization of Time\Instant will be supported, the exact format is considered an implementation detail. " - Should be a period, not a comma. Also, the serialization format of other values has often been subject to discussion (eg, Enums), so why is it omitted here?
- I am fine with this being essentially a port of PSR-20, and the naming doesn't bother me.
- I also agree that not having Instant:now() is the correct approach. If someone really wants to just pluck the current time out of thin air, testing be damned, that's easy enough:
$now = new SystemClock()->now();
Moreover, making it injectable is also quite trivial thanks to modern syntax:
class SomeService {
public function __construct(
private SomeDependency $dep,
private Clock $clock = new SystemClock(),
) {}
public function doThings() {
$now = $this->clock->now(); // The system time, unless it's been overridden by injection.
// ...
}
}
That's a perfectly fine approach in my mind, and offers all the pieces of flexibility that we need in that regard.
That said, I am also on-board with having the trivial/obvious test clocks provided in core, be they in this RFC or a follow-up.
In short, yes please deprecate PSR-20.
- It's not clear to me how I would serialize an Instant outside of PHP, say to store it in a DB. UTC ISO8601? If so, that should be stated explicitly (and again, replicated to the docs eventually) to avoid confusion and people trying to invent clever mechanisms.
My big issue, though, is context. This is very clearly part 2 of N, which is fine. Presumably you and Derick have a roadmap between you of what the end goal looks like and the moving parts that will be added, even if it's in steps. But that's not documented anywhere other than your heads, AFAIK, and the future scope section as currently written is barely 3 of N, and not in detail.
I realize that designs change over time as they're implemented, which is totally fine, and all the details of later steps have likely not been worked out yet, which is also totally fine. But I still need to see the big picture, at least at a coarse resolution, to understand this RFC in context. Things that we feel are missing may be missing not out of incompleteness but "that's step N+3", which may be a perfectly good design but that's not at all apparent.
I would strongly urge you to post (probably as an RFC page, even though it's not a full RFC) an overarching roadmap/plan for PHP Time TNG, so we can get a sense of where we're going. That will allow us to see each RFC in its appropriate context, and clearly note when things have been pushed to later that "later" is defined and documented. (Eg, the FrozenClock idea.)
- Why is the representable range unspecified? Can we at least hint at what it is? If I cannot even guarantee that next week is within range, I basically cannot trust this API at all. (Later on in the prose the text says "the same as DateTimeImmutable", but I don't know what that is off hand. It should be specified more explicitly, and that later captured in documentation, even if it's just a minimum and subject to system details.)
“and will be left to implementation constraints”. I plan to clarify that further when the general API design is finalized and Derick and I are looking into the implementation to figure out the details. The limits will be “reasonable computational limits” - so you’ll be able to represent Instants until at least the release of PHP 8.7
I added an item to the Open Issues to keep track of that.
- Why is a compare() method needed if Instant already supports <, >, etc?
API consistency with Duration. Also a method has proper typing and works with first class callables / PFA. Duration specifically had this example:
usort($durations, Duration::compare(...));
Also, the serialization format of other values has often been subject to discussion (eg, Enums), so why is it omitted here?
The internal state of Instant is deliberately unspecified and that extends to serialization. The serialization format will be portable across 32- and 64-bit versions of PHP and backwards compatibility with existing serialized data will be kept across PHP versions. I just clarified that part in the RFC text. If you need to look at the output of serialization you are doing something very wrong. I consider the enum discussion to be a different case, since they introduced an entirely new type specifier `E`. Instant will just be a plain old class that implements (the equivalent of) `__serialize()` and `__unserialize()`.
- It's not clear to me how I would serialize an Instant outside of PHP, say to store it in a DB. UTC ISO8601? If so, that should be stated explicitly (and again, replicated to the docs eventually) to avoid confusion and people trying to invent clever mechanisms.
As with any other value object, the best serialization format depends on your exact use case. If an HTTP API provides you times as the number of milliseconds since the Unix epoch, then you would use the `fromUnixTimestampMilliseconds()` constructor to create an Instant from it. If an API expects an ISO-8601 string as input, you would use the `toIso8601DateTimeString()` getter. All the getters and constructors are valid options for serialization and each constructor is paired with a getter. Even just `serialize($instant)` can be a valid option. As already noted in the RFC, the ISO-8601 format (and of course `serialize()`) is the portable one that is capable of representing all possible Instants.
For custom formats, in particular formats with an implied timezone, you’ll need to wait for the future scope “Instant + Timezone” class.
My big issue, though, is context. This is very clearly part 2 of N, which is fine. Presumably you and Derick have a roadmap between you of what the end goal looks like and the moving parts that will be added, even if it's in steps. But that's not documented anywhere other than your heads, AFAIK, and the future scope section as currently written is barely 3 of N, and not in detail.
I realize that designs change over time as they're implemented, which is totally fine, and all the details of later steps have likely not been worked out yet, which is also totally fine. But I still need to see the big picture, at least at a coarse resolution, to understand this RFC in context. Things that we feel are missing may be missing not out of incompleteness but "that's step N+3", which may be a perfectly good design but that's not at all apparent.
I would strongly urge you to post (probably as an RFC page, even though it's not a full RFC) an overarching roadmap/plan for PHP Time TNG, so we can get a sense of where we're going. That will allow us to see each RFC in its appropriate context, and clearly note when things have been pushed to later that "later" is defined and documented. (Eg, the FrozenClock idea.)
Each part of the series is intended to stand on its own and to be complete enough to be usable for production use cases rather than just toy examples. The end goal is to model (all) real-world concepts of time and the focus of each RFC text is explaining that concept and how it was modeled. If you expected the modeling to cover something and the RFC doesn’t mention it (“I expected it to do X, why doesn’t it?”), we failed at that explanation and I recommend raising the issue on the mailing list.
The evolution of the new date and time API is also not linear, but rather a graph connecting the various concepts that can be worked on in any order, as long as the dependencies are satisfied. To get a rough idea of where we’re heading you can take a look at java.time and JavaScript’s Temporal.
Thank you for considering the ISO 8601 date-time representation in the new API !
If I may add one small suggestion: could toIso8601DateTimeString() be shortened to toIso8601String() and fromIso8601DateTimeString() to fromIso8601String() ?
Since Time\Instant already represents a date-time value, these shorter names remain clear and are easier to use.
Best regards,
Théo
On 23 Sep 2026, at 19:29, Tim Düsterhus <tim@bastelstu.be> wrote:
Hi
On 2026-09-23 17:02, Larry Garfield wrote:
- Why is the representable range unspecified? Can we at least hint at what it is? If I cannot even guarantee that next week is within range, I basically cannot trust this API at all. (Later on in the prose the text says "the same as DateTimeImmutable", but I don't know what that is off hand. It should be specified more explicitly, and that later captured in documentation, even if it's just a minimum and subject to system details.)
“and will be left to implementation constraints”. I plan to clarify that further when the general API design is finalized and Derick and I are looking into the implementation to figure out the details. The limits will be “reasonable computational limits” - so you’ll be able to represent Instants until at least the release of PHP 8.7
I added an item to the Open Issues to keep track of that.
- Why is a compare() method needed if Instant already supports <, >, etc?
API consistency with Duration. Also a method has proper typing and works with first class callables / PFA. Duration specifically had this example:
usort($durations, Duration::compare(...));
Also, the serialization format of other values has often been subject to discussion (eg, Enums), so why is it omitted here?
The internal state of Instant is deliberately unspecified and that extends to serialization. The serialization format will be portable across 32- and 64-bit versions of PHP and backwards compatibility with existing serialized data will be kept across PHP versions. I just clarified that part in the RFC text. If you need to look at the output of serialization you are doing something very wrong. I consider the enum discussion to be a different case, since they introduced an entirely new type specifier `E`. Instant will just be a plain old class that implements (the equivalent of) `__serialize()` and `__unserialize()`.
- It's not clear to me how I would serialize an Instant outside of PHP, say to store it in a DB. UTC ISO8601? If so, that should be stated explicitly (and again, replicated to the docs eventually) to avoid confusion and people trying to invent clever mechanisms.
As with any other value object, the best serialization format depends on your exact use case. If an HTTP API provides you times as the number of milliseconds since the Unix epoch, then you would use the `fromUnixTimestampMilliseconds()` constructor to create an Instant from it. If an API expects an ISO-8601 string as input, you would use the `toIso8601DateTimeString()` getter. All the getters and constructors are valid options for serialization and each constructor is paired with a getter. Even just `serialize($instant)` can be a valid option. As already noted in the RFC, the ISO-8601 format (and of course `serialize()`) is the portable one that is capable of representing all possible Instants.
For custom formats, in particular formats with an implied timezone, you’ll need to wait for the future scope “Instant + Timezone” class.
My big issue, though, is context. This is very clearly part 2 of N, which is fine. Presumably you and Derick have a roadmap between you of what the end goal looks like and the moving parts that will be added, even if it's in steps. But that's not documented anywhere other than your heads, AFAIK, and the future scope section as currently written is barely 3 of N, and not in detail.
I realize that designs change over time as they're implemented, which is totally fine, and all the details of later steps have likely not been worked out yet, which is also totally fine. But I still need to see the big picture, at least at a coarse resolution, to understand this RFC in context. Things that we feel are missing may be missing not out of incompleteness but "that's step N+3", which may be a perfectly good design but that's not at all apparent.
I would strongly urge you to post (probably as an RFC page, even though it's not a full RFC) an overarching roadmap/plan for PHP Time TNG, so we can get a sense of where we're going. That will allow us to see each RFC in its appropriate context, and clearly note when things have been pushed to later that "later" is defined and documented. (Eg, the FrozenClock idea.)
Each part of the series is intended to stand on its own and to be complete enough to be usable for production use cases rather than just toy examples. The end goal is to model (all) real-world concepts of time and the focus of each RFC text is explaining that concept and how it was modeled. If you expected the modeling to cover something and the RFC doesn’t mention it (“I expected it to do X, why doesn’t it?”), we failed at that explanation and I recommend raising the issue on the mailing list.
The evolution of the new date and time API is also not linear, but rather a graph connecting the various concepts that can be worked on in any order, as long as the dependencies are satisfied. To get a rough idea of where we’re heading you can take a look at java.time and JavaScript’s Temporal.
On Wed, Sep 23, 2026, at 12:29 PM, Tim Düsterhus wrote:
- Why is a compare() method needed if Instant already supports <, >,
etc?
API consistency with Duration. Also a method has proper typing and works
with first class callables / PFA. Duration specifically had this
example:
usort($durations, Duration::compare(...));
Ah, valid use case indeed. Thanks.
For custom formats, in particular formats with an implied timezone,
you’ll need to wait for the future scope “Instant + Timezone” class.
I am indeed waiting with baited breath. (That will be the DateTimeImmutable replacement, essentially, yes?)
My big issue, though, is context. This is very clearly part 2 of N,
which is fine. Presumably you and Derick have a roadmap between you of
what the end goal looks like and the moving parts that will be added,
even if it's in steps. But that's not documented anywhere other than
your heads, AFAIK, and the future scope section as currently written is
barely 3 of N, and not in detail.
I realize that designs change over time as they're implemented, which
is totally fine, and all the details of later steps have likely not
been worked out yet, which is also totally fine. But I still need to
see the big picture, at least at a coarse resolution, to understand
this RFC in context. Things that we feel are missing may be missing
not out of incompleteness but "that's step N+3", which may be a
perfectly good design but that's not at all apparent.
I would strongly urge you to post (probably as an RFC page, even though
it's not a full RFC) an overarching roadmap/plan for PHP Time TNG, so
we can get a sense of where we're going. That will allow us to see
each RFC in its appropriate context, and clearly note when things have
been pushed to later that "later" is defined and documented. (Eg, the
FrozenClock idea.)
Each part of the series is intended to stand on its own and to be
complete enough to be usable for production use cases rather than just
toy examples. The end goal is to model (all) real-world concepts of time
and the focus of each RFC text is explaining that concept and how it was
modeled. If you expected the modeling to cover something and the RFC
doesn’t mention it (“I expected it to do X, why doesn’t it?”), we failed
at that explanation and I recommend raising the issue on the mailing
list.
The evolution of the new date and time API is also not linear, but
rather a graph connecting the various concepts that can be worked on in
any order, as long as the dependencies are satisfied. To get a rough
idea of where we’re heading you can take a look at java.time and
JavaScript’s Temporal.
Then please share the graph. There is nowhere right now that someone can go to canonically see "oh, they are planning to add a new timezone object and a new DateTimeImmutable replacement later, too. Good to know."
I know you and I have discussed this before, but I am going to keep riding this horse: You clearly have a roadmap in your heads. Share it. At whatever level of granularity it exists, share it. We need that sort of context., It builds trust in the process and in the people that are working on it. Everyone understands it's not a promise, but a communication mechanism, and a very important one for this sort of effort.
Following up on my previous message about the method names,I noticed one separate technical point.
The RFC specifies nanosecond precision for Time\Instant, but it is not clear whether toIso8601DateTimeString() preserves that precision in its output. Could the RFC clarify the fractional-second format and whether an optional precision argument is supported?
I’m suggesting this only to clarify the existing method’s contract.
Best regards,
Théo
On 23 Sep 2026, at 19:29, Tim Düsterhus <tim@bastelstu.be> wrote:
Hi
On 2026-09-23 17:02, Larry Garfield wrote:
- Why is the representable range unspecified? Can we at least hint at what it is? If I cannot even guarantee that next week is within range, I basically cannot trust this API at all. (Later on in the prose the text says "the same as DateTimeImmutable", but I don't know what that is off hand. It should be specified more explicitly, and that later captured in documentation, even if it's just a minimum and subject to system details.)
“and will be left to implementation constraints”. I plan to clarify that further when the general API design is finalized and Derick and I are looking into the implementation to figure out the details. The limits will be “reasonable computational limits” - so you’ll be able to represent Instants until at least the release of PHP 8.7
I added an item to the Open Issues to keep track of that.
- Why is a compare() method needed if Instant already supports <, >, etc?
API consistency with Duration. Also a method has proper typing and works with first class callables / PFA. Duration specifically had this example:
usort($durations, Duration::compare(...));
Also, the serialization format of other values has often been subject to discussion (eg, Enums), so why is it omitted here?
The internal state of Instant is deliberately unspecified and that extends to serialization. The serialization format will be portable across 32- and 64-bit versions of PHP and backwards compatibility with existing serialized data will be kept across PHP versions. I just clarified that part in the RFC text. If you need to look at the output of serialization you are doing something very wrong. I consider the enum discussion to be a different case, since they introduced an entirely new type specifier `E`. Instant will just be a plain old class that implements (the equivalent of) `__serialize()` and `__unserialize()`.
- It's not clear to me how I would serialize an Instant outside of PHP, say to store it in a DB. UTC ISO8601? If so, that should be stated explicitly (and again, replicated to the docs eventually) to avoid confusion and people trying to invent clever mechanisms.
As with any other value object, the best serialization format depends on your exact use case. If an HTTP API provides you times as the number of milliseconds since the Unix epoch, then you would use the `fromUnixTimestampMilliseconds()` constructor to create an Instant from it. If an API expects an ISO-8601 string as input, you would use the `toIso8601DateTimeString()` getter. All the getters and constructors are valid options for serialization and each constructor is paired with a getter. Even just `serialize($instant)` can be a valid option. As already noted in the RFC, the ISO-8601 format (and of course `serialize()`) is the portable one that is capable of representing all possible Instants.
For custom formats, in particular formats with an implied timezone, you’ll need to wait for the future scope “Instant + Timezone” class.
My big issue, though, is context. This is very clearly part 2 of N, which is fine. Presumably you and Derick have a roadmap between you of what the end goal looks like and the moving parts that will be added, even if it's in steps. But that's not documented anywhere other than your heads, AFAIK, and the future scope section as currently written is barely 3 of N, and not in detail.
I realize that designs change over time as they're implemented, which is totally fine, and all the details of later steps have likely not been worked out yet, which is also totally fine. But I still need to see the big picture, at least at a coarse resolution, to understand this RFC in context. Things that we feel are missing may be missing not out of incompleteness but "that's step N+3", which may be a perfectly good design but that's not at all apparent.
I would strongly urge you to post (probably as an RFC page, even though it's not a full RFC) an overarching roadmap/plan for PHP Time TNG, so we can get a sense of where we're going. That will allow us to see each RFC in its appropriate context, and clearly note when things have been pushed to later that "later" is defined and documented. (Eg, the FrozenClock idea.)
Each part of the series is intended to stand on its own and to be complete enough to be usable for production use cases rather than just toy examples. The end goal is to model (all) real-world concepts of time and the focus of each RFC text is explaining that concept and how it was modeled. If you expected the modeling to cover something and the RFC doesn’t mention it (“I expected it to do X, why doesn’t it?”), we failed at that explanation and I recommend raising the issue on the mailing list.
The evolution of the new date and time API is also not linear, but rather a graph connecting the various concepts that can be worked on in any order, as long as the dependencies are satisfied. To get a rough idea of where we’re heading you can take a look at java.time and JavaScript’s Temporal.
Note: When replying on this list, please keep your reply below the quoted parts and cut the quote to a minimum. And please try to reply to the fit appropriate email to keep the threading from working well (see the thread view at the bottom of php.internals: Re: [RFC] Time\Instant and Time\Clock), in the Gmail web interface you can do that by using the Reply button on the email itself, instead of the big box at the bottom.
On 2026-09-23 22:16, Théo Attali wrote:
The RFC specifies nanosecond precision for Time\Instant, but it is not clear whether toIso8601DateTimeString() preserves that precision in its output. Could the RFC clarify the fractional-second format and whether an optional precision argument is supported?
As the RFC notes, the ISO-8601 representation is the universal one. It therefore keeps full nanosecond precision. I clarified that in the doc comment of the stub after having made the executive decision that it will omit the fractional part if it is zero.
No precision argument is supported, and I don't think it would be a good idea doing that as part of the “presentation layer”. Cutting off the fractional part is possible using $instant->sub(Duration::fromNanoseconds($instant->getNanoseconds())), which while a mouthful would be an explicit action. I can imagine adding a `->truncateTo()` method in the future, but that one will require some design as to how to specify the the desired precision in a useful way without opening up footguns.
For custom formats, in particular formats with an implied timezone,
you’ll need to wait for the future scope “Instant + Timezone” class.
I am indeed waiting with baited breath. (That will be the DateTimeImmutable replacement, essentially, yes?)
Instant+Timezone would be the same information that DateTimeImmutable holds, yes. What would likely be another future scope after that is the “calendar-based math” (i.e. the replacement for what's currently DateInterval and `setDate()` and similar) since that's an entire rabbit hole of its own
There is nowhere right now that someone can go to canonically see "oh, they are planning to add a new timezone object and a new DateTimeImmutable replacement later, too. Good to know."
Well, it's listed in the Future Scope of this one. And me pointing towards java.time and JavaScript’s Temporal was a sincere answer: The extent of the planning is that it will look somewhat like that and each step is slicing off a small standalone part along the way.
If I may add one small suggestion: could toIso8601DateTimeString() be shortened to toIso8601String() and fromIso8601DateTimeString() to fromIso8601String() ?
Since Time\Instant already represents a date-time value, these shorter names remain clear and are easier to use.
The proposed wording is consistent with `Time\Duration::fromIso8601DurationString()` which also includes the explicit “Duration” indicator. It also has some practical benefits: ISO-8601 defines a multitude of interchange formats that could be reasonably interpreted as being applicable, particularly with some components being implied as accepted by DateTimeImmutable. As an example `Instant::fromIso8601String('2026-09-25')` could be argued to mean “midnight UTC on 2026-09-25”.
I believe the naming strikes an appropriate middle-ground between specific, while not being overly verbose and it leaves some future space for a `toIso8601OrdinalDateTimeString()` to emit `2026-268T13:55:00Z` (with 268 referring to the 268th day of the year 2026), with the 2026-09-25 being the “calendar date” format, where the “calendar” part is being implied (and also the “extended” representation being implied).
I just had a chat with Derick about the discussion points that have come up and made some changes to the RFC as a result:
- We added `DateTime(Immutable)::toInstant()` to the proposal as a basic compatibility layer between the old and new API. `toInstant()` naturally drops the timezone information, but is otherwise fully lossless, since Time\Instant is capable of representing better precison (nanoseconds) than DTI and DT (microseconds). A `DTI::fromInstant()` is missing, since it's not obvious how to deal with the extended precision. Truncating might be one choice, rejecting another. The snippet mentioned in php.internals: Re: [RFC] Time\Instant and Time\Clock will work and make the decision (truncation) explicit.
- The behavior of `Instant::toIso8601DateTimeString()` regarding fractional seconds was adjusted again: Trailing zeros will now be kept to make the precision supported by Time\Instant explicit. `2026-09-29T16:42:45.5Z` could mean “exactly 0.5 seconds” or “0.5 seconds ± 0.05 seconds” whereas `2026-09-29T16:42:45.500000000Z` is explicit as to how precise the data is. This might matter for consumers that also store an “uncertainty” range.
- The language regarding testing clocks as adjusted as per Seifeddine’s suggestion: They are not ruled out explicitly and mentioned as possible future scope. But Derick and I want to be clear in that we don’t plan to write an RFC for testing clocks, since we remain unconvinced they belong in core. Personally I also haven't had sufficient exposure to testing clocks in practice, so I'm not the best person to design the API. Instead we want to invite you (as the PHP internals community) to write the follow-up RFC for testing clocks if you'd like to see them in core and have a good API design ready. Given that the clocks are scoped away in the dedicated Time\Clock namespace this follow-up RFC also won't need any coordination with Derick and I and doesn't conflict with our vision for the new date/time API.
It didn't result in a change to the RFC, but Derick confirmed he agreed with my reasoning regarding the naming of the ISO-8601 constructor / getter that I provided in php.internals: Re: [RFC] Time\Instant and Time\Clock. So I would consider that discussion thread settled.
----------------
Other than that it feels that this RFC has much fewer opinion than Time\Duration in the same timeframe. Are you all happy and thus didn't say anything or did you not get around to reading and evaluating the RFC? If it's the former, feel free to provide a LGTM, ship it. And if there's anything that bothers you or remains unclear, please mention it, no matter how big or small.
- The behavior of `Instant::toIso8601DateTimeString()` regarding fractional seconds was adjusted again: Trailing zeros will now be kept to make the precision supported by Time\Instant explicit. `2026-09-29T16:42:45.5Z` could mean “exactly 0.5 seconds” or “0.5 seconds ± 0.05 seconds” whereas `2026-09-29T16:42:45.500000000Z` is explicit as to how precise the data is. This might matter for consumers that also store an “uncertainty” range.
Forgot to mention, but it's in the RFC: The fractional part will be entirely omitted if it's zero. While that drops the explicit precision information in *that* case, it should also improve interoperability with consumers who are not prepared to handle fractional seconds. A consumer that already supports fractional seconds should be able to deal with trailing zeros just fine.
Other than that it feels that this RFC has much fewer opinion than
Time\Duration in the same timeframe. Are you all happy and thus
didn't say anything or did you not get around to reading and
evaluating the RFC? If it's the former, feel free to provide a LGTM,
ship it. And if there's anything that bothers you or remains unclear,
please mention it, no matter how big or small.
Well, I do have 8 comments on the Request For Comments (RFC).
This is a lengthy post, be prepared for a long reading time.
=========
Comment 1
Is \Time\Instant meant to be immutable? If so, please state that in
the RFC.
=========
Comment 2
The RFC states:
An Time\Instant carries no timezone. It represents a unique point
on the timeline of the universe.
Why is there no explicit or implicit timezone attached? The RFC reads
to me like there is an implicit timezone attached, namely the UTC
timezone. Especially the `toIso8601DateTimeString()` function which
always returns the 'Z' timezone identifier seems to imply an implicit
UTC timezone?
=========
Comment 3
Why is it called \Time\Instant? I find \Time\UtcInstant a better name,
which embeds the timeline unit used.
\Time\UtcInstant leaves room for future different timeline Instants
like:
- \Time\TaiInstant (International Atomic Time)
- \Time\Ut1Instant (Universal Time)
- \Time\TtInstant (Terrestrial Time)
=========
Comment 4
The \Time\Clock interface conflicts with PSR-20 Clock interface, see PSR-20: Clock - PHP-FIG . A class can never implement both
interfaces at the same time. This will hinder adoption.
Consider renaming the `function now(): \Time\Instant` function to
`function nowAsInstant(): \Time\Instant`.
To clarify, with the current proposal the following will not work:
<?php
namespace Time
{
final readonly class Instant
{
// Omitted for the sake of brevity.
}
interface Clock
{
/**
* The current instant, at the resolution offered by the
* underlying clock.
*/
public function now(): Instant;
}
}
namespace Psr\Clock
{
interface ClockInterface
{
/**
* Returns the current time as a DateTimeImmutable Object
*/
public function now(): \DateTimeImmutable;
}
}
namespace MyApp
{
class MyClock implements \Psr\Clock\ClockInterface, \Time\Clock
{
public function now(): \DateTimeImmutable
{
return new \DateTimeImmutable();
}
public function nowAsInstant(): \Time\Instant
{
// return (new \DateTimeImmutable())->toInstant();
}
}
}
// Fatal error: Declaration of MyApp\MyClock::now(): DateTimeImmutable
// must be compatible with Time\Clock::now(): Time\Instant
?>
=========
Comment 5
The RFC introduces a SystemClock class:
<?php
final readonly class SystemClock implements \Time\Clock
{
public function __construct() {}
/**
* The current instant, at the resolution offered by the platform
* clock,
* which is generally coarser than nanoseconds.
*
* Returned values are not guaranteed to be monotonically
* increasing across calls.
*/
public function now(): \Time\Instant {}
}
?>
Why is SystemClock not a Singleton? Can there be multiple different
system clocks in practice? I believe a System Clock, with the emphasis
on "System" is a System Wide clock. This clock is not bound to PHP,
but to all processes running on the system, including Apache,
shell-scripts, NGINX and others.
The RFC also states a FrozenClock for testing purposes. Well this
pattern is not always going to work. Especially when an application
(the main program) uses Composer libraries and wants to freeze time
for each and everyone, including the `vendor` directory. Because not
every library in the `vendor` directory uses SystemClock. They all
use `time()`, because that is currently the standard way to obtain
the System Clock. Time freezing must involve the `time()` function!
I think `SystemClock` should be removed from this RFC, it deserves
a seperate RFC. That RFC should also explore a frozen `time()`
function. Meaning the output of `time()` can be frozen recursively.
Because of this probably the \Time\Clock interface should also
be moved to that new RFC.
After removal, how to obtain a \Time\Instant from the System Clock
is in comment 6.
=========
Comment 6
Why does \Time\Instant not have a named constructor
`public function fromSystemClock(): \Time\Instant` ?
Why all the complexity of constructing a \Time\SystemClock and
calling `now()`? The System Clock is a Singleton and is always
available, as the `time()` function shows.
=========
Comment 7
The RFC states:
PHP currently offers two ways to represent such a point, and both
are unsatisfying for the general case. A plain int as returned by
time() discards all sub-second precision and carries no type
information, leaving the question of “is this seconds, milliseconds
or something entirely different” to documentation instead of the
type system.
I want to bring something else to the table. What if one wants *less*
precision? What if one does not want seconds, milliseconds,
microseconds, nanoseconds? How is that going to fit into the
type system?
As a practical example, take a public transportation coach. The coach
has a schedule which is expressed in *minutes*, never in *seconds*. The
same argument, only *minutes* precision, goes for flight departures
and arrivals. Also ferries express their times in *minutes*.
One could use a milliseconds \Time\Instant and state that seconds and
lower must always be 0. But how can the type system then differentiate
between a milliseconds precision \Time\Instant and a minutes precision
\Time\Instant? Both are exactly the same type.
I think *precision* should somehow be an explicit part of \Time\Instant
class. How precise is the \Time\Instant? That can only be answered by
the application/system who new-ed the \Time\Instant. Even with
`fromUnixTimestamp()` the precision might not be 'seconds', if the
agreement in the application-system is to never provide seconds, then
only timestamps having :00 seconds are valid.
Also the struggle with `toIso8601DateTimeString()` and its output
precision indicates there is a need for explicitly specifying the
precision. That would also eliminate the assumption that a fractional
part can be omitted if it is 0. PHP can never guess in what
system/application or communication the \Time\Instant is involved
and should never guess its precision.
=========
Comment 8
The RFC states:
Extensions may want to adjust their API to take Time\Instant
in addition to an int representing a Unix timestamp or an
object implementing DateTimeInterface.
With regard to the `DateTimeInterface`, I want to share that this
interface is an internal interface and can't be implemented by
user-space classes. See also issue #23543 at
To clarify, this does not work:
<?php
class MyTime implements DateTimeInterface
{
}
// Fatal error: DateTimeInterface can't be implemented by user classes
?>