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

Reply via email to