Search code examples
phpdatemilliseconds

PHP Converting milliseconds to date fails for specific millisecond (1425318722000)


So I'm trying to convert milliseconds to date in PHP and I thought the script I had was working fine but getting odd behaviour for a specific millisecond value (1425318722000).

I've checked this across a few websites and all come back with a valid value...

Monday, March 2, 2015 5:52:02 PM GMT

Mon Mar 02 2015 17:52:02

Mon Mar 02 2015 17:52:02 GMT+0000 (GMT)

Mon, 02 Mar 2015 17:52:02 GMT

Any idea why this occurs, is it a PHP bug perhaps?

php -r "var_dump(DateTime::createFromFormat('U.u', 1425318721999/1000));"
object(DateTime)#1 (3) {
  ["date"]=>
  string(19) "2015-03-02 17:52:01"
  ["timezone_type"]=>
  int(1)
  ["timezone"]=>
  string(6) "+00:00"
}

php -r "var_dump(DateTime::createFromFormat('U.u', 1425318722000/1000));"
bool(false)

php -r "var_dump(DateTime::createFromFormat('U.u', 1425318722001/1000));"
object(DateTime)#1 (3) {
  ["date"]=>
  string(19) "2015-03-02 17:52:02"
  ["timezone_type"]=>
  int(1)
  ["timezone"]=>
  string(6) "+00:00"
}

php -r "var_dump(DateTime::createFromFormat('U.u', 1425318722002/1000));"
object(DateTime)#1 (3) {
  ["date"]=>
  string(19) "2015-03-02 17:52:02"
  ["timezone_type"]=>
  int(1)
  ["timezone"]=>
  string(6) "+00:00"
}

php -r "var_dump(DateTime::createFromFormat('U.u', 1425318722003/1000));"
object(DateTime)#1 (3) {
  ["date"]=>
  string(19) "2015-03-02 17:52:02"
  ["timezone_type"]=>
  int(1)
  ["timezone"]=>
  string(6) "+00:00"
}

php -r "var_dump(DateTime::createFromFormat('U.u', 1425318722004/1000));"
object(DateTime)#1 (3) {
  ["date"]=>
  string(19) "2015-03-02 17:52:02"
  ["timezone_type"]=>
  int(1)
  ["timezone"]=>
  string(6) "+00:00"
}

php -r "var_dump(DateTime::createFromFormat('U.u', 1425318722005/1000));"
object(DateTime)#1 (3) {
  ["date"]=>
  string(19) "2015-03-02 17:52:02"
  ["timezone_type"]=>
  int(1)
  ["timezone"]=>
  string(6) "+00:00"
}

Solution

  • It's the fact that you're using the 'U.u' mask, but the .u is lost from the value when the result is all 0s after the decimal for that division

    for($ms = 1425318721999; $ms <= 1425318722001; ++$ms) {
        var_dump(DateTime::createFromFormat('U.u', sprintf('%14.3f', $ms/1000)));
    }
    

    Will work, because you're using sprintf() to force those zeroes to be retained after the decimal point

    for($ms = 1425318721999; $ms <= 1425318722001; ++$ms) {
        var_dump(DateTime::createFromFormat('U', floor($ms/1000)));
    }
    

    would also work, but you'd lose the milliseconds precision

    By way of some explanation:

    public static DateTime DateTime::createFromFormat ( string $format , string $time [, DateTimeZone $timezone ] )
    

    the createFromFormat() expects a string as the second argument, so PHP is loose casting the result of your division to a string, and a float value like 1425318722.000 will be cast to a string of "1425318722" with no decimal point or following zeroes, so it doesn't conform with the U.u mask which requires the decimal point and following digits