Skip to content

Custom dateFormat generates Carbon "missing data" error on update if timestamps exist #21338

@pdbreen

Description

@pdbreen
  • Laravel Version: 5.5.12
  • PHP Version: 7.1.6
  • Database Driver & Version: MySql 5.7.18

Description:

When attempting to update() a model that has a custom $dateFormat set, Carbon generates an "Unexpected data found. Data missing." exception.

Steps To Reproduce:

Migration

        Schema::create('date_format_tests', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->integer('test_value')->nullable();
            $table->timestamps();
        });

Model

class DateFormatTest extends Model
{
    protected $guarded = [];
    protected $dateFormat = \DateTime::ATOM;
}

Failing Test Case

    public function test_it_will_update()
    {
        $item = \App\DateFormatTest::create();

        $item = $item->fresh(); // If you don't refresh the item, you don't get the error!
        $item->update(['test_value' => 1]);     // <= FAILURE HERE!

        $this->assertEquals(1, $item->test_value);
    }

Stack

Unexpected data found.
Data missing
 D:\src\jrweb\vendor\nesbot\carbon\src\Carbon\Carbon.php:582
 D:\src\jrweb\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Concerns\HasAttributes.php:715
 D:\src\jrweb\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Concerns\HasAttributes.php:738
 D:\src\jrweb\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Concerns\HasAttributes.php:1062
 D:\src\jrweb\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Concerns\HasAttributes.php:1023
 D:\src\jrweb\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Model.php:611
 D:\src\jrweb\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Model.php:529
 D:\src\jrweb\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Model.php:476
 D:\src\jrweb\tests\DateFormatTest.php:14

With Laravel 5.4, I was using a custom $dateFormat to essentially map data from external sources into a Laravel Model/MySql table. This worked fine. I think this PR #18400 introduced the problem.

While I could use set mutators for this (since its inbound only), its a bit of a pain if there are multiple models involved, each having multiple date fields to convert.

What would be wonderful would be a way to define an array of acceptable input patterns that HasAttributes::fromDateTime could check. Something along the lines of this:

    // Null by default, showing an override example here
    protected $dateTimePatterns = [
        '/[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}[\+\-][0-9]{2}:[0-9]{2}/' => \DateTime::ATOM
    ];
    
    /** **/
    
    public function fromDateTime($value)
    {
        // Check allowed input patterns
        if ($this->dateTimePatterns && is_string($value)) {
            foreach ($this->dateTimePatterns as $pattern => $format) {
                if (preg_match($pattern, $value)) {
                    return Carbon::createFromFormat($format, $value);
                }
            }
        }

        // Current Implementation!
        return is_null($value) ? $value : $this->asDateTime($value)->format(
            $this->getDateFormat()
        );
    }

(What would be even better is if the regex pattern could be assembled automatically from the date time format string!!)

In the meantime, I have locally overridden fromDateTime in a shared base model to get me going again in 5.5

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions