Hi
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:
https://wiki.php.net/rfc/time_instant_class
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
Hey Tim, Hey Derick
Hi
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:
https://wiki.php.net/rfc/time_instant_class
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 |
+---------------------------------------------------------------------+
Hi
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 theCLockInterfacenow
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.
Best regards
Tim Düsterhus
Hey All, Hey Tim.
Thanks for the feedback!
The other point I find irritating is the naming of the
Clockinterface. While I can understand the reasoning behind it I see irritation in the community upcoming as theCLockInterfacenow 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
Interfacesuffix 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 |
+---------------------------------------------------------------------+
Hi
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:https://wiki.php.net/rfc/time_instant_class
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
represents wall-clock time, which can change following clock
corrections, while
Instant
represents monotonic time, suitable for measuring elapsed time,
benchmarking, etc.
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
distinction I use in Whim's
Instant
and SystemTime
types for reference.
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()
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:
FrozenClocktakes a time value and always returns that value. (
potentionally withadvance(Duration $duration): voidand
travelTo(SystemTime $moment): void)OffsetClocktakes 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.
Best regards,
Seifeddine
Hi
Replying with my personal opinion, not as the RFC author (since I didn't
discuss this reply with Derick).
Could we name the proposed class
Time\SystemTimeand reserve
Time\Instantfor a future high-resolution monotonic clock API? Rust
uses this distinction, and I find it helpful:
SystemTime
represents wall-clock time, which can change following clock
corrections, while
Instant
represents monotonic time, suitable for measuring elapsed time,
benchmarking, etc.
The proposed naming is consistent with JavaScript’s Temporal
(https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant)
and Java’s java.time
(https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html),
which are the primary sources of reference we used.
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
eventuallyInstant::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
nowstatic method, I think adding
elapsed()
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
FrozenClockandOffsetClock.
The RFC notes that specialised clocks can have different behaviours,
but these two seem useful and sufficiently well-defined:
FrozenClocktakes a time value and always returns that value. (
potentionally withadvance(Duration $duration): voidand
travelTo(SystemTime $moment): void)OffsetClocktakes 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.
Best regards
Tim Düsterhus
Hi Tim,
Thanks for the response.
The proposed naming is consistent with JavaScript’s Temporal
(https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant)
and Java’s java.time
(https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html),
which are the primary sources of reference we used.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
theInstantclass, though, because that would intimately tie it to the
SystemClock, which feels wrong. The correct approach would probably be
aTime\now(): Instantfunction.
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
nowstatic method, I think adding
elapsed()
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:
$startedAt = Instant::now();
$result = evaluate_expression($expr);
fwrite(STDERR, 'Took ' . $startedAt->elapsed());
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.
FrozenClocktakes a time value and always returns that value. (
potentionally withadvance(Duration $duration): voidand
travelTo(SystemTime $moment): void)OffsetClocktakes 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.
Best regards,
Seifeddine
Hi
I agree that wall-clock time will be used more often, but I’m not
convinced thatInstantis 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:$startedAt = Instant::now(); $result = evaluate_expression($expr); fwrite(STDERR, 'Took ' . $startedAt->elapsed());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:
$watch = Stopwatch::start();
// …
echo "Took ", $watch->elapsed();
In fact using Stopwatch as the name might allow for a nice API where a
method could store “named round timers” as an array. Something like:
$start = Stopwatch::start();
prepare();
$start->snapshot('preparation');
execute();
$start->snapshot('execution');
var_dump($start->snapshots);
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
FrozenClockandOffsetClockto 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.
Best regards
Tim Düsterhus
I agree that wall-clock time will be used more often, but I’m not
convinced thatInstantis 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 Instantrepresents 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 itSystemTime` wouldn't make any sense.
BR,
Juris
Hi
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:https://wiki.php.net/rfc/time_instant_class
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.)
--Larry Garfield
Hi
- 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.
Best regards
Tim Düsterhus
Hi Tim,
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
Hi
- 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 thetoIso8601DateTimeString()getter. All the getters and constructors are valid options for serialization and each constructor is paired with a getter. Even justserialize($instant)can be a valid option. As already noted in the RFC, the ISO-8601 format (and of courseserialize()) 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.
Best regards
Tim Düsterhus
Hi
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).
Best regards
Tim Düsterhus
- 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.
--Larry Garfield
Hi
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.
Best regards
Tim Düsterhus
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
Hi
- 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 thetoIso8601DateTimeString()getter. All the getters and constructors are valid options for serialization and each constructor is paired with a getter. Even justserialize($instant)can be a valid option. As already noted in the RFC, the ISO-8601 format (and of courseserialize()) 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.
Best regards
Tim Düsterhus
Hi
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
https://news-web.php.net/php.internals/132612), 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.
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.
Best regards
Tim Düsterhus
Hi
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:
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). ADTI::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
https://news-web.php.net/php.internals/132600 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.5Zcould mean “exactly 0.5 seconds” or “0.5
seconds ± 0.05 seconds” whereas2026-09-29T16:42:45.500000000Zis
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 https://news-web.php.net/php.internals/132636.
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.
Best regards
Tim Düsterhus
Hi
- 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.5Zcould mean “exactly 0.5 seconds” or “0.5
seconds ± 0.05 seconds” whereas2026-09-29T16:42:45.500000000Zis
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.
Best regards
Tim Düsterhus
Hello Tim Düsterhus,
Tue, 22 Sep 2026 14:20:19 +0000
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:
And Tue, 29 Sep 2026 17:17:49 +0000
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.
=========
Prologue
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
https://www.php-fig.org/psr/psr-20/ . 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
https://github.com/php/php-src/issues/23543
To clarify, this does not work:
<?php
class MyTime implements DateTimeInterface
{
}
// Fatal error: DateTimeInterface can't be implemented by user classes
?>
===============
End of comments
Kind regards,
Mirco Babin
=========
Comment 2The 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 thetoIso8601DateTimeString()function which
always returns the 'Z' timezone identifier seems to imply an implicit
UTC timezone?
I think I can answer that one. It's because for display purposes some
consistent way of labelling Instants is required so that they can be
displayed. Using an ISO8601 string just makes that label human-readable;
the UTC timezone is not an intrinsic property of the Instant but of the
labelling (nor are the labels themselves, for that matter). Instants
would still be a thing and people would be able to say that two events
occurred "at the same time" even if the International Meridian
Conference had never happened.
(I mentioned earlier during the Duration discussion that time arithmetic
is a one-dimensional real affine space. Such a space can be formalised
as a set of points and a space of free vectors (and a way to add points
and vectors together). Here, Durations provide the vectors and Instants
provide the points.)
I think precision should somehow be an explicit part of \Time\Instant
class. How precise is the \Time\Instant?
Well, then it's not an instant, is it? It's a combination of an
instant ("now") and a duration ("and the subsequent sixty seconds").
Hi
I think I can answer that one. […]
Yes, thank you! That matches what I just sent, just using different
words.
I think precision should somehow be an explicit part of
\Time\Instant
class. How precise is the \Time\Instant?Well, then it's not an instant, is it? It's a combination of an
instant ("now") and a duration ("and the subsequent sixty seconds").
Indeed. That's the uncertainty information that I mentioned in
https://news-web.php.net/php.internals/132698. As you say, it can be
representing by an Instant + Duration indicating the amount of
uncertainty.
Best regards
Tim Düsterhus
Hi
=========
PrologueWell, I do have 8 comments on the Request For Comments (RFC).
This is a lengthy post, be prepared for a long reading time.
Thank you! I'm fully prepared for lengthy emails and am happy to answer
any clarifying questions to make sure we're all on the same page with
regard to what is being proposed. Your email is also nicely structured,
which makes it easy to work through it.
=========
Comment 1Is \Time\Instant meant to be immutable? If so, please state that in
the RFC.
Yes. This was already implied since the stub specifies that it's a
readonly class, but I've just adjusted the RFC text to explicitly
include the word “immutable”.
=========
Comment 2The 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 thetoIso8601DateTimeString()function which
always returns the 'Z' timezone identifier seems to imply an implicit
UTC timezone?
Timezones are a concept to translate a point in time (“Instant”) into
date and time as shown on a clock. Both 2026-09-30T20:13:30+02:00 and
2026-09-30T18:13:30+00:00 refer to the same point in time. The point in
time itself does not have a timezone. The ISO-8601 getter chooses to
represent the point in time with the UTC / Zulu timezone, because
specifying an offset is required for the “date and time” representation
to be unambiguous. It would be equally valid for it to select a random
offset each time you use it. Using UTC / a zero offset is just a
pragmatic choice for best interoperability.
=========
Comment 3Why 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)
UTC, TAI and the others are just different projections of a “point on
the timeline of the universe” and the Instant class is largely
agnostic to them. It differs from a perfect model of the timeline of the
universe in that leap seconds are ignored (see my reply to Andreas
https://news-web.php.net/php.internals/132600), since that is what the
computing industry converged on. Nevertheless, this brings us back to
comment (2): The differences between UTC, TAI and the others concern the
clock time. The point on the timeline of the universe remains the
same.
And of course Time\Instant is just the ergonomic name, matching the
precedent set in Java (java.time.Instant) and JavaScript
(Temporal.Instant).
=========
Comment 4The \Time\Clock interface conflicts with PSR-20 Clock interface, see
https://www.php-fig.org/psr/psr-20/ . A class can never implement both
interfaces at the same time. This will hinder adoption.
The RFC acknowledges that. Writing an adapter is easy, particularly
since the addition of the DateTimeImmutable::toInstant() method to the
proposal.
Consider renaming the
functionnow(): \Time\Instantfunction to
function nowAsInstant(): \Time\Instant.
now() is the obvious name for the method. Using something else would
mean that the proposed API would still suffer from a decision made for
compatibility after everyone has migrated to it and PSR-20 is long
forgotten. The horizon we are planning with is at least the next 15
years, but we're hoping that the API holds up for even longer.
For the same reason the compatibility layer with DateTimeImmutable is
explicitly added to the old API so that it is possible to deprecate
and remove the old API without affecting the new one.
=========
Comment 5The 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.
Conceptually there is only one SystemClock and internally each
SystemClock object accesses the same clock source provided by the
operating system. But you are making a good point here: Replacing the
constructor with a static ::get() method or similar would make that
explicit and would also allow for reducing memory usage when
SystemClock objects are obtained at different places in the code
(instead of being pulled from a dependency injection container). I'll
put this on my list to discuss with Derick (and am happy to receive
naming suggestions for such a static getter).
The RFC also states a FrozenClock for testing purposes. Well this
The FrozenClock is not part of the RFC. It is provided in the
non-normative part of the RFC as an example to showcase how the Clock
interface can be useful for testing purposes. Whether or not a
FrozenClock will be added to PHP itself is to be decided as part of a
future RFC (by different authors, as indicated in my email from
yesterday).
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 thevendordirectory. Because not
every library in thevendordirectory uses SystemClock. They all
usetime(), because that is currently the standard way to obtain
the System Clock. Time freezing must involve thetime()function!
It is debatable whether time() is the standard way to access the
system clock. It lacks support for fractional seconds and has terrible
ergonomics due to returning a bare integer. It is certainly still in
common use, but I'd argue the current best practice is a
DateTimeImmutable object, ideally one you obtained from a PSR-20
clock.
I think
SystemClockshould be removed from this RFC, it deserves
a seperate RFC. That RFC should also explore a frozentime()
function. Meaning the output oftime()can be frozen recursively.
Because of this probably the \Time\Clock interface should also
be moved to that new RFC.
The point of the new date and time API is to completely supersede the
existing date and time API with a proper API design matching best
practices. Superseding the existing API includes the time() function -
which I would say is already a legacy API as of today, since
DateTimeImmutable does everything time() does and it does it better
(except maybe for brevity).
=========
Comment 6Why does \Time\Instant not have a named constructor
public function fromSystemClock(): \Time\Instant?Why all the complexity of constructing a \Time\SystemClock and
callingnow()? The System Clock is a Singleton and is always
available, as thetime()function shows.
See https://news-web.php.net/php.internals/132594. “Injecting” a clock
would be the correct solution. If SystemClock was made a singleton
with a static getter as mentioned above (while still implementing the
interface), this would allow for:
SystemClock::get()->now()
for simple use cases and one-off scripts, which would be even shorter
than your proposed:
Instant::fromSystemClock()
=========
Comment 7The 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?
The Instant represents a point in time as precisely as possible. If
for some reason you need less precision, you can adjust it using the
->add() and ->sub() methods as desired. Please also see
https://news-web.php.net/php.internals/132621 for a possible future
scope ->truncateTo() method, but …
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.
… using an Instant to represent a public transportation schedule is the
wrong abstraction: Public transportation works on “date and time” as
shown on a calendar and a clock and only becomes meaningful once you
involve timezones. This is particularly important when a DST changeover
happens or your jurisdiction changes timezone rules: Now the point on
the timeline of the universe where your bus departs is different, but
the time shown on your wristwatch has remained the same.
=========
Comment 8The 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
https://github.com/php/php-src/issues/23543To clarify, this does not work:
<?php class MyTime implements DateTimeInterface { } // Fatal error: DateTimeInterface can't be implemented by user classes ?>
That is an accurate observation, but not relevant to the quoted part. I
think there might be a misunderstanding: What the quote is saying is
that if there is a function foo(DateTimeInterface $when) it should be
adjusted to foo(DateTimeInterface|Instant $when) so that it is
possible to use it with the new API without needing to first convert the
Instant into a DateTimeImmutable.
Best regards
Tim Düsterhus
Hello Tim Düsterhus,
Reply to Wed, 30 Sep 2026 19:48:51 +0000
=========
Comment 2The RFC states:
An Time\Instant carries no timezone. It represents a unique point
on the timeline of the universe.
Timezones are a concept to translate a point in time (“Instant”) into
date and time as shown on a clock. Both 2026-09-30T20:13:30+02:00 and
2026-09-30T18:13:30+00:00 refer to the same point in time. The point in
time itself does not have a timezone. The ISO-8601 getter chooses to
represent the point in time with the UTC / Zulu timezone, because
specifying an offset is required for the “date and time” representation
to be unambiguous. It would be equally valid for it to select a random
offset each time you use it. Using UTC / a zero offset is just a
pragmatic choice for best interoperability.
Is this unique point on the timelime of the universe concept somewhere
defined? For example in an IETF-RFC, academic paper or somewhere else?
Because it seems to me that timeline of the universe must have some
definition somewhere? The RFC does not have any reference to a formal
definition?
=========
Comment 4The \Time\Clock interface conflicts with PSR-20 Clock interface, see
https://www.php-fig.org/psr/psr-20/ . A class can never implement both
interfaces at the same time. This will hinder adoption.
The RFC acknowledges that. Writing an adapter is easy, particularly
since the addition of theDateTimeImmutable::toInstant()method to the
proposal.
I have demonstrated it is impossible to write an adapter. Given the
constraint that only the PSR-20 MyClock class can be changed, call-sites
must not be adjusted. Also the purpose of a PSR-20 Clock is to never
call new DateTimeImmutable() or time() anymore, so the assumption there
are new DateTimeImmutable() calls that can be adapted is incorrect.
Consider renaming the
functionnow(): \Time\Instantfunction to
function nowAsInstant(): \Time\Instant.
now()is the obvious name for the method. Using something else would
mean that the proposed API would still suffer from a decision made for
compatibility after everyone has migrated to it and PSR-20 is long
forgotten. The horizon we are planning with is at least the next 15
years, but we're hoping that the API holds up for even longer.
This is a strange point of view. Why would PSR-20 be forgotten? Is it
a goal to deprecate/forget PSR-20? And why is this not mentioned in
the RFC? And what would the duration be of the forget timeline?
time() is also an obvious name for the method. And it would not
conflict with PSR-20.
SystemClock::get()->time();
=========
Comment 5The RFC introduces a SystemClock class:
The RFC also states a FrozenClock for testing purposes. Well this
The
FrozenClockis not part of the RFC. It is provided in the
non-normative part of the RFC as an example to showcase how theClock
interface can be useful for testing purposes. Whether or not a
FrozenClockwill be added to PHP itself is to be decided as part of a
future RFC (by different authors, as indicated in my email from
yesterday).
That is surprising. How can a SystemClock be designed without
exploring the freezing of time? If the SystemClock is wrongly
designed in this RFC, and hinders the unexplored, unknown FrozenTime,
"at least the next 15 years" there will be an misdesign.
I really think Time\Clock interface, SystemClock, FrozenTime,
DateTimeImmutable freezing and time() freezing must be one RFC,
that designs them very well. And don't forget the very popular Carbon
library in this picture. This design should not be split between
2 RFC's and different authors.
=========
Comment 6Why does \Time\Instant not have a named constructor
public static function fromSystemClock(): \Time\Instant?
If
SystemClockwas made a singleton
with a static getter as mentioned above (while still implementing the
interface), this would allow for:SystemClock::get()->now()for simple use cases and one-off scripts, which would be even shorter
than your proposed:Instant::fromSystemClock()
If it becomes SystemClock::get()->time() both variants have 26
characters to type. But IDE autocompletion has to parse 3 parts in
SystemClock::get()->time() and only 2 parts in
Instant::fromSystemClock().
If it is not too much trouble I would add them both, because it
increases Developer Experience.
Kind regards,
Mirco Babin
Hey Mirco
Hello Tim Düsterhus,
Reply to Wed, 30 Sep 2026 19:48:51 +0000
=========
Comment 2The RFC states:
An Time\Instant carries no timezone. It represents a unique point
on the timeline of the universe.Timezones are a concept to translate a point in time (“Instant”) into
date and time as shown on a clock. Both 2026-09-30T20:13:30+02:00 and
2026-09-30T18:13:30+00:00 refer to the same point in time. The point in
time itself does not have a timezone. The ISO-8601 getter chooses to
represent the point in time with the UTC / Zulu timezone, because
specifying an offset is required for the “date and time” representation
to be unambiguous. It would be equally valid for it to select a random
offset each time you use it. Using UTC / a zero offset is just a
pragmatic choice for best interoperability.Is this unique point on the timelime of the universe concept somewhere
defined? For example in an IETF-RFC, academic paper or somewhere else?
Because it seems to me that timeline of the universe must have some
definition somewhere? The RFC does not have any reference to a formal
definition?
I'd say that is defined as Barycentric Coordinated Time (TCB). But that is slightly off towards the Geocentric Coordinated Time (TCG) by about 490 ms per year. Geocentric Coordinated Time and Terrestrial Time (TT) again differ by a constant from one another. Though both are linear counts of SI-seconds.
But all that is obsoleted and irrelevant in the end as the Instant is based on the epoch which is based on UTC minus leap-seconds. At least as far as I understood Tim.
=========
Comment 4The \Time\Clock interface conflicts with PSR-20 Clock interface, see
https://www.php-fig.org/psr/psr-20/ . A class can never implement both
interfaces at the same time. This will hinder adoption.The RFC acknowledges that. Writing an adapter is easy, particularly
since the addition of theDateTimeImmutable::toInstant()method to the
proposal.I have demonstrated it is impossible to write an adapter. Given the
constraint that only the PSR-20 MyClock class can be changed, call-sites
must not be adjusted. Also the purpose of a PSR-20 Clock is to never
callnew DateTimeImmutable()ortime()anymore, so the assumption there
arenew DateTimeImmutable()calls that can be adapted is incorrect.
An adapter does never need to implement both interfaces. In essence an adapter only needs to implement "the other" interface. A ClockAdapter uses a ClockInterface to create an Instant whereas a ClockInterfaceAdapter uses a Clock to create a DateTime. 2 classes that seem rather straight forward.
Consider renaming the
functionnow(): \Time\Instantfunction to
function nowAsInstant(): \Time\Instant.
now()is the obvious name for the method. Using something else would
mean that the proposed API would still suffer from a decision made for
compatibility after everyone has migrated to it and PSR-20 is long
forgotten. The horizon we are planning with is at least the next 15
years, but we're hoping that the API holds up for even longer.This is a strange point of view. Why would PSR-20 be forgotten? Is it
a goal to deprecate/forget PSR-20? And why is this not mentioned in
the RFC? And what would the duration be of the forget timeline?
time()is also an obvious name for the method. And it would not
conflict with PSR-20.
I can only second Tim here. The correct and only sensible method name is now() as is for any other ClockInterface.
Time() has a very special meaning in PHP and people will be very confused to realise that time() does not return a timestamp.
We had a lot of these discussions when preparing PSR20 and now() IS the sensible name.
And it will also make very clear that it is not possible to have ONE clock that serves BOTH purposes. Where we come back again to the adapters that make it possible to translate between the two
SystemClock::get()->time();=========
Comment 5The RFC introduces a SystemClock class:
The RFC also states a FrozenClock for testing purposes. Well this
The
FrozenClockis not part of the RFC. It is provided in the
non-normative part of the RFC as an example to showcase how theClock
interface can be useful for testing purposes. Whether or not a
FrozenClockwill be added to PHP itself is to be decided as part of a
future RFC (by different authors, as indicated in my email from
yesterday).That is surprising. How can a SystemClock be designed without
exploring the freezing of time? If the SystemClock is wrongly
designed in this RFC, and hinders the unexplored, unknown FrozenTime,
"at least the next 15 years" there will be an misdesign.I really think Time\Clock interface, SystemClock, FrozenTime,
DateTimeImmutable freezing andtime()freezing must be one RFC,
that designs them very well. And don't forget the very popular Carbon
library in this picture. This design should not be split between
2 RFC's and different authors.
Freezing time is always subject to the circumstances. And creating a frozenClock is just a few lines of code so that for the sake of testing it makes indeed more sense to leave that to the implementor of the test to create a FrozenClock matching exactly their needs.
=========
Comment 6Why does \Time\Instant not have a named constructor
public static function fromSystemClock(): \Time\Instant?If
SystemClockwas made a singleton
with a static getter as mentioned above (while still implementing the
interface), this would allow for:SystemClock::get()->now()for simple use cases and one-off scripts, which would be even shorter
than your proposed:Instant::fromSystemClock()If it becomes
SystemClock::get()->time()both variants have 26
characters to type. But IDE autocompletion has to parse 3 parts in
SystemClock::get()->time()and only 2 parts in
Instant::fromSystemClock().If it is not too much trouble I would add them both, because it
increases Developer Experience.
When using SystemClock::get() or Instant::fromSystemClock() you can equally just call time() and be done.
The whole advantage of injecting an interface that can then be used with a "real" clock in production and a frozen clock for testing is then obsoleted.
If one requires these static dependencies in their code it is very easy to create the appropriate classes in Userland and be done.
IMO that is not something the language should provide out of the box.
My 0.02€
Cheers
Andreas
(PSR20 Workinggroup Member)
--
,,,
(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
thank you Andreas, that mostly matches what I would have answered
myself. I'll add my own nuance in a separate reply to Mirco directly.
But all that is obsoleted and irrelevant in the end as the Instant is
based on the epoch which is based on UTC minus leap-seconds. At least
as far as I understood Tim.
Yes, or “POSIX time”, which avoids the overloaded term UTC that could
also refer to the UTC timezone. Timezones reintroduce “date and time” as
a concept, which is not relevant to a point in time.
I have demonstrated it is impossible to write an adapter. Given the
constraint that only the PSR-20 MyClock class can be changed,
call-sites
must not be adjusted. Also the purpose of a PSR-20 Clock is to never
callnew DateTimeImmutable()ortime()anymore, so the assumption
there
arenew DateTimeImmutable()calls that can be adapted is incorrect.An adapter does never need to implement both interfaces. In essence an
adapter only needs to implement "the other" interface. A ClockAdapter
uses a ClockInterface to create an Instant whereas a
ClockInterfaceAdapter uses a Clock to create a DateTime. 2 classes that
seem rather straight forward.
Indeed. Here's an example implementation for both directions. The
NativeToPsr20 adapter truncates to microseconds and needs to “invent”
a timezone. The opinionated choices made there should be correct for the
vast majority of users:
final readonly class Psr20ToNative implements \Time\Clock
{
public function __construct(
private \Psr\Clock\ClockInterface $clock,
) {}
public function `now()`: \Time\Instant
{
return $this->clock->now()->toInstant();
}
}
final readonly class NativeToPsr20 implements
\Psr\Clock\ClockInterface
{
public function __construct(
private \Time\Clock $clock,
private \DateTimeZone $timezone = new \DateTimeZone('UTC'),
) {}
public function `now()`: \DateTimeImmutable
{
$now = $this->clock->now();
return
\DateTimeImmutable::createFromTimestamp($now->getUnixTimestamp())
->setMicrosecond(\intdiv($now->getNanoseconds(), 1_000))
->setTimezone($this->timezone);
}
}
Best regards
Tim Düsterhus
Hello Tim Düsterhus,
=========
Comment 2
... snipped ...
(Tim Düsterhus)
Timezones are a concept to translate a point in time (“Instant”) into
date and time as shown on a clock. Both 2026-09-30T20:13:30+02:00 and
2026-09-30T18:13:30+00:00 refer to the same point in time. The point in
time itself does not have a timezone. The ISO-8601 getter chooses to
represent the point in time with the UTC / Zulu timezone, because
specifying an offset is required for the “date and time” representation
to be unambiguous. It would be equally valid for it to select a random
offset each time you use it. Using UTC / a zero offset is just a
pragmatic choice for best interoperability.
(Mirco Babin)
Is this unique point on the timelime of the universe concept somewhere
defined? For example in an IETF-RFC, academic paper or somewhere else?
Because it seems to me that timeline of the universe must have some
definition somewhere? The RFC does not have any reference to a formal
definition?
(Andreas Heigl)
I'd say that is defined as Barycentric Coordinated Time (TCB). But
that is slightly off towards the Geocentric Coordinated Time (TCG)
by about 490 ms per year. Geocentric Coordinated Time and Terrestrial
Time (TT) again differ by a constant from one another. Though both are
linear counts of SI-seconds.But all that is obsoleted and irrelevant in the end as the Instant is
based on the epoch which is based on UTC minus leap-seconds. At least
as far as I understood Tim.
(Tim Düsterhus)
Yes, or “POSIX time”, which avoids the overloaded term UTC that could
also refer to the UTC timezone. Timezones reintroduce “date and time”
as a concept, which is not relevant to a point in time.
(Mirco Babin) Thank you to Andreas Heighl and to Tim Düsterhus for
this answer. This
clears a lot of my confusion.
The RFC introduction currently states:
This RFC proposes the addition of a Time\Instant class representing
an absolute point on the timeline of the universe together with a
Time\Clock interface and a Time\Clock\SystemClock implementation for
obtaining the current Time\Instant.
The term "timeline of the universe" made me believe it was something
completely different, a new time concept. It did not make me think of
the Unix-Epoch-int time concept.
Can an extra explanation be added in the introduction near the
aforementioned RFC paragraph? Something like:
A \Time\Instant can be viewed as a replacement for the Unix-Epoch-int.
With the difference that the base-date might not be 1970-01-01. And
with the difference that the \Time\Instant can have more precision in
the form of milliscondes, microseconds, nanoseconds. The base-date
and extra precision are an implementation detail and not described
further. This also means the full timerange of the \Time\Instant is at
least that of the Unix-Epoch-int, but can be extended in the future.
=========
Comment 4The \Time\Clock interface conflicts with PSR-20 Clock interface, see
https://www.php-fig.org/psr/psr-20/ . A
class can never implement both
interfaces at the same time. This will hinder adoption.
(Tim Düsterhus)
The RFC acknowledges that. Writing an adapter is easy, particularly
since the addition of the DateTimeImmutable::toInstant() method to
the proposal.
(Mirco Babin)
I have demonstrated it is impossible to write an adapter. Given the
constraint that only the PSR-20 MyClock class can be changed,
call-sites must not be adjusted. Also the purpose of a PSR-20 Clock
is to never call new DateTimeImmutable() ortime()anymore, so the
assumption there are new DateTimeImmutable() calls that can be adapted
is incorrect.
(Andreas Heigl)
An adapter does never need to implement both interfaces. In essence an
adapter only needs to implement "the other" interface. A ClockAdapter
uses a ClockInterface to create an Instant whereas a
ClockInterfaceAdapter uses a Clock to create a DateTime.
2 classes that seem rather straight forward.
(Tim Düsterhus)
Indeed. Here's an example implementation for both directions.
The NativeToPsr20 adapter truncates to microseconds and needs to
“invent” a timezone. The opinionated choices made there should be
correct for the vast majority of users:
<?php
final readonly class Psr20ToNative implements \Time\Clock
{
public function __construct(
private \Psr\Clock\ClockInterface $clock,
) {}
public function `now()`: \Time\Instant
{
return $this->clock->now()->toInstant();
}
}
?>
<?php
final readonly class NativeToPsr20 implements
\Psr\Clock\ClockInterface
{
public function __construct(
private \Time\Clock $clock,
private \DateTimeZone $timezone =
new \DateTimeZone('UTC'),
) {}
public function `now()`: \DateTimeImmutable
{
$now = $this->clock->now();
return \DateTimeImmutable
::createFromTimestamp($now->getUnixTimestamp())
->setMicrosecond(\intdiv($now->getNanoseconds(), 1_000))
->setTimezone($this->timezone);
}
}
?>
(Mirco Babin) These adapters are not what I meant. My point concerns
all applications that currently use the PSR-20-Clock. To clarify more,
consider an application using the PSR-20 clock with a theoretical
function like this:
<?php
//
// The original theoretical PSR-20-application
//
class MyApp_Clock implements \Psr\Clock\ClockInterface
{
public function `now()`: \DateTimeImmutable {
return new \DateTimeImmutable();
}
}
function AFunctionOfTheApplication()
{
// $clock might also come from a Dependency Injection container.
// Or be injected via parameters or via constructor injection.
// It doesn't really matter.
$clock = new MyApp_Clock();
// The $clock is injected into external libraries. These
// libraries are not under the control of the application.
// Also the libraries do not use the Dependency Injection
// container of the application. Otherwise the DI container
// would have beeen provided as parameter instead of the
// \Psr\Clock\ClockInterface.
$result1 = Library_Alfa_Do_Something($clock);
$result2 = Library_Beta_Do_Something($clock);
$result3 = Library_Gamma_Do_Something($clock);
$result4 = Library_Theta_Do_Something($clock);
}
?>
At some point in the future time, one of the 4 libraries switches
to \Time\SystemClock. Let's assume it is Beta that adapts. How is this
code then going to look?
<?php
//
// Modified theoretical PSR-20-application
//
function AFunctionOfTheApplication()
{
// $clock might also come from a Dependency Injection container.
// Or be injected via parameters or via constructor injection.
// It doesn't really matter.
$clock = new MyApp_Clock();
// The $clock is injected into external libraries. These
// libraries are not under the control of the application.
// Also the libraries do not use the Dependency Injection
// container of the application. Otherwise the DI container
// would have beeen provided as parameter instead of the
// \Psr\Clock\ClockInterface.
$result1 = Library_Alfa_Do_Something($clock);
// Or use the provided Psr20ToNative adapter.
$clockInstant = new \Time\SystemClock();
$result2 = Library_Beta_Do_Something($clockInstant);
$result3 = Library_Gamma_Do_Something($clock);
$result4 = Library_Theta_Do_Something($clock);
}
?>
I hope the pattern becomes clear. After the \Time\Clock fact
the PSR-20-application will be split in 2 clocks. Carefully selecting
the right clock for the right call. And the careful selection has to
take place on every update of any external library. Because the
external library might have switched to \Time\Clock at any moment.
This is a maintenance disaster.
The best solution I can think of, is to adjust one class, namely
the MyApp_Clock which the application controls. And make it a
hybrid clock, both a \Psr\Clock\ClockInterface and a \Time\Clock.
In that case there is an one-time adjustment in one class and all
else will work without any modification.
<?php
// hybrid clock
class MyApp_Clock implements \Psr\Clock\ClockInterface,
\Time\Clock
{
public function `now()`: \DateTimeImmutable {
return new \DateTimeImmutable();
}
public function nowAsInstant(): \Time\Instant {
return \Time\SystemClock::get()->nowAsInstant();
}
}
But because the \Time\Clock->now() conflicts, as I have
demonstrated, with the \Psr\Clock\ClockInterface->now(), this
one-time solution is not possible. Now assume this PSR-20-application
is a big one, thousands of classes, millions line of code. This will
become a PSR-20-application maintenance disaster.
So I ask once again, consider renaming \Time\Clock->now()
to something else. The 'now()' naming obsession is not worth this
interface conflict and maintenance disaster for PSR-20-applications.
This also means: Yes, I do consider all Psr's including PSR-20
to be part of Php, and that PSR-interfaces
can't be ignored nor conflicted.
Kind regards,
Mirco Babin
(Mirco Babin) These adapters are not what I meant. My point concerns
all applications that currently use the PSR-20-Clock. To clarify more,
consider an application using the PSR-20 clock with a theoretical
function like this:<?php // // The original theoretical PSR-20-application // class MyApp_Clock implements \Psr\Clock\ClockInterface { public function `now()`: \DateTimeImmutable { return new \DateTimeImmutable(); } } function AFunctionOfTheApplication() { // $clock might also come from a Dependency Injection container. // Or be injected via parameters or via constructor injection. // It doesn't really matter. $clock = new MyApp_Clock(); // The $clock is injected into external libraries. These // libraries are not under the control of the application. // Also the libraries do not use the Dependency Injection // container of the application. Otherwise the DI container // would have beeen provided as parameter instead of the // \Psr\Clock\ClockInterface. $result1 = Library_Alfa_Do_Something($clock); $result2 = Library_Beta_Do_Something($clock); $result3 = Library_Gamma_Do_Something($clock); $result4 = Library_Theta_Do_Something($clock); } ?>At some point in the future time, one of the 4 libraries switches
to \Time\SystemClock. Let's assume it is Beta that adapts. How is this
code then going to look?<?php // // Modified theoretical PSR-20-application // function AFunctionOfTheApplication() { // $clock might also come from a Dependency Injection container. // Or be injected via parameters or via constructor injection. // It doesn't really matter. $clock = new MyApp_Clock(); // The $clock is injected into external libraries. These // libraries are not under the control of the application. // Also the libraries do not use the Dependency Injection // container of the application. Otherwise the DI container // would have beeen provided as parameter instead of the // \Psr\Clock\ClockInterface. $result1 = Library_Alfa_Do_Something($clock); // Or use the provided Psr20ToNative adapter. $clockInstant = new \Time\SystemClock(); $result2 = Library_Beta_Do_Something($clockInstant); $result3 = Library_Gamma_Do_Something($clock); $result4 = Library_Theta_Do_Something($clock); } ?>I hope the pattern becomes clear. After the \Time\Clock fact
the PSR-20-application will be split in 2 clocks. Carefully selecting
the right clock for the right call.
You're setting up a pretty pathological case here. Many services using a clock, many 3rd party services using a clock, but NOT using a DI container that has even rudimentary autowiring support.
Such an application is already stupidly written. A decent DI container makes this problem go away. And if you want to only have one source of time truth, you can, as Tim showed, wrap one clock around another to translate the value object.
And the careful selection has to
take place on every update of any external library. Because the
external library might have switched to \Time\Clock at any moment.
This is a maintenance disaster.
This is a maintainer disaster. If they switch external dependencies that you're supposed to handle without a major version bump, you can blame the maintainer, not PHP. No different than if they switch from one HTTP client to another, etc.
This also means: Yes, I do consider all Psr's including PSR-20
to be part of Php, and that PSR-interfaces
can't be ignored nor conflicted.Kind regards,
Mirco Babin
As a long-time member of the PHP-FIG Core Committee, I disagree. The existing ecosystem should not be ignored, but nor should we be shackled by it. I've repeatedly said that any new http-message-object-in-core effort should NOT be bound by PSR-7, nor need it follow PSR-7; it should be good enough to replace PSR-7.
This proposal seems like a good step to replacing PSR-20 with something even more standardized and universally available. That seems like a win in my book.
--Larry Garfield
Hi
Can an extra explanation be added in the introduction near the
aforementioned RFC paragraph? Something like:A \Time\Instant can be viewed as a replacement for the Unix-Epoch-int.
With the difference that the base-date might not be 1970-01-01. And
with the difference that the \Time\Instant can have more precision in
the form of milliscondes, microseconds, nanoseconds. The base-date
and extra precision are an implementation detail and not described
further. This also means the full timerange of the \Time\Instant is at
least that of the Unix-Epoch-int, but can be extended in the future.
I'll put adding a clarification on my list for the next RFC update.
[…]
At some point in the future time, one of the 4 libraries switches
to \Time\SystemClock. Let's assume it is Beta that adapts. How is this
code then going to look?<?php // // Modified theoretical PSR-20-application // function AFunctionOfTheApplication() { // $clock might also come from a Dependency Injection container. // Or be injected via parameters or via constructor injection. // It doesn't really matter. $clock = new MyApp_Clock(); // The $clock is injected into external libraries. These // libraries are not under the control of the application. // Also the libraries do not use the Dependency Injection // container of the application. Otherwise the DI container // would have beeen provided as parameter instead of the // \Psr\Clock\ClockInterface. $result1 = Library_Alfa_Do_Something($clock); // Or use the provided Psr20ToNative adapter. $clockInstant = new \Time\SystemClock(); $result2 = Library_Beta_Do_Something($clockInstant); $result3 = Library_Gamma_Do_Something($clock); $result4 = Library_Theta_Do_Something($clock); } ?>I hope the pattern becomes clear. After the \Time\Clock fact
the PSR-20-application will be split in 2 clocks. Carefully selecting
the right clock for the right call. And the careful selection has to
take place on every update of any external library. Because the
external library might have switched to \Time\Clock at any moment.
This is a maintenance disaster.
The expectation is that you would use one of the two adapters to
translate between the PSR-20 clock and PHP’s native clock, depending on
which one is the source of truth in your application. The resulting code
would look like this:
function AFunctionOfTheApplication()
{
$clock = new MyApp_Clock();
// The PSR-20 clock is the canonical one for this application,
// create the native adapter.
$nativeClock = new Psr20ToNative($clock);
$result1 = Library_Alfa_Do_Something($clock);
$result2 = Library_Beta_Do_Something($nativeClock);
$result3 = Library_Gamma_Do_Something($clock);
$result4 = Library_Theta_Do_Something($clock);
}
This is one extra line to initialize the $nativeClock adapter and one
changed line to pass the native clock to Beta instead of the PSR-20
clock.
But because the \Time\Clock->now() conflicts, as I have
demonstrated, with the \Psr\Clock\ClockInterface->now(), this
one-time solution is not possible. Now assume this PSR-20-application
is a big one, thousands of classes, millions line of code. This will
become a PSR-20-application maintenance disaster.
As Larry noted, changing from PSR-20 to the new date and time API would
be a breaking change. This kind of migration would also not be limited
to just the change in clocks. The Time\Instant, and the modeling of
the new date and time API in general are not a drop-in replacement for
the existing DateTimeImmutable API. A library that migrates from
PSR-20 to the native clock will want to consistently use the date and
time API to benefit from it. This will affect internal calculations and
possibly returned values (e.g. returning an Instant where previously a
DateTimeImmutable was returned). Just replacing the clock and keeping
everything else the same brings little benefit. Changing the clock you
pass in is the smallest part of the migration effort.
Best regards
Tim Düsterhus
=========
Comment 4The \Time\Clock interface conflicts with PSR-20 Clock interface, see
https://www.php-fig.org/psr/psr-20/ . A
class can never implement both
interfaces at the same time. This will hinder adoption.
... snipped ...
(Tim Düsterhus)
The expectation is that you would use one of the two adapters to
translate between the PSR-20 clock and PHP’s native clock, depending
on which one is the source of truth in your application.
(Mirco Babin) There is a misconception. The application does not
choose the abstracted clock truth for the Composer installed
libraries. It only chooses the clock for its internal parts. Each
piece of software, internal application and each different external
library, chooses its own source of abstracted clock truth, like:
- \time()
- new DateTimeImmutable()
- PSR-20 \Psr\Clock\ClockInterface->now()
- PSR-11 ContainerInterface->get(\Psr\Clock\ClockInterface::class)
- laravel/framework exposes a \now() function.
- MySql 'SELECT SYSDATE();'
- MySql 'SELECT
NOW();' (can be frozen with SET TIMESTAMP). - \Carbon\CarbonImmutable::now()
- \Time\SystemClock::get()->now()
- \Time\Clock->now()
- PSR-11 ContainerInterface->get(\Time\Clock::class)
etcetera.
But in the end, every "abstracted clock truth" uses one singleton, one
source of truth, namely the singleton system clock.
(Mirco Babin)
But because the \Time\Clock->now() conflicts, as I have demonstrated,
with the \Psr\Clock\ClockInterface->now(), this one-time solution is
not possible. Now assume this PSR-20-application is a big one,
thousands of classes, millions line of code. This will become a
PSR-20-application maintenance disaster.
(Tim Düsterhus)
As Larry noted, changing from PSR-20 to the new date and time API
would be a breaking change. This kind of migration would also not
be limited to just the change in clocks. The Time\Instant, and the
modeling of the new date and time API in general are not a drop-in
replacement for the existing DateTimeImmutable API. A library that
migrates from PSR-20 to the native clock will want to consistently
use the date and time API to benefit from it. This will affect
internal calculations and possibly returned values (e.g. returning
an Instant where previously a DateTimeImmutable was returned). Just
replacing the clock and keeping everything else the same brings
little benefit. Changing the clock you pass in is the smallest part
of the migration effort.
(Mirco Babin) Every effort, however small, however little, that eases
this migration path for applications, should be provided.
Also because Packagist https://packagist.org/packages/psr/clock shows
439 458 545 (439 million) installations of the PSR-20 clock. This is
an impure number, because it also includes CI/test installations,
and it also includes nesbot/carbon and laravel/framework
installations.
I agree to disagree.
Can the RFC be updated? Under rejected features can something be added
like:
A hybrid clock, both implementing \Time\Clock from this RFC and the
PSR-20 \Psr\Clock\ClockInterface is rejected. Because ...(reasons)...
===============
End of comments
I have no further comments.
Thank you Tim Düsterhus for considering my comments.
And good luck with the RFC.
Kind regards,
Mirco Babin
Hi
(Mirco Babin) There is a misconception. The application does not
choose the abstracted clock truth for the Composer installed
libraries. It only chooses the clock for its internal parts. Each
piece of software, internal application and each different external
library, chooses its own source of abstracted clock truth, like:
Of course. If the library provides no way to inject a clock, then you
can't inject a clock. What I meant to say is that if the libraries you
use allow injecting a clock (e.g. PSR-20 or the new Time\Clock, or
something else of course), then you can use the adapters to translate
between the different interfaces, while having a single “canonical”
clock as a source of truth.
If the library doesn't allow injecting a clock right now, then the
introduction of the new interface is no worse than the status quo.
(Mirco Babin) Every effort, however small, however little, that eases
this migration path for applications, should be provided.Also because Packagist https://packagist.org/packages/psr/clock shows
439 458 545 (439 million) installations of the PSR-20 clock. This is
an impure number, because it also includes CI/test installations,
and it also includes nesbot/carbon and laravel/framework
installations.I agree to disagree.
I'm happy to leave it at “agree to disagree”. I nevertheless thank you
for raising your concern here.
Can the RFC be updated? Under rejected features can something be added
like:A hybrid clock, both implementing \Time\Clock from this RFC and the
PSR-20 \Psr\Clock\ClockInterface is rejected. Because ...(reasons)...
I have added a short note to the rejected features section. For the full
reasoning readers can read the mailing list discussion (linked at the
top of the RFC). This also avoids accidentally misrepresenting the
nuance provided in the discussion due to a lack of context.
Best regards
Tim Düsterhus
Hi
=========
Comment 5The 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.Conceptually there is only one
SystemClockand internally each
SystemClockobject accesses the same clock source provided by the
operating system. But you are making a good point here: Replacing the
constructor with a static::get()method or similar would make that
explicit and would also allow for reducing memory usage when
SystemClockobjects are obtained at different places in the code
(instead of being pulled from a dependency injection container). I'll
put this on my list to discuss with Derick (and am happy to receive
naming suggestions for such a static getter).
We have now made the SystemClock a singleton with a
SystemClock::get() factory / getter. The constructor is private now.
Best regards
Tim Düsterhus
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:
Hi Tim,
Broadly in favor. The timezone-less design and keeping the compat layer
on the old API are the right calls. Three things I'd like pinned down
before a vote:
-
toIso8601DateTimeString() output width. Omitting the fraction only
when it is zero makes the output format depend on the value. A consumer
that can't parse fractions passes tests with whole-second fixtures, then
fails on almost every SystemClock value in production, so the interop
benefit doesn't hold. It also breaks string ordering:
"2026-09-29T16:42:45Z" sorts after "2026-09-29T16:42:45.500000000Z", and
people do store these in VARCHAR columns and ORDER BY them. Always
emitting 9 digits gives fixed width, chronological string order for
years 0000 to 9999, and the explicit precision you were after. -
The accepted grammar of fromIso8601DateTimeString(). "May not support
all legal formats, extendable by a PR" means the accepted set gets
decided in code review rather than by the vote. I'd like the RFC to
guarantee RFC 3339 date-time (plus the expanded years the getter emits)
and state the outcome for: more than 9 fractional digits, comma decimal
sign, lowercase t/z, space separator, -00:00, 24:00:00 and 23:59:60. On
the last one, java.time parses 23:59:60 as 23:59:59 rather than
rejecting it, and RFC 3339 explicitly permits :60. Rejecting is
defensible, but it should be written down. -
json_encode(). With no public properties, json_encode($instant)
yields {}. Duration and DateTimeImmutable both encode to something
meaningful today, so a silent {} is a trap for any API response carrying
a timestamp. Either implement JsonSerializable returning the ISO-8601
string, or state that {} is intentional.
On the exceptions open issue: splitting TimeException into subclasses
later is BC-safe, since catch (TimeException) keeps working, so I don't
think it needs to block this RFC.
Ilia Alshanetsky
Technologist, CTO, Entrepreneur
E: ilia@ilia.ws
T: @iliaa
B: http://ilia.ws
Hi
- toIso8601DateTimeString() output width. Omitting the fraction only
when it is zero makes the output format depend on the value. A consumer
that can't parse fractions passes tests with whole-second fixtures,
then
fails on almost every SystemClock value in production, so the interop
benefit doesn't hold. It also breaks string ordering:
You are correct that SystemClock will emit fractional seconds in
virtually all cases. However from my experience with dealing with API
responses, they rarely contain sub-second information and fractional
seconds are accordingly omitted from the ISO-8601 string. When using
those as inputs, the “lack of fractional seconds” roundtrips and any
math with Durations that are integral multiples of a second will keep
that property.
"2026-09-29T16:42:45Z" sorts after "2026-09-29T16:42:45.500000000Z",
and
people do store these in VARCHAR columns and ORDER BY them. Always
emitting 9 digits gives fixed width, chronological string order for
years 0000 to 9999, and the explicit precision you were after.
As you say yourself, the VARCHAR ordering breaks down for years before
0000 and after 9999 since a mandatory sign will be added. While these
are well outside our lifetimes, I don’t think “always emitting
fractional seconds allows for naive string-based ordering” follows from
that, since it still breaks down for points in time that are explicitly
supported by the Instant class.
For proper ordering the Instant::compare() method is provided and
users should use the date/time types provided by their database (e.g.
timestamptz for PostgreSQL), which will also allow for in-database
time arithmetic and default values (e.g. provided by NOW()).
- The accepted grammar of fromIso8601DateTimeString(). "May not
support
all legal formats, extendable by a PR" means the accepted set gets
decided in code review rather than by the vote. I'd like the RFC to
guarantee RFC 3339 date-time (plus the expanded years the getter emits)
The RFC already specifies that:
It naturally will support all possible return values of
toIso8601DateTimeString().
And in practice the toIso8601DateTimeString() getter implements the
RFC 3339 subset of ISO-8601. That would leave non-Z offsets, which are
easy enough to include (and already supported by DateTimeImmutable, so
we can just borrow from there).
and state the outcome for: more than 9 fractional digits, comma decimal
sign, lowercase t/z, space separator, -00:00, 24:00:00 and 23:59:60. On
the last one, java.time parses 23:59:60 as 23:59:59 rather than
rejecting it, and RFC 3339 explicitly permits :60. Rejecting is
defensible, but it should be written down.
The RFC notes that the parser is strict: All invalid inputs will be
rejected (that includes the space separator). Other than that the inputs
that can be parsed losslessly fall under the “extendable by PR” rule. As
an example, if a comma decimal is not supported in the initial version,
it can be added later without introducing a breaking change. The input
is well-defined and representable by Instant. The same applies to
lowercase t/z or the “basic format” 20261001T194330+0200.
The behavior for lossy inputs I’ll put on my list to discuss with
Derick.
json_encode(). With no public properties, json_encode($instant)
yields {}. Duration and DateTimeImmutable both encode to something
meaningful today, so a silent {} is a trap for any API response
carrying
a timestamp. Either implement JsonSerializable returning the ISO-8601
string, or state that {} is intentional.
I’ll put that on my Derick list, but my personal answer is: As the RFC
notes there is no single representation that is correct in 100% of the
cases. Some JSON payloads might want an ISO-8601 string, some might want
a Unix timestamp with millisecond precision. Not implementing
JsonSerializable forces the user to make a choice - it is unfortunate
that PHP’s JSON serializer defaults to a useless object representation
rather than requiring an opt-in. For the same reason there is no
__toString(), but a toIso8601DateTimeString() (see also the design
of ext/uri).
On the exceptions open issue: splitting TimeException into subclasses
later is BC-safe, since catch (TimeException) keeps working, so I don't
think it needs to block this RFC.
Yes. However we’ve already deferred adding the hierarchy in the Duration
RFC, so I put it on the list for discussion. But it probably makes sense
to define the remaining classes before committing to the final
hierarchy.
Best regards
Tim Düsterhus
Hey Tim,
thanks for the RFC.
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:
Why would this not become an always bundled ext/time?
You called it 'greenfield design', and said 'deliberately don't want to
“infect” the new API with ideas from the legacy API'. Yet it lives next
to this old code. It was also mentioned that JavaScript's Temporal is
roughly where the journey goes, which is quite some scope yet to come.
Since this proposed new date/time API will be the successor of the old
date/time API, why not also physically & cleanly separate the new from
the old stuff? ext/standard is such a kitchen sink already that I
wonder why modern successors would not live in a more "sensible"
structure. Imagine a (far) future where we could chose to compile PHP
with or without legacy stuff -- clean separation would make this easier.
As far as my understanding of the policy goes, nothing would forbid
making this an extension (see ext/uri)?
Cheers
Nick
Hi
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:Why would this not become an always bundled
ext/time?
For the “always enabled” extensions it doesn't really matter where the
code is located and the (observable) end result for the user is the same
(except for ReflectionExtension). Time\Duration went into ext/date for
simplicity, since timelib is already properly wired into ext/date.
Since this proposed new date/time API will be the successor of the old
date/time API, why not also physically & cleanly separate the new from
the old stuff?ext/standardis such a kitchen sink already that I
wonder why modern successors would not live in a more "sensible"
structure. Imagine a (far) future where we could chose to compile PHP
with or without legacy stuff -- clean separation would make this
easier.
It's ext/date, not ext/standard where the API lives. For Time\Duration
we already split off the implementation into a separate and effectively
standalone php_time.h header with php_time.c and time_duration.c
source files, instead of the php_date.[ch] kitchen sink.
As far as my understanding of the policy goes, nothing would forbid
making this an extension (see ext/uri)?
That is accurate.
Best regards
Tim Düsterhus
Hey Tim,
Why would this not become an always bundled
ext/time?For the “always enabled” extensions it doesn't really matter where
the code is located and the (observable) end result for the user is
the same (except forReflectionExtension). Time\Duration went into
ext/date for simplicity, since timelib is already properly wired into
ext/date.
It matters how code is organised. Patterns, assumptions, cognitive load,
these, that. I think that's important. A "doesn't really matter where"
approach is what brings code where core is.
I would find it more natural to look at the namespace Time, and find
an ext/time. Me getting confused in my last mail shows it nicely. My
mail was in draft since you opened the discussion, today I found time to
answer and whoops mixed up the polling follow up commit with where
things actually live. :)
That is accurate.
If "doesn't really matter where" stands, then it cannot hurt to
separate it. So: why not doing it?
Cheers
Nick
Hi
For the “always enabled” extensions it doesn't really matter where
the code is located and the (observable) end result for the user is
the same (except forReflectionExtension). Time\Duration went into
ext/date for simplicity, since timelib is already properly wired into
ext/date.It matters how code is organised. Patterns, assumptions, cognitive
load, these, that. I think that's important. A "doesn't really matter
where" approach is what brings code where core is.I would find it more natural to look at the namespace
Time, and find
anext/time. Me getting confused in my last mail shows it nicely. My
mail was in draft since you opened the discussion, today I found time
to answer and whoops mixed up the polling follow up commit with where
things actually live. :)
For the vast majority of PHP developers the exact extension where the
code lives is not relevant - they likely don't even know that PHP’s
always-enabled standard library is split into multiple extensions, since
no action is required for it to be available. Accordingly the RFC also
leaves this unspecified as an implementation detail.
If "doesn't really matter where" stands, then it cannot hurt to
separate it. So: why not doing it?
ext/date was the pragmatic choice to get things into PHP 8.6. Splitting
it out would likely also require a separate ext/timelib extension
(similar to ext/lexbor) that provides the underlying library for
internal use. Otherwise we would just move some files - that are already
entirely standalone - into a separate directory, but not actually do
anything meaningful to separate ext/time from ext/date.
Best regards
Tim Düsterhus
Hi
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:https://wiki.php.net/rfc/time_instant_class
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,
I'm just now getting around to reading over the RFC, and I've read most messages but not all of them. So, I apologize if I'm reopening any old discussions here.
After spending much of the last 4-5 years working on a database that needs to deal with relativity, I'd recommend dropping the physics framing in the introduction. Time doesn't move at the same speed for everyone, even on Earth, and you don't specify a frame of reference for the instant. It would also make it easier to explain until as "computes the difference between two represented time values. It does not establish how much physical time elapsed between the events that produced those values."
This also raises a question about uncertainty. I appreciate that the RFC distinguishes the nanosecond precision of the representation from the resolution of the underlying clock. However, Clock::now() returns only an Instant, with no way through that contract to convey the source resolution or any available uncertainty information. I've had servers lose access to their hardware clock due to hardware malfunctions, so this is not just a theoretical concern. Where that information is available, it should be preserved alongside the reading. Where it is unknown, it should remain explicitly unknown. Printing nine fractional digits cannot supply that information.
That being said, other than the introduction throwing me into a "things programmers wrongly believe about time" rage, this is really well written.
I think there are some things worth addressing in the current RFC:
Instantshould throw when serialized to JSON. I understand the argument is that applications should choose their own representation, but the choice should be enforced rather than silently doing the wrong thing. Or provide an opaque serialization that can restore theInstant.DateTimeInterfaceis missing thetoInstant()method, is that by design?- The backward-compatibility section still says this RFC only introduces three new namespaced symbols. It should also cover the new methods on
DateTimeandDateTimeImmutable, including potential conflicts with existing subclass methods namedtoInstant().
— Rob
Hi
I'm just now getting around to reading over the RFC, and I've read most
messages but not all of them. So, I apologize if I'm reopening any old
discussions here.
The only duplication was the JSON serialization that had also been
mentioned by Ilia (who I'm Ccing here).
After spending much of the last 4-5 years working on a database that
needs to deal with relativity, I'd recommend dropping the physics
framing in the introduction. Time doesn't move at the same speed for
everyone, even on Earth, and you don't specify a frame of reference for
the instant. It would also make it easier to explainuntilas
"computes the difference between two represented time values. It does
not establish how much physical time elapsed between the events that
produced those values."
Thanks for the physics discussion in Discord. To loop in the list: Rob
is correct here, I'll check what I can do to satisfy the physics experts
without leading other folks down the “I need a timezone” track.
This also raises a question about uncertainty. I appreciate that the
RFC distinguishes the nanosecond precision of the representation from
the resolution of the underlying clock. However,Clock::now()returns
only anInstant, with no way through that contract to convey the
source resolution or any available uncertainty information. I've had
servers lose access to their hardware clock due to hardware
malfunctions, so this is not just a theoretical concern. Where that
information is available, it should be preserved alongside the reading.
Where it is unknown, it should remain explicitly unknown. Printing nine
fractional digits cannot supply that information.
Uncertainty is likely not of interest to the average user or something
they understand: They just trust that their operating system provides
them with the correct time. For applications where this matters adding a
custom extension with a “ClockWithUncertainty” clock that returns the
Instant + a Duration would be the right choice. The current design is in
line with what virtually all the other programming languages do for
their standard library. Marc also suggested exposing clock_getres() on
the SystemClock, I'll reply there. In any case, I don't think this is
something that belongs on the Clock interface, since it increases the
API complexity for little benefit.
I think there are some things worth addressing in the current RFC:
Instantshould throw when serialized to JSON. I understand the
argument is that applications should choose their own representation,
but the choice should be enforced rather than silently doing the wrong
thing. Or provide an opaque serialization that can restore the
Instant.
I discussed this with Derick and it's our position that:
- As you mentioned and as I said in
https://news-web.php.net/php.internals/132764 that there is no single
canonical representation for JSON and that users need to make a choice
themselves. - That implementing
JsonSerializablejust to always throw an
exception would be misleading, because from the API surface it very much
looks like JSON serialization would be supported.
That json_encode() includes a useless default serialization for
arbitrary objects instead of requiring an opt-in is something that
should be fixed in ext/json and not worked around for every (value)
object we implement. Since there is no “json decode to object”, an
opaque serialization that can restore the Instant is not really a
thing. If users need to manually call methods, they can just go via
ISO-8601.
DateTimeInterfaceis missing thetoInstant()method, is that by
design?
No. I just forgot that the interface exists. Now added.
- The backward-compatibility section still says this RFC only
introduces three new namespaced symbols. It should also cover the new
methods onDateTimeandDateTimeImmutable, including potential
conflicts with existing subclass methods namedtoInstant().
Did some preliminary adjustments there. Might tune the phrasing later.
Best regards
Tim Düsterhus
On 22.9.2026 16:20 CEST Tim Düsterhus tim@bastelstu.be wrote:
Hi
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:
Hi Tim, Derick,
thanks for the RFC and the work you have put into this.
I like the direction.
I think the following points still need some discussion. Some of
them may overlap with things others have already raised in this thread. Rather than replying to each of those messages separately, I've collected
everything in this one mail and mention who brought a point up where it
applies.
- Naming of Clock::now()
The method returns an Instant, but the RFC already plans future classes that combine an instant with a timezone (a ZonedDateTime-like class). Callers
will want to get those from a clock directly as well, without going the long way around: $clock->now()->toZonedDateTime($zone). Once a clock can return more than one type, now() no longer says what you get.
Naming the method after the process of reading a clock and after what is
read, e.g. $clock->takeInstant(), which leaves room for $clock->takeZonedDateTime($zone) later. java.time.Clock and Temporal.Now
also name their methods after the result type. As a side effect, this also
resolves the conflict with PSR-20's ClockInterface::now() that Mirco raised, a single class could implement both interfaces.
One constraint thought, PHP interfaces can't have default implementations. Adding methods to Time\Clock later would break every userland clock. So either the interface should get its full shape now, or further result types should come in through factories on the target types
(see 2).
- Instant::fromClock(Clock $clock)
You rejected Instant::now() because it would tie Instant to the SystemClock. With a required Clock parameter that objection goes away. It is exactly what Java does. Later classes can then follow the same pattern
(ZonedDateTime::fromClock($clock, $zone)) without the Clock interface ever
growing.
- Clock resolution
The docblock says "at the resolution offered by the underlying clock", but
nothing in the API exposes that resolution (Rob raised the same point). I
measured what the platforms report via clock_getres() and what they actually deliver:
clock_getres() smallest observed step
macOS arm64, REALTIME 1 us 1 us
Linux arm64, REALTIME 1 ns 42 ns (24 MHz counter)
Linux, REALTIME_COARSE 1 ms -
So clock_getres() is honest on some systems and only nominal on others. I'd still like SystemClock to expose it, documented as the nominal resolution of the source. That is better than no information at all.
- Configurable clock resolution
I'd like a way to configure the resolution,
e.g. a SystemClock that only ever returns whole seconds. Many consumers (JWT iat/exp, HTTP dates, database columns) never want fractions.
A clock configured that way guarantees whole seconds to every consumer it is injected into, instead of each consumer having to truncate on its own. It also makes test expectations simpler, because the values never carry
fractions.
- Taking a Unix timestamp directly from a clock
Unix timestamps are used everywhere, not only in PHP, so "current time as a Unix timestamp" is a very common operation. Today that's time(), which can't be mocked. With this RFC the mockable replacement is
$clock->now()->getUnixTimestamp(), which always goes through an Instant
object. I'd like a way to take the Unix timestamp directly from the clock.
- "Follows POSIX time" vs. "unspecified representation"
The RFC states that Instants "follow POSIX time". At the same time it says
that "the internal representation of Time\Instant is intentionally left
unspecified and there are deliberately no public properties. In particular
the 'Unix timestamp' is not the primary representation."
I think an unspecified internal representation is the right way forward. But it doesn't match the class being specified as POSIX time. POSIX time is not just one of many ways to look at an instant. It is a specific time
scale: seconds counted since 1970-01-01T00:00:00Z. If Instant follows POSIX time, then the Unix timestamp (plus a fraction of a second) is effectively the model of the class.
So I'd suggest not defining Instant itself in terms of POSIX time. POSIX
time is relevant where Unix timestamps come in or go out, so it belongs in
the documentation of the Unix timestamp constructor and getter, not in the
definition of the class.
- get vs to
The RFC explicitly states that the Unix timestamp is not the internal
representation. getUnixTimestamp() is therefore a conversion, and by the RFC's own convention (toIso8601DateTimeString(), DateTime::toInstant()) it should be toUnixTimestamp(). Duration exposes its components as properties, so with get*() we'd end up with a third style within the same family.
- getUnixTimestamp() / getUnixTimestampMilliseconds()
Only the milliseconds variant names its unit, and microseconds (used all
over PHP: microtime(), DateTime) and nanoseconds are missing entirely. A
single pair taking the unit as an argument would cover all of them and stay symmetric:
public static function fromUnixTimestamp(int $timestamp, TimeUnit $unit = TimeUnit::Second): self;
public function toUnixTimestamp(TimeUnit $unit = TimeUnit::Second): int;
TimeUnit would be an enum of Second, Millisecond, Microsecond and
Nanosecond, rounding towards negative infinity as already specified. The
current fromUnixTimestampSeconds($seconds, $nanoseconds) could remain alongside.
- getNanoseconds()
It isn't obvious whether this returns the nanoseconds since epoch or the
nanoseconds within the current second. getNanoOfSecond() (Java's
NANO_OF_SECOND) would remove that ambiguity.
- Concepts the ISO-8601 pair brings into Instant
An Instant is described as a timezone-less point in time. The ISO-8601
constructor and getter bring in several concepts that Instant otherwise
deliberately avoids:
a) parsing and formatting of date-time strings;
b) UTC as the reference ("Z" means a zero offset from UTC; since RFC 9557 it formally means "UTC known, local offset unknown");
c) a calendar: ISO 8601 uses the proleptic Gregorian calendar with year
0000;
d) an opinionated input and output format
- The accepted input grammar
The RFC says the parser is strict but "may not support all legal formats",
and that accepted formats can be extended "by a simple pull request". That
means the vote doesn't decide the accepted grammar. Widening it later is
also not BC-neutral for code that uses the parser as a validator. I'd like
the RFC to specify the grammar explicitly and to state the outcome for the
cases Ilia listed: comma decimal sign, lowercase t/z, space separator,
-00:00, 24:00:00, 23:59:60, more than 9 fractional digits.
A related point:
the docblock requires "a zone offset" while the prose requires "a timezone". Is an RFC 9557 suffix such as [Europe/Berlin] accepted, rejected or ignored?
- The output format
The getter always emits Z, has a fractional part whose width depends on the value (omitted when zero, otherwise 9 digits), and uses signed expanded years outside 0000-9999.
You mentioned that the getter implements the RFC 3339 subset of ISO-8601.
That is not quite true: RFC 3339 only allows 4-digit years 0000-9999, so the output isn't RFC 3339 for expanded years.
I also tested how well the possible outputs work as input for the existing
DateTime API (PHP 8.5):
- new DateTimeImmutable($s) accepts all of them, but truncates the 9
fractional digits to 6 without notice. - With createFromFormat() no single format string works.
DATE_ATOMonly
matches whole seconds with 4-digit years. DATE_RFC3339_EXTENDED (.v,
3 digits) never matches. 'Y-m-d\TH:i:s.uP' fails on 9 digits because
'u' takes at most 6, so you need a hack like 'Y-m-d\TH:i:s.u???P'.
Expanded years need 'X' instead of 'Y'.
Because the fractional part is only present for some values, any strict
consumer needs at least two format strings and has to choose between them,
which is the trap Ilia described. An output that always includes the
fractional part would at least make 'X-m-d\TH:i:s.u???P' work for every
value.
- A dedicated parser/formatter instead
I'd prefer parsing and formatting to live in a dedicated parser/formatter
API (future scope) rather than on Instant, where it would sit next to the
other ISO-8601 profiles and custom patterns. If the pair stays on Instant,
the name should say what it really is (a fixed RFC 3339-like profile), and
the grammar should be part of what we vote on.
- until() vs Duration::between()
Was Duration::between(Instant $a, Instant $b) considered? Calculating a
duration feels like Duration's responsibility to me.
- Smaller things
- On 32-bit, Duration only covers about 68 years, so until() is more
limited there than the examples suggest. That's worth a sentence. - Duration uses sign-magnitude for its (seconds, nanoseconds) pair, while fromUnixTimestamp(-1, 500_000_000) uses floor semantics (-0.5s). Both are defensible, but the RFC should say why they differ.
Best regards,
Marc
- get vs to
The RFC explicitly states that the Unix timestamp is not the internal
representation. getUnixTimestamp() is therefore a conversion, and by
the RFC's own convention (toIso8601DateTimeString(),
DateTime::toInstant()) it should be toUnixTimestamp(). Duration exposes
its components as properties, so with get*() we'd end up with a third
style within the same family.
I agree with this. $foo->toBar() is a very common "type conversion" pattern, and it makes sense to use here as well.
- getUnixTimestamp() / getUnixTimestampMilliseconds()
Only the milliseconds variant names its unit, and microseconds (used all
over PHP:microtime(), DateTime) and nanoseconds are missing entirely. A
single pair taking the unit as an argument would cover all of them and
stay symmetric:public static function fromUnixTimestamp(int $timestamp, TimeUnit
$unit = TimeUnit::Second): self;
public function toUnixTimestamp(TimeUnit $unit = TimeUnit::Second):
int;TimeUnit would be an enum of Second, Millisecond, Microsecond and
Nanosecond, rounding towards negative infinity as already specified. The
current fromUnixTimestampSeconds($seconds, $nanoseconds) could remain alongside.
I would agree here as well.
- A dedicated parser/formatter instead
I'd prefer parsing and formatting to live in a dedicated parser/formatter
API (future scope) rather than on Instant, where it would sit next to the
other ISO-8601 profiles and custom patterns. If the pair stays on Instant,
the name should say what it really is (a fixed RFC 3339-like profile), and
the grammar should be part of what we vote on.
I understand the tidiness/purity argument here, and I am generally sympathetic to it. However, that always needs to be balanced against usability. Any incoming date/time information to the PHP process is likely to be either an ISO-8601 string or a unix timestamp int. If we want people to convert those into Instants for doing manipulation and comparison on (which I presume is the case), then that needs to be ergonomically easy to do. Having to go through multiple parser/converter objects (which some purist is likely to insist be injected from a container rather than instantiated directly; I have been known to be that purist) to get there is not ergonomic. So if that logic is split off, we would need to be very careful to ensure the result is ergonomically simple and readily copy-pasteable.
- until() vs Duration::between()
Was Duration::between(Instant $a, Instant $b) considered? Calculating a
duration feels like Duration's responsibility to me.
This also seems reasonable, as a named constructor of Duration.
--Larry Garfield
Hi
I think the following points still need some discussion. Some of
them may overlap with things others have already raised in this thread.
Rather than replying to each of those messages separately, I've
collected
everything in this one mail and mention who brought a point up where it
applies.
Thank you for the review. Derick and I already discussed some, but not
all of the points. For now I'll just reply to those that are resolved to
get them out of the way.
- Configurable clock resolution
I'd like a way to configure the resolution,
e.g. a SystemClock that only ever returns whole seconds. Many consumers
(JWT iat/exp, HTTP dates, database columns) never want fractions.
A clock configured that way guarantees whole seconds to every consumer
it is injected into, instead of each consumer having to truncate on its
own. It also makes test expectations simpler, because the values never
carry
fractions.
We believe this is the job of a “Clock decorator”, e.g. CoarseClock
that takes in some clock and appropriately rounds or truncates the
Instant to the desired precision. Such a decorator would automatically
be compatible with any type of clock (e.g. a FrozenClock for testing).
As with the testing clocks, we consider a CoarseClock out of scope for
this RFC. With a possible future truncateTo() that I already mentioned
elsewhere in the discussion, a userland implementation would become
trivial for the use cases where a coarser clock is required.
- get vs to
The RFC explicitly states that the Unix timestamp is not the internal
representation. getUnixTimestamp() is therefore a conversion, and by
the RFC's own convention (toIso8601DateTimeString(),
DateTime::toInstant()) it should be toUnixTimestamp(). Duration exposes
its components as properties, so with get*() we'd end up with a third
style within the same family.
Good catch, we renamed the methods to use the to prefix.
- getNanoseconds()
It isn't obvious whether this returns the nanoseconds since epoch or
the
nanoseconds within the current second. getNanoOfSecond() (Java's
NANO_OF_SECOND) would remove that ambiguity.
We believe that with the renaming of the “Unix timestamp” converter to
use the to prefix and the lack of “Unix” in the name of
getNanoseconds() the naming is clear enough. It is also easy to see
that the result of getNanoseconds() can't be the number of nanoseconds
elapsed since the Unix epoch, because the value is guaranteed to be less
than 1_000_000_000 (i.e. less than 1 second). Using nanoseconds as the
fractional part is consistent with Duration::$nanoseconds.
For completeness I'd like to note that the epoch of the Instant is
intentionally left unspecified and that the Unix epoch is just one of
several possible epochs.
Best regards
Tim Düsterhus
Hi,
On 7.10.2026 12:45 CEST Tim Düsterhus tim@bastelstu.be wrote:
Hi
I think the following points still need some discussion. Some of
them may overlap with things others have already raised in this thread.
Rather than replying to each of those messages separately, I've
collected
everything in this one mail and mention who brought a point up where it
applies.Thank you for the review. Derick and I already discussed some, but not
all of the points. For now I'll just reply to those that are resolved to
get them out of the way.
- Configurable clock resolution
I'd like a way to configure the resolution,
e.g. a SystemClock that only ever returns whole seconds. Many consumers
(JWT iat/exp, HTTP dates, database columns) never want fractions.
A clock configured that way guarantees whole seconds to every consumer
it is injected into, instead of each consumer having to truncate on its
own. It also makes test expectations simpler, because the values never
carry
fractions.We believe this is the job of a “Clock decorator”, e.g.
CoarseClock
that takes in some clock and appropriately rounds or truncates the
Instant to the desired precision. Such a decorator would automatically
be compatible with any type of clock (e.g. aFrozenClockfor testing).As with the testing clocks, we consider a
CoarseClockout of scope for
this RFC. With a possible futuretruncateTo()that I already mentioned
elsewhere in the discussion, a userland implementation would become
trivial for the use cases where a coarser clock is required.
Fine by me to keep it out of scope for this RFC. For a future RFC, though, I'd prefer a built-in CoarseSystemClock over a generic decorator. A decorator always has to read the precise clock first and only then truncates, so it can never be cheaper than the clock it wraps. A built-in system clock configured for second (or millisecond) resolution could read a coarse clock source directly instead, such as CLOCK_REALTIME_COARSE on Linux.
On Linux arm64 I measured:
clock_gettime(CLOCK_REALTIME) 13.3 ns
clock_gettime(CLOCK_REALTIME_COARSE) 3.3 ns
- getNanoseconds()
It isn't obvious whether this returns the nanoseconds since epoch or
the
nanoseconds within the current second. getNanoOfSecond() (Java's
NANO_OF_SECOND) would remove that ambiguity.We believe that with the renaming of the “Unix timestamp” converter to
use thetoprefix and the lack of “Unix” in the name of
getNanoseconds()the naming is clear enough. It is also easy to see
that the result ofgetNanoseconds()can't be the number of nanoseconds
elapsed since the Unix epoch, because the value is guaranteed to be less
than 1_000_000_000 (i.e. less than 1 second). Usingnanosecondsas the
fractional part is consistent withDuration::$nanoseconds.
I'm not convinced yet
The value range is only "easy to see" after reading the documentation. The name alone should tell you what you get. Within the same class, the plural unit is used as a unit marker: toUnixTimestampMilliseconds() is "the timestamp in milliseconds". Read the same way, getNanoseconds() is "<something> in nanoseconds", and the only <something> an Instant has is its position on the timeline.
Duration is the wrong reference. A Duration is an amount, so "N nanoseconds" is correct there. An Instant is a point in time, and its sub-second part is a position within the second, not an amount. PEven the current DateTime* API already makes this distinction, using singular names:
DateTime::getMicrosecond(): int
DateTime::setMicrosecond(int $microsecond)
DateTime::setTime(int $hour, int $minute, int $second = 0, int $microsecond = 0)
while the plural is used for amounts:
Duration::fromMicroseconds(int $microseconds)
Duration::fromMilliseconds(int $milliseconds)
getNanoOfSecond() removes the ambiguity entirely, at the cost of a few characters. If that's too far from the existing naming, getNanosecond() would at least match DateTime::getMicrosecond().
For completeness I'd like to note that the epoch of the Instant is
intentionally left unspecified and that the Unix epoch is just one of
several possible epochs.
Agreed, and that's exactly the point I made about "follows POSIX time": if
the epoch and the representation are unspecified, the class definition
shouldn't state that Instants follow POSIX time. That belongs to the Unix
timestamp conversion methods.
Best regards,
Marc
Best regards
Tim Düsterhus