ChronosTime::parseString() passed the fractional-seconds digits straight to (int), so a short fraction such as .5 was read as 5 microseconds instead of 500000 (half a second), diverging from DateTime. Right-pad the fractional digits to microseconds before converting.
2.1 KiB
fix: parse fractional seconds in ChronosTime as a fraction of a second
Problem
ChronosTime misinterprets a time string whose fractional-seconds part has fewer
than six digits. The fractional part is treated as a literal microsecond count
instead of a fraction of a second:
(new Cake\Chronos\ChronosTime('12:00:00.5'))->format('H:i:s.u');
// "12:00:00.000005" (5 microseconds)
// expected "12:00:00.500000" (half a second, matching DateTime)
(new DateTime('12:00:00.5'))->format('u'); // "500000"
So 12:00:00.5 becomes 5 microseconds rather than 500000, diverging from PHP's
own DateTime and from the round-trip used when constructing a ChronosTime
from a DateTimeInterface (which formats to six digits).
Root cause
src/ChronosTime.php, parseString():
$microseconds = (int)substr($matches[4] ?? '', 0, 6);
$matches[4] is the digit run after the decimal point. Passing it straight to
(int) reads "5" as 5, ignoring that these are the most-significant digits of
a fractional second. Only six-digit input (for example .500000) happened to
work, which is why the issue was not caught: every existing test uses a full
six-digit fraction (or a seven-digit one that is truncated to six).
The fix
Right-pad the (at most six) fractional digits to microseconds before converting:
$microseconds = (int)str_pad(substr($matches[4] ?? '', 0, 6), 6, '0', STR_PAD_RIGHT);
.5->500000.05->50000.500000->500000(unchanged).000005->5(unchanged).9999991-> truncated to999999(unchanged; existing behaviour preserved)
This matches how DateTime interprets fractional seconds.
Test
Added ChronosTimeTest::testConstructFromStringWithFractionalSeconds, asserting
that 12:00:00.5, 12:00:00.05 and 12:00:00.000005 produce
500000, 50000 and 5 microseconds respectively.
Gate
composer install && vendor/bin/phpunit
Result: exit code 0 - OK (981 tests, 2496 assertions).
Counterfactual: reverting parseString() to (int)substr(...) makes the new test
fail - 12:00:00.5 yields 12:00:00.000005 instead of 12:00:00.500000.