Files
cakephp-chronos/PR.md
T
dualfroz 08305c1b95 fix: parse fractional seconds in ChronosTime as a fraction of a second
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.
2026-09-04 09:47:39 +02:00

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 to 999999 (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.