Re: [RFC] Time\Instant and Time\Clock
| From: | marc at mabe dot berlin | Date: | Fri, 06 Nov 2026 08:33:40 +0000 |
| Subject: | Re: [RFC] Time\Instant and Time\Clock | ||
| References: | 1 2 3 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-133016@lists.php.net to get a copy of this message | ||
Hi Tim,
> On 29.10.2026 14:39 CET Tim Düsterhus <tim@bastelstu.be> wrote:
>
>
> Hi
>
> Due to the security releases it's been a while since I last looked into
> this RFC and I probably lost some of my working memory. Please indicate
> if there is something obvious that I missed somewhere or similar.
>
> On 2026-10-06 11:17, marc@mabe.berlin wrote:
> > 3. Clock resolution
> >
> [...]
>
> … but we can see how exposing the (nominal) resolution of specifically
> the SystemClock can be useful. I assume that a
>
SystemClock::getNominalResolution(): Duration with the
> Duration
> being the greatest common divisor of possible returned values is what
> you would expect? [...]
Yes, that’s exactly what I would expect. Thanks for adding it to the future scope.
>
> > 5. 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.
>
> Our goal with this RFC series is to provide a well-designed and
> well-typed new API that nudges the user to do the right thing. Making
> “raw integers” a first class citizen would go against that goal to save
> 7 characters (and one object creation). The previous arguments against
> adding a takeZonedDateTime() or similar also apply here.
This isn’t about saving characters. Software reads the current timestamp all the time and for many
reasons, so that call should be as cheap as possible. With the proposed API, getting the Unix
timestamp integer means creating an Instant object and making one more method call, only to get back
the integer the system already returned in the first place.
>
> >
> > 8. 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:
>
> The toUnixTimestamp() +
> getNanoseconds() pair is the canonical
> representation of a Unix timestamp - with “seconds” also being arguably
> by far the most common interpretation when hearing “Unix timestamp”. The
> “milliseconds” variant is provided for interoperability convenience, in
> particular with JavaScript.
I find it odd to give milliseconds a dedicated method just because one other language uses them as
its main Unix timestamp resolution. And JavaScript doesn’t even use an integer there - it's a
floating point. The result is an API that looks inconsistent: seconds and milliseconds are covered,
microseconds and nanoseconds are not.
>
> PHP’s existing DateTime does not provide functionality to
> obtain the
> timestamp as an integral number of microseconds, and
> microtime()
> either returns a float or a weird two-component pair as a
> string. I am
> not aware of anything commonly used that returns “integral microseconds
> since the Unix epoch”, do you know of anything?
Microseconds are the finest resolution in a lot of what PHP applications work with every day:
* PHP itself: DateTime/DateTimeImmutable, microtime()
* MySQL/MariaDB: DATETIME/TIMESTAMP with up to 6 fractional digits
* PostgreSQL: timestamp/timestamptz
* Apache Spark: TIMESTAMP
* Python: datetime/timedelta
>
> > 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
>
> I do not find it obvious at all that “seconds” would be the largest
> supported unit of a TimeUnit.
Agreed, it isn’t really a time unit. What about something like FractionUnit or SecondUnit, so that
the name makes clear it covers seconds and their fractions only?
>
> > 10. 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
>
> Yes. I don't understand what you are getting at, though. There clearly
> has to be a way to represent a point in time in some way - and an
> ISO-8601 datetime string is one of the possible options, probably the
> most widely used one. The Unix timestamp is another one.
The RFC defines Instant as "an absolute point on the timeline of the universe". It also
says the timeline of the universe "does not know about days or months". An ISO-8601 string
is made of exactly those things: a calendar (years, months, days) and an offset from UTC. Both
belong to the Earth, not to the timeline of the universe.
The Unix timestamp is different in nature. It is the distance from one point in time to another. Any
point on the timeline of the universe needs at least one reference point to be expressed.
I see why the ISO-8601 representation is convenient, and I have never had to represent a time
outside Earth myself. But is it really the right thing to do? At the very least, it conflicts with
the definition of Instant.
>
> > 11. 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
>
> Since the RFC guarantees that only legal formats will be accepted, you
> are able to use it as a validator. If your use case requires narrower
> validation, then you will need to perform custom validation according to
> whatever specification you are dealing with. This is not unique to the
> Instant class.
My concern is the wording in the RFC: “It will only accept well-formed inputs, but may not support
all legal formats” is very vague. It doesn’t say which legal formats are not supported, and I
think that should be written out explicitly. ...
>
> > 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.
>
> See my reply to Ilia (https://news-web.php.net/php.internals/132764).
> The RFC changes are still open.
... OK
>
> > 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?
>
> Thanks, I adjusted the prose to also require a “zone offset”.
>
> > 12. 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.
>
> That is a bit of a distinction without difference: RFC 3339 is only
> capable of representing years 0000 to 9999, so years outside of that
> range are by definition not representable as RFC 3339. The ISO-8601
> getter specifically only switches to the expanded format outside that
> range. Adding a separate RFC 3339 getter that differs only by throwing
> an Exception for unrepresentable instants does not sound useful, since
> this Exception is not actionable in any way.
I did not ask for a separate RFC 3339 getter. I only wanted to point out that the output of
toIso8601DateTimeString() does not conform to RFC 3339 in all cases - not more.
>
> > 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_ATOM
> > only
> > 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.
>
> I do not believe that the existing DateTime API is a model of
> good
> design in that regard (and there are better ways for interoperability
> with it, as noted in
> https://news-web.php.net/php.internals/132761).
> Only taking a fixed format is already a problem as of now, because
> “conditionally omitting the fractional part” is common.
A lot of code depends on the existing DateTime API, so there should be a simple way to convert in
both directions. The RFC already adds DateTimeInterface::toInstant(). Why not also add the reverse,
e.g. DateTimeImmutable::createFromInstant() / DateTime::createFromInstant()?
>
> >
> > 13. 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 agree with Larry’s DX argument here. I'd also like to note that the
> *output* format is fixed by the RFC. The input format is less
> constrained, but the RFC guarantees that it stays true to its name by
> only accepting what ISO-8601 specifies.
About DX - the simplicity comes from having a dedicated function to parse/format to/from
ISO-8601/Instant without having to instantiate or configure anything - which is a good argument but
this can be solved differently as well without introducing concepts to Instant it is not designed
for e.g. by proving helper functions Time\parseIso8601DateTimeToInstant´,
Time\instantToIso8601DateTime`.
>
> >
> > 14. until() vs Duration::between()
> >
> > Was Duration::between(Instant $a, Instant $b) considered? Calculating a
> > duration feels like Duration's responsibility to me.
>
> That suggestion breaks down when measuring elapsed time with a monotonic
> clock. PHP does not support method overloading, so it would need to
> become Duration::between(Instant|MonoInstant,
> Instant|MonoInstant)
> which then also allows non-sense combinations.
mh true
Thanks,
Marc Bennewitz