Framework: [5.5] Fill a eloquent date with an ISO8601 string causes "Unexpected data found. Trailing data"

Created on 4 May 2018  路  10Comments  路  Source: laravel/framework

  • Laravel Version: 5.5.40 (but should also affect 5.6.x)
  • PHP Version: PHP 7.2.2-3+ubuntu16.04.1+deb.sury.org+1
  • Database Driver & Version: -irrelevant-

Description:

Filling a model date attribute with an ISO8601 string causes a carbon exception: Unexpected data found. Trailing data

The problem is the asDateTime() function on HasAttributes trait. Note that it checks the following situations:

  • The value is a Carbon
  • The value is a DateTime
  • The value is a timestamp
  • The value is "Standard Date Format (Y-m-d)
  • The value is formatted as the default database connection format

This is a issue, since ISO8601 is the standard for javascript dates for example, and also handles timezone.

IMO, before the last check, we should check if it's an ISO8601 string, otherwise I'm unable to do

MyModel::create($request->all());

forcing me to always parse de date manually into a carbon instance, and that's a pain!

https://github.com/laravel/framework/blob/ac963a0dc8bc0e06591ee704cf279f2bff3d66ce/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php#L679-L717

Steps To Reproduce:

  1. Create a Model with some_date_field and add it to $dates property.
  2. Try to fill the some_date_field with an ISO8601 string, eg: 2018-05-04T16:04Z
  3. You'll get the following error:

    "message": "Unexpected data found.\nTrailing data",
    "exception": "InvalidArgumentException",
    "file": "/var/www/vendor/nesbot/carbon/src/Carbon/Carbon.php",
    "line": 836,
    

Most helpful comment

You can use a mutator:

public function setSomeDateFieldAttribute($value)
{
    $this->attributes['some_date_field'] = Carbon::parse($value)->toDateTimeString();
}

All 10 comments

You can use a mutator:

public function setSomeDateFieldAttribute($value)
{
    $this->attributes['some_date_field'] = Carbon::parse($value)->toDateTimeString();
}

But the "date" mutator already is a mutator! It just dont work for the most common date pattern in the world! I really hate when issues are closed without argumentation @tillkruss

Your question was answered. Using an Accessor and a Mutator solves your case.

Also, you didn't seem to have read the issue template, this is not the right place for feature requests.

most common date pattern in the world

I would argue about that ;-)

You can also specify a custom format:

protected $dateFormat = 'Y-m-d\TH:i\Z';

@staudenmeir It really will make the ISO8601 format be accepted, but it will try to save it as ISO8601 on database, and mysql doesn't accept it.

This property determines how date attributes are stored in the database, as well as their format when the model is serialized to an array or JSON

@tillkruss It really can be solved with a custom mutator, but it's just an workaround IMO. Also this isn't a feature request, it's a bug report.

Feel free to submit a PR 馃憤馃徎

I don't think the Database Driver & Version is irrelevant. Depending on the mysql strict mode settings you can either pass ISO8601 timestamps into mysql and it'll save as a UTC date time automatically, or it will fail. I have had similar issues with timestamp formats with laravel and carbon formatting and casts since I can remember, and it always requires some extra work to convert into the proper format, especially with mysql strict mode. Using the mutators is a solid solution as @staudenmeir suggested.

The strange is that I remember to do store the dates as 8601 with the date mutator without problems, but not sure if thats true.

I'll look about mysql supporting 8601, if it does I really can change the date time format to 8601 without problem!

Thanks for the idea!

I'm really agains using the mutator pattern in this case since it's the defaul behavior for a entire project, so it will be very repetitive. I'll make some testings and write down here to help anyone with same problem.

Other way around actually. Mysql stores Date Time fields in UTC without any timezone offsets. Everything you store in the timestamp fields should be converted to UTC, and you'd have to store the offsets separately for conversion on retrieval. My point was without mysql strict mode on, mysql let you pass an ISO8601 formatted timestamp and it would just save it as date time without complaining.

If this is the default behavior for the entire project, you can create a parent class for your models and override HasAttributes::asDateTime() etc.

Was this page helpful?
0 / 5 - 0 ratings