Hi
On 2026-10-01 17:40, Ilia wrote:
1. 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 `Duration`s 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()`).
2. 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.
3. 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