From 4e05a86e85b8697059dd6d9669e185263c3e9f58 Mon Sep 17 00:00:00 2001 From: Sam Hanes Date: Sun, 19 Jun 2022 04:29:35 -0700 Subject: [PATCH] Handle 'U' format in createFromFormat. Symfony 4.4 uses this strange construction to get the current time: DateTime::createFromFormat('U', time()) In normal PHP that works fine, but the mocked version was throwing TypeError: DateTime::createFromFormat() expects parameter 2 to be string, int given This explicitly stringifies the datetime parameter to match the behavior of the vanilla implementation. It also simplifies the workaround for createFromFormat returning the current time when the format doesn't contain any time parts. The prior overcomplicated version didn't work with the relative date structure returned for the 'U' format. --- src/ClockMock.php | 30 ++++++++++++++++++++---------- tests/ClockMockTest.php | 29 +++++++++++++++++++++++++---- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/src/ClockMock.php b/src/ClockMock.php index 27cdeaa..bc0fadb 100644 --- a/src/ClockMock.php +++ b/src/ClockMock.php @@ -166,16 +166,26 @@ private static function mock_date_create(): callable private static function mock_date_create_from_format(): callable { return function ($format, $datetime, DateTimeZone $timezone = null) { - $dateTimeObject = \DateTime::createFromFormat($format, $datetime, $timezone); - - $parsedDate = date_parse($datetime); - - return $dateTimeObject->setTime( - $parsedDate['hour'] === false ? idate('H') : $parsedDate['hour'], - $parsedDate['minute'] === false ? idate('i') : $parsedDate['minute'], - $parsedDate['second'] === false ? idate('s') : $parsedDate['second'], - $parsedDate['fraction'] === false ? (int) date('u') : $parsedDate['fraction'], - ); + $dateTimeObject = \DateTime::createFromFormat($format, (string) $datetime, $timezone); + + // If the format doesn't include any time parts `createFromFormat` uses the current time, not all zeroes. + // In that case the un-mocked call above will use the real time, not the mocked time. + // This uses `date_parse_from_format` to detect whether the format includes time parts and, + // if so, replaces the time of the result with the frozen time. + // `date_parse_from_format` returns false for all time parts if no time parts are included in the format, + // but if the format includes at least one time part all time parts not included become zero instead. + $parsedDate = date_parse_from_format($format, (string) $datetime); + if ($parsedDate['hour'] === false) { + $frozen = ClockMock::getFrozenDateTime(); + return $dateTimeObject->setTime( + (int) $frozen->format('H'), + (int) $frozen->format('i'), + (int) $frozen->format('s'), + (int) $frozen->format('u'), + ); + } else { + return $dateTimeObject; + } }; } diff --git a/tests/ClockMockTest.php b/tests/ClockMockTest.php index 775348d..3b07ccf 100644 --- a/tests/ClockMockTest.php +++ b/tests/ClockMockTest.php @@ -61,7 +61,7 @@ public function test_DateTimeImmutable_createFromFormat() // Verification: when not provided with a time, createFromFormat should use current time. $this->assertSame('2022-05-28 12:13:14', $dateTimeFromFormat->format('Y-m-d H:i:s')); } - + public function test_DateTime_constructor_with_absolute_mocked_date() { ClockMock::freeze($fakeNow = new \DateTime('1986-06-05')); @@ -107,14 +107,35 @@ public function test_DateTime_constructor_with_relative_mocked_date_without_micr $this->assertEquals($juneFifth1986, new \DateTime('1986-06-05')); } - public function test_DateTime_createFromFormat() + public function test_DateTime_createFromFormat_with_time() { - ClockMock::freeze(new \DateTimeImmutable('1986-06-05 12:13:14')); + ClockMock::freeze(new \DateTimeImmutable('1986-06-05 12:13:14.168432')); + + $dateTimeFromFormat = \DateTime::createFromFormat('Y-m-d i', '2022-05-28 24'); + + $this->assertSame('2022-05-28 00:24:00.000000', $dateTimeFromFormat->format('Y-m-d H:i:s.u')); + } + + public function test_DateTime_createFromFormat_without_time() + { + ClockMock::freeze(new \DateTimeImmutable('1986-06-05 12:13:14.168432')); $dateTimeFromFormat = \DateTime::createFromFormat('Y-m-d', '2022-05-28'); // Verification: when not provided with a time, createFromFormat should use current time. - $this->assertSame('2022-05-28 12:13:14', $dateTimeFromFormat->format('Y-m-d H:i:s')); + $this->assertSame('2022-05-28 12:13:14.168432', $dateTimeFromFormat->format('Y-m-d H:i:s.u')); + } + + public function test_DateTime_createFromFormat_unix() + { + ClockMock::freeze(new \DateTimeImmutable('1986-06-05 12:13:14.168432')); + + // this strange pattern occurs in Symfony 4.4.42 + // Symfony\Component\HttpFoundation\ResponseHeaderBag.php:306 + $dateTimeFromFormat = \DateTime::createFromFormat('U', time()); + + // time() is integer seconds, so the microseconds get rounded off + $this->assertSame('1986-06-05 12:13:14.000000', $dateTimeFromFormat->format('Y-m-d H:i:s.u')); } public function test_date()