I recently came across what felt like a Bug in PHP and was about to file a bug report. I found a couple of people talking about workarounds, but no explanation about why this “bug” exists and is allowed to persist. Hopefully this post helps to explain this unintuitive behavior.
The problem has to do with the PHP DateTime::createFromFormat() function. The desire here is usually to create a DateTime object that represents a precise point in time, up to 1/100,000th of a second. This code would normally work as expected:
$time = microtime(true);
$dateTimeObj = DateTime::createFromFormat('U.u', $time);
echo $dateTimeObj->format('Y-m-d H:i:s.u')."\n";
However, I was observing a problem that occurred infrequently that said
PHP Fatal error: Uncaught Error: Call to a member function format() on bool
So somehow, despite working the vast majority of the time, the $dateTimeObj would sometimes return a boolean (false) instead of a DateTime object.
After some looking into the failed cases, I found that the time 1696832681.000019 return false, but 1696832681.000119 would work as expected. The difference there is 100 microseconds apart. Clearly 0.000019 is close to zero, and somewhere being rounded down, and unexpectedly causing a problem. The DateTimeImmutable::GetLastErrors function tells me that the error is about “Data missing”
calling createFromFormat('U.u') with
float(1696832681.000049)
Array
(
[warning_count] => 0
[warnings] => Array
(
)
[error_count] => 1
[errors] => Array
(
[10] => Data missing
)
)
In order to understand what is occurring, you need to notice that the second argument for DateTime::createFromFormat is expected to be a string. So when using a float as the second argument, PHP internally converts it to a string first. And converting a high precision float to a string in this case, results it in rounding the float “1696832681.000049” to the string “1696832681”. Thus, the createFromFormat function is complaining about the “Data Missing” because it is expecting to see the period and microsecond portion of the string.
The fix is fortunately, very simple. Simply wrap the float around number_format($float, 6, '.', '')
which will return a string representation including the six decimal places intended. It’s not a elegant looking, but it doesn’t suffer from the occasional problem of returning false and having a fatal error!