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()