diff --git a/src/Chronos.php b/src/Chronos.php index 635c109..a74bfb6 100644 --- a/src/Chronos.php +++ b/src/Chronos.php @@ -660,98 +660,9 @@ public static function createFromFormat( throw new InvalidArgumentException($message); } - $testNow = static::getTestNow(); - if ($testNow !== null) { - $dateTime = static::applyTestNowToMissingComponents($dateTime, $format, $testNow); - } - return $dateTime; } - /** - * Apply testNow values to date/time components that weren't in the format string. - * - * @param static $dateTime The parsed datetime instance. - * @param string $format The format string used for parsing. - * @param \Cake\Chronos\Chronos $testNow The test now instance. - * @return static - */ - protected static function applyTestNowToMissingComponents( - self $dateTime, - string $format, - Chronos $testNow, - ): static { - // Parse format string to find which characters are actual format specifiers (not escaped) - $formatChars = static::getFormatCharacters($format); - - // Check which components are present in the format - $hasYear = (bool)array_intersect($formatChars, ['Y', 'y', 'o', 'X', 'x']); - $hasMonth = (bool)array_intersect($formatChars, ['m', 'n', 'M', 'F']); - $hasDay = (bool)array_intersect($formatChars, ['d', 'j', 'D', 'l', 'N', 'z', 'w', 'W', 'S']); - $hasHour = (bool)array_intersect($formatChars, ['H', 'G', 'h', 'g']); - $hasMinute = (bool)array_intersect($formatChars, ['i']); - $hasSecond = (bool)array_intersect($formatChars, ['s']); - $hasMicro = (bool)array_intersect($formatChars, ['u', 'v']); - - // If the format includes '!' or '|', PHP resets unspecified components to Unix epoch or zero - // If 'U' is present, all components are set from the Unix timestamp - // In these cases, we should not override with testNow - $hasReset = in_array('!', $formatChars, true) || in_array('|', $formatChars, true); - $hasUnixTimestamp = in_array('U', $formatChars, true); - if ($hasReset || $hasUnixTimestamp) { - return $dateTime; - } - - // Replace missing components with testNow values - $year = $hasYear ? $dateTime->year : $testNow->year; - $month = $hasMonth ? $dateTime->month : $testNow->month; - $day = $hasDay ? $dateTime->day : $testNow->day; - $hour = $hasHour ? $dateTime->hour : $testNow->hour; - $minute = $hasMinute ? $dateTime->minute : $testNow->minute; - $second = $hasSecond ? $dateTime->second : $testNow->second; - $micro = $hasMicro ? $dateTime->micro : $testNow->micro; - - // Only modify if something needs to change - if ( - !$hasYear || !$hasMonth || !$hasDay || - !$hasHour || !$hasMinute || !$hasSecond || !$hasMicro - ) { - return $dateTime - ->setDate($year, $month, $day) - ->setTime($hour, $minute, $second, $micro); - } - - return $dateTime; - } - - /** - * Extract format characters from a format string, handling escapes. - * - * @param string $format The format string. - * @return array Array of format characters. - */ - protected static function getFormatCharacters(string $format): array - { - $chars = []; - $length = strlen($format); - $i = 0; - - while ($i < $length) { - $char = $format[$i]; - - // Backslash escapes the next character - if ($char === '\\' && $i + 1 < $length) { - $i += 2; - continue; - } - - $chars[] = $char; - $i++; - } - - return $chars; - } - /** * Returns parse warnings and errors from the last ``createFromFormat()`` * call. diff --git a/tests/TestCase/DateTime/CreateFromFormatTest.php b/tests/TestCase/DateTime/CreateFromFormatTest.php index bdf826b..f124679 100644 --- a/tests/TestCase/DateTime/CreateFromFormatTest.php +++ b/tests/TestCase/DateTime/CreateFromFormatTest.php @@ -29,104 +29,6 @@ public function testCreateFromFormatReturnsInstance() $this->assertTrue($d instanceof Chronos); } - public function testCreateFromFormatWithTestNowMissingYear() - { - Chronos::setTestNow(new Chronos('2020-12-01 14:30:45')); - $d = Chronos::createFromFormat('m-d H:i:s', '10-05 09:15:30'); - $this->assertDateTime($d, 2020, 10, 5, 9, 15, 30); - } - - public function testCreateFromFormatWithTestNowMissingDate() - { - Chronos::setTestNow(new Chronos('2020-12-01 14:30:45')); - $d = Chronos::createFromFormat('H:i:s', '09:15:30'); - $this->assertDateTime($d, 2020, 12, 1, 9, 15, 30); - } - - public function testCreateFromFormatWithTestNowMissingTime() - { - Chronos::setTestNow(new Chronos('2020-12-01 14:30:45')); - $d = Chronos::createFromFormat('Y-m-d', '2021-06-15'); - $this->assertDateTime($d, 2021, 6, 15, 14, 30, 45); - } - - public function testCreateFromFormatWithTestNowPartialDate() - { - Chronos::setTestNow(new Chronos('2020-12-01 00:00:00')); - $d = Chronos::createFromFormat('m-d', '10-05'); - $this->assertDateTime($d, 2020, 10, 5, 0, 0, 0); - } - - public function testCreateFromFormatWithTestNowDayOnly() - { - Chronos::setTestNow(new Chronos('2020-12-01 00:00:00')); - $d = Chronos::createFromFormat('d', '05'); - $this->assertDateTime($d, 2020, 12, 5, 0, 0, 0); - } - - public function testCreateFromFormatWithTestNowComplete() - { - // When format is complete, testNow should not affect the result - Chronos::setTestNow(new Chronos('2020-12-01 14:30:45')); - $d = Chronos::createFromFormat('Y-m-d H:i:s', '1975-05-21 22:32:11'); - $this->assertDateTime($d, 1975, 5, 21, 22, 32, 11); - } - - public function testCreateFromFormatWithTestNowResetModifier() - { - // The '!' modifier resets to Unix epoch, should not use testNow - Chronos::setTestNow(new Chronos('2020-12-01 14:30:45')); - $d = Chronos::createFromFormat('!Y-m-d', '2021-06-15'); - $this->assertDateTime($d, 2021, 6, 15, 0, 0, 0); - } - - public function testCreateFromFormatWithTestNowPipeModifier() - { - // The '|' modifier resets unspecified components to zero, should not use testNow - Chronos::setTestNow(new Chronos('2020-12-01 14:30:45')); - $d = Chronos::createFromFormat('Y-m-d|', '2021-06-15'); - $this->assertDateTime($d, 2021, 6, 15, 0, 0, 0); - } - - public function testCreateFromFormatWithoutTestNow() - { - // Without testNow set, behavior should use real current time for missing components - Chronos::setTestNow(null); - $d = Chronos::createFromFormat('Y-m-d H:i:s', '1975-05-21 22:32:11'); - $this->assertDateTime($d, 1975, 5, 21, 22, 32, 11); - } - - public function testCreateFromFormatWithTestNowEscapedCharacters() - { - // Escaped format characters should not be treated as format specifiers - Chronos::setTestNow(new Chronos('2020-12-01 14:30:45')); - $d = Chronos::createFromFormat('\Y\-m-d', 'Y-10-05'); - $this->assertDateTime($d, 2020, 10, 5, 14, 30, 45); - } - - public function testCreateFromFormatWithTestNowMicroseconds() - { - Chronos::setTestNow(new Chronos('2020-12-01 14:30:45.123456')); - $d = Chronos::createFromFormat('Y-m-d H:i:s', '2021-06-15 09:15:30'); - $this->assertSame(123456, $d->micro); - } - - public function testCreateFromFormatWithTestNowUnixTimestamp() - { - // Unix timestamp ('U' format) sets all components, should not use testNow - Chronos::setTestNow(new Chronos('2020-12-01 14:30:45')); - $d = Chronos::createFromFormat('U', '0'); - $this->assertDateTime($d, 1970, 1, 1, 0, 0, 0); - } - - public function testCreateFromFormatWithTestNowNegativeUnixTimestamp() - { - // Negative Unix timestamp should also not use testNow - Chronos::setTestNow(new Chronos('2020-12-01 14:30:45')); - $d = Chronos::createFromFormat('U', '-1000'); - $this->assertDateTime($d, 1969, 12, 31, 23, 43, 20); - } - public function testCreateFromFormatWithTimezoneString() { $d = Chronos::createFromFormat('Y-m-d H:i:s', '1975-05-21 22:32:11', 'Europe/London'); @@ -160,4 +62,12 @@ public function testCreateFromFormatInvalidFormat() $this->assertIsArray(Chronos::getLastErrors()); $this->assertNotEmpty(Chronos::getLastErrors()['errors']); } + + public function testCreateFromFormatDoesNotUseTestNow() + { + // createFromFormat should not use testNow - it should behave like PHP's native method + Chronos::setTestNow(new Chronos('2020-12-01 14:30:45')); + $d = Chronos::createFromFormat('Y-m-d H:i:s', '1975-05-21 22:32:11'); + $this->assertDateTime($d, 1975, 5, 21, 22, 32, 11); + } }