From 7487888650c344e69285f4fd8e2dea25679eb73b Mon Sep 17 00:00:00 2001 From: JoniJnm <577282+JoniJnm@users.noreply.github.com> Date: Fri, 16 Oct 2026 13:26:25 +0200 Subject: [PATCH] keep DateTimeImmutable inner and outer dates in sync, fixes #759 The constructor created the parent and the inner \DateTimeImmutable separately, so relative times like 'now' could differ by microseconds between the two, breaking diff() and comparisons. Build the inner date first and initialise the parent from its exact value and timezone. Co-Authored-By: Claude Opus 5.5 --- lib/DateTimeImmutable.php | 2 +- tests/DateTimeImmutableTest.php | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/DateTimeImmutable.php b/lib/DateTimeImmutable.php index 8a240e8d..99f74101 100644 --- a/lib/DateTimeImmutable.php +++ b/lib/DateTimeImmutable.php @@ -24,8 +24,8 @@ class DateTimeImmutable extends \DateTimeImmutable */ public function __construct($time = 'now', $timezone = null) { - parent::__construct($time, $timezone); $this->innerDateTime = new parent($time, $timezone); + parent::__construct($this->innerDateTime->format('Y-m-d H:i:s.u'), $this->innerDateTime->getTimezone()); } //switch between regular datetime and safe version diff --git a/tests/DateTimeImmutableTest.php b/tests/DateTimeImmutableTest.php index a6c27cfd..9ad4226f 100644 --- a/tests/DateTimeImmutableTest.php +++ b/tests/DateTimeImmutableTest.php @@ -56,6 +56,31 @@ public function testConstructorPreserveTimeAndTimezone(): void $this->assertEquals($timezone, $datetime->getTimezone()); } + public function testConstructorInnerAndOuterDatesAreEqual(): void + { + for ($i = 0; $i < 1000; $i++) { + $datetime = new \Safe\DateTimeImmutable(); + $this->assertSame( + self::nativeFormat($datetime, 'Y-m-d H:i:s.u'), + $datetime->getInnerDateTime()->format('Y-m-d H:i:s.u') + ); + } + } + + public function testConstructorPreservesTimezoneFromTimeString(): void + { + foreach (['@1700000000', '2020-01-01T00:00:00+02:00'] as $time) { + $datetime = new \Safe\DateTimeImmutable($time); + $this->assertSame(self::nativeFormat($datetime, 'U P'), $datetime->getInnerDateTime()->format('U P')); + } + } + + private static function nativeFormat(\DateTimeImmutable $datetime, string $format): string + { + // bypass \Safe\DateTimeImmutable::format() to read the parent's own value + return (new \ReflectionMethod(\DateTimeImmutable::class, 'format'))->invoke($datetime, $format); + } + public function testCreateFromFormatPreserveTimeAndTimezone(): void { $timezone = new \DateTimeZone('Pacific/Chatham');