Skip to content

ChronosInterval and the accepted Time\Duration RFC (PHP 8.6) #529

Description

@dereuromark

The Duration Class RFC has been accepted for PHP 8.6. It adds a Time\Duration class. Since 4.x introduces ChronosInterval as a new public API, it is worth deciding now how the two relate, while nothing is released yet.

There is no direct conflict

Time\Duration lives in a new Time\ namespace, models exact elapsed time only (int $seconds + int $nanoseconds + bool $negative), and rejects ISO 8601 duration strings with a date component ("The biggest allowed component is H"). ChronosInterval decorates the native DateInterval and covers full calendar units.

More importantly, the RFC changes nothing on the existing classes: no new DateTimeImmutable::diff() return type, no changes to DateInterval. So Chronos::diff(), ChronosDate::diff() and Chronos::fromNow() returning ChronosInterval in 4.x remain valid on PHP 8.6. No class-name clash, no signature clash.

The RFC's Future Scope does say that "Calculating the difference between two points in time ('Instant') will return a Time\Duration object", and that calendar math above hours will get a dedicated class. That is a parallel Time\* API alongside DateTimeImmutable, so it does not affect Chronos today, but it is the direction of travel.

Three things worth deciding for 4.x

1. totalSeconds() hardcodes 30-day months and 365-day years

src/ChronosInterval.php:

public function totalSeconds(): int
{
    $seconds = $this->interval->s;
    $seconds += $this->interval->i * 60;
    $seconds += $this->interval->h * 3600;
    $seconds += $this->interval->d * 86400;
    $seconds += $this->interval->m * 30 * 86400;
    $seconds += $this->interval->y * 365 * 86400;

    return $this->interval->invert ? -$seconds : $seconds;
}

This is exactly the ambiguity the RFC cites as its reason for restricting Duration to hours and below: days and larger units "cannot be unambiguously mapped into a fixed number of seconds due to daylight saving time and months having different lengths".

The docblock does disclose the approximation, but this is a brand new public method, and a silently wrong number is worse than no method. Options:

  • Throw when y or m are non-zero and days === false, so only unambiguous intervals answer.
  • Drop the method and let callers diff two concrete dates.
  • Keep as is, with the caveat documented more prominently than a docblock note.

totalDays() is fine when the interval came from diff() (it uses ->days); it only falls back to the approximation otherwise, and the same decision applies to that fallback.

2. Method naming versus the RFC

The RFC's secondary vote picked full names 30 to 2. Current overlap is only add() / sub(), which already match in name and semantics. The rest diverges:

Time\Duration ChronosInterval
fromSeconds(), fromMinutes(), fromHours(), fromIso8601DurationString() create(), createFromValues(), createFromDateString()
negate(), absolute(), multiplyBy(), divideBy(), compare() not present
no ISO 8601 output method toIso8601String()

Keeping createFrom* is defensible since it matches DateTime::createFromFormat. But adding negate(), absolute(), multiplyBy(), divideBy() and a static compare() under the RFC's names is cheap, fills real gaps, and makes the mental model transfer once 8.6 lands. Worth doing before 4.0 is tagged, since adding them later is fine but renaming is not.

3. Interop, later

Nothing to implement yet, but eventually a toDuration() / fromDuration() pair could bridge the two, guarded by a PHP 8.6 version check. Only meaningful for intervals with no date component. Filing this as a note rather than a task.

Suggested outcome

Decide on point 1 before 4.0 is tagged (it is a correctness question, not a style one), optionally do point 2 in the same pass, and leave point 3 until PHP 8.6 is actually out.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions