Carbon::instance fails if DateTime has microseconds assigned

Created on 28 Jun 2016  路  5Comments  路  Source: briannesbitt/Carbon

I'm using Carbon to work with MongoDB\BSON\UTCDateTime values, which are stored as a unix timestamp in milliseconds. I want to convert this to a Carbon object and preserve the millisecond precision. After converting it to a DateTime and trying to create a Carbon instance, it fails:

$timestamp = floor(microtime(true) * 1000); // 1467132827636.0
$mongoDate = new MongoDB\BSON\UTCDateTime($timestamp);
$carbonDate = Carbon\Carbon::instance($mongoDate->toDateTime());

Exception with message 'DateTime::__construct(): Failed to parse time string (2016-06-28 16:53:47.636000000) at position 11 (1): The timezone could not be found in the database'

This seems to be caused because of the three extra trailing zeros generated by $dt->format('Y-m-d H:i:s.u'). It's unclear to me why this is happening--it seems like it could be a bug in PHP (I'm running 7.0.8). Certainly DateTime doesn't support nanosecond precision!

The only way I've found to make this work is to manipulate the timestamps as strings (yuck):

$timestamp = (string)$mongoDate; // "1467132827636"
Carbon::parse(date('Y-m-d H:i:s', bcdiv($timestamp, 1000)) . '.' . substr($timestamp, -3));

Most helpful comment

@lucasmichot createFromFormat is supposed to take a string, not a DateTime object. That comment you linked to is not an indication that this is "expected." It's still a bug, even if it's confirmed by others.

$mongoDate->toDateTime()->format('u') returns "636000000" instead of "636000" when "u" is supposed to be _microseconds_ (according to php.net/date), not nanoseconds. It's off by a factor of 1000, and seems to be treating it as a string instead of a fraction. I don't know if the blame lies in how UTCDate::toDateTime() works, or in how DateTime stores the microseconds internally that it would allow such a large number.

Either way, this is a PHP bug and not related to Carbon.

Edit: Upstream bug report: https://jira.mongodb.org/browse/PHPC-631

Fixed in version 1.1.8 of the PHP MongoDB driver.

All 5 comments

I had similar problem with MS SQL Server with Laravel 5.2.

Try adding the following 2 methods to your model.

public function getDateFormat()
{
    return 'Y-m-d H:i:s.u';
}

public function fromDateTime($value)
{
    return substr(parent::fromDateTime($value), 0, -3);
}

But to avid having to copy and paste my code into bunch of model I used class inheritance and extended my model like so.

I create my BaseModel Class

<?php 
namespace Vendor\MyApp;

use Illuminate\Database\Eloquent\Model as EloquentModel;

class BaseModel extends EloquentModel 
{

    public function getDateFormat()
    {
        return 'Y-m-d H:i:s.u';
    }

    public function fromDateTime($value)
    {
        return substr(parent::fromDateTime($value), 0, -3);
    }

}

Then for each model I had I extended my BaseModel class something like this

 class User extends BaseModel 
 {
 ...
 ...
 }

I hope this helps

@malhayek2014 The substr method is not robust. It should at least use a regex to check the format and length of the number of characters after the decimal point before truncating it. Also, instead of overriding getDateFormat(), you can just set the property: protected $dateFormat = 'Y-m-d\TH:i:s.uP'; (from my own BaseModel)

@hackel , this is expected: http://php.net/manual/en/mongodb-bson-utcdatetime.todatetime.php#119308.
Feel free to extend \Carbon\Carbon

<?php

require __DIR__.'/vendor/autoload.php';

use Carbon\Carbon;
use MongoDB\BSON\UTCDatetime;

class MyCarbon extends Carbon
{
    /**
     * Create a new instance from a BSON UTCDatetime.
     *
     * @param \MongoDB\BSON\UTCDatetime $UTCDatetime
     * @return static
     */
    public static function fromBSONUTCDateTime(UTCDatetime $UTCDatetime)
    {
        return static::createFromFormat('Y-m-d H:i:s.u', $UTCDatetime->toDateTime());
    }
}

@lucasmichot createFromFormat is supposed to take a string, not a DateTime object. That comment you linked to is not an indication that this is "expected." It's still a bug, even if it's confirmed by others.

$mongoDate->toDateTime()->format('u') returns "636000000" instead of "636000" when "u" is supposed to be _microseconds_ (according to php.net/date), not nanoseconds. It's off by a factor of 1000, and seems to be treating it as a string instead of a fraction. I don't know if the blame lies in how UTCDate::toDateTime() works, or in how DateTime stores the microseconds internally that it would allow such a large number.

Either way, this is a PHP bug and not related to Carbon.

Edit: Upstream bug report: https://jira.mongodb.org/browse/PHPC-631

Fixed in version 1.1.8 of the PHP MongoDB driver.

Thanks for this useful feeback @hackel

Was this page helpful?
0 / 5 - 0 ratings