From 87d51312600eedf31badbb54570f7b2543aec6ab Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 26 Mar 2026 12:15:38 +0100 Subject: [PATCH 1/3] Return ChronosInterval from Chronos::diff() This changes Chronos::diff() and ChronosDate::diff() to return ChronosInterval instead of DateInterval. This provides: - ISO 8601 duration formatting via __toString() - Convenience methods like totalSeconds(), totalDays(), isZero(), isNegative() - Arithmetic methods add(), sub() - toDateString() for strtotime-compatible output - toNative() for backwards compatibility when DateInterval is needed Users who need a DateInterval can call ->toNative() on the result. Also updates fromNow() to return ChronosInterval. Closes #511 --- src/Chronos.php | 10 ++++---- src/ChronosDate.php | 6 ++--- tests/TestCase/ChronosIntervalTest.php | 34 +++++++++++++++++++------- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/Chronos.php b/src/Chronos.php index 2cb3e0c7..b5c6c472 100644 --- a/src/Chronos.php +++ b/src/Chronos.php @@ -1011,11 +1011,11 @@ public function modify(string $modifier): static * * @param \DateTimeInterface $target Target instance * @param bool $absolute Whether the interval is forced to be positive - * @return \DateInterval + * @return \Cake\Chronos\ChronosInterval */ - public function diff(DateTimeInterface $target, bool $absolute = false): DateInterval + public function diff(DateTimeInterface $target, bool $absolute = false): ChronosInterval { - return parent::diff($target, $absolute); + return new ChronosInterval(parent::diff($target, $absolute)); } /** @@ -2778,9 +2778,9 @@ public function secondsUntilEndOfDay(): int * Convenience method for getting the remaining time from a given time. * * @param \DateTimeInterface $other The date to get the remaining time from. - * @return \DateInterval|bool The DateInterval object representing the difference between the two dates or FALSE on failure. + * @return \Cake\Chronos\ChronosInterval The ChronosInterval object representing the difference between the two dates. */ - public static function fromNow(DateTimeInterface $other): DateInterval|bool + public static function fromNow(DateTimeInterface $other): ChronosInterval { $timeNow = new static(); diff --git a/src/ChronosDate.php b/src/ChronosDate.php index 3d800892..902314f3 100644 --- a/src/ChronosDate.php +++ b/src/ChronosDate.php @@ -407,11 +407,11 @@ public function setISODate(int $year, int $week, int $dayOfWeek = 1): static * * @param \Cake\Chronos\ChronosDate $target Target instance * @param bool $absolute Whether the interval is forced to be positive - * @return \DateInterval + * @return \Cake\Chronos\ChronosInterval */ - public function diff(ChronosDate $target, bool $absolute = false): DateInterval + public function diff(ChronosDate $target, bool $absolute = false): ChronosInterval { - return $this->native->diff($target->native, $absolute); + return new ChronosInterval($this->native->diff($target->native, $absolute)); } /** diff --git a/tests/TestCase/ChronosIntervalTest.php b/tests/TestCase/ChronosIntervalTest.php index ffefedc4..2875a0d4 100644 --- a/tests/TestCase/ChronosIntervalTest.php +++ b/tests/TestCase/ChronosIntervalTest.php @@ -15,11 +15,32 @@ namespace Cake\Chronos\Test\TestCase; use Cake\Chronos\Chronos; +use Cake\Chronos\ChronosDate; use Cake\Chronos\ChronosInterval; use DateInterval; class ChronosIntervalTest extends TestCase { + public function testChronosDiffReturnsChronosInterval(): void + { + $start = new Chronos('2020-01-01'); + $end = new Chronos('2020-01-11'); + $diff = $start->diff($end); + + $this->assertInstanceOf(ChronosInterval::class, $diff); + $this->assertSame(10, $diff->d); + } + + public function testChronosDateDiffReturnsChronosInterval(): void + { + $start = ChronosDate::create(2020, 1, 1); + $end = ChronosDate::create(2020, 1, 11); + $diff = $start->diff($end); + + $this->assertInstanceOf(ChronosInterval::class, $diff); + $this->assertSame(10, $diff->d); + } + public function testCreateFromSpec(): void { $interval = ChronosInterval::create('P1Y2M3D'); @@ -88,10 +109,8 @@ public function testToIso8601StringNegative(): void { $past = new Chronos('2020-01-01'); $future = new Chronos('2021-02-02'); - $diff = $past->diff($future); - $diff->invert = 1; + $interval = $future->diff($past); - $interval = ChronosInterval::instance($diff); $this->assertStringStartsWith('-P', $interval->toIso8601String()); } @@ -123,9 +142,8 @@ public function testTotalDaysFromDiff(): void { $start = new Chronos('2020-01-01'); $end = new Chronos('2020-01-11'); - $diff = $start->diff($end); + $interval = $start->diff($end); - $interval = ChronosInterval::instance($diff); $this->assertSame(10, $interval->totalDays()); } @@ -136,9 +154,8 @@ public function testIsNegative(): void $past = new Chronos('2020-01-01'); $future = new Chronos('2020-01-02'); - $diff = $future->diff($past); + $interval = $future->diff($past); - $interval = ChronosInterval::instance($diff); $this->assertTrue($interval->isNegative()); } @@ -296,9 +313,8 @@ public function testToDateStringNegative(): void { $past = new Chronos('2020-01-01'); $future = new Chronos('2020-01-02'); - $diff = $future->diff($past); + $interval = $future->diff($past); - $interval = ChronosInterval::instance($diff); $this->assertStringStartsWith('-', $interval->toDateString()); } From b9727444ab7f925b7515f2e5fb5eac576e9f831e Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 26 Mar 2026 12:37:01 +0100 Subject: [PATCH 2/3] Add ReturnTypeWillChange attribute and phpstan ignore Suppress PHP deprecation notice and PHPStan errors for the intentional return type change from DateInterval to ChronosInterval. --- phpstan.neon | 3 +++ src/Chronos.php | 1 + 2 files changed, 4 insertions(+) diff --git a/phpstan.neon b/phpstan.neon index 02711ac3..1d3977ae 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -17,3 +17,6 @@ parameters: message: "#with generic class DatePeriod but does not specify its types: TDate, TEnd, TRecurrences$#" count: 1 path: src/ChronosDatePeriod.php + - + identifier: method.childReturnType + path: src/Chronos.php diff --git a/src/Chronos.php b/src/Chronos.php index b5c6c472..73d958e6 100644 --- a/src/Chronos.php +++ b/src/Chronos.php @@ -1013,6 +1013,7 @@ public function modify(string $modifier): static * @param bool $absolute Whether the interval is forced to be positive * @return \Cake\Chronos\ChronosInterval */ + #[\ReturnTypeWillChange] public function diff(DateTimeInterface $target, bool $absolute = false): ChronosInterval { return new ChronosInterval(parent::diff($target, $absolute)); From 556c707f58747168a91e52bdf4c24cd5c7761c37 Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 26 Mar 2026 12:38:37 +0100 Subject: [PATCH 3/3] Use import for ReturnTypeWillChange attribute --- src/Chronos.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Chronos.php b/src/Chronos.php index 73d958e6..baa67b14 100644 --- a/src/Chronos.php +++ b/src/Chronos.php @@ -20,6 +20,7 @@ use DateTimeInterface; use DateTimeZone; use InvalidArgumentException; +use ReturnTypeWillChange; use RuntimeException; use Stringable; @@ -1013,7 +1014,7 @@ public function modify(string $modifier): static * @param bool $absolute Whether the interval is forced to be positive * @return \Cake\Chronos\ChronosInterval */ - #[\ReturnTypeWillChange] + #[ReturnTypeWillChange] public function diff(DateTimeInterface $target, bool $absolute = false): ChronosInterval { return new ChronosInterval(parent::diff($target, $absolute));