laravel / framework

The Laravel Framework.

Home Page:https://laravel.com

Repository from Github https://github.comlaravel/frameworkRepository from Github https://github.comlaravel/framework

L8.15.0 - Validation for RFC3339_EXTENDED date format doesn't work

vlauciani opened this issue · comments

  • Laravel Framework 8.15.0
  • PHP Version: 7.4

Description:

The validation for RFC3339_EXTENDED PHP date format, doesn't work correctly.

Steps To Reproduce:

<?php
// ./routes/web.php
use Illuminate\Support\Facades\Route;
Route::get('test_date', function () {
    $array = [
        'mydate1' => '2018-01-29T20:36:01.123Z',
        'mydate2' => '2018-01-29T20:36:01.123+00:00'
    ];
    Validator::make($array, [
        'mydate1' => 'required|date_format:"' . \DateTimeInterface::RFC3339_EXTENDED . '"',
        'mydate2' => 'required|date_format:"' . \DateTimeInterface::RFC3339_EXTENDED . '"'
    ])->validate();
    return "Ok";
});

Both mydate1 and mydate2 are a valid RFC3339_EXTENDED PHP date format:

but calling the test_date route on your browser, Laravel Validation return an error on mydate1.

The validator just uses DateTime::createFromFormat behind the scenes so this isn't a Laravel issue.

@driesvints DateTime::createFromFormat works; the date are "valid". Please, check the link to the sandbox.

@vlauciani although both dates are created with no errors when calling DateTIme::createFromFormat(...), the one ending with Z does not match the format string specified in the DateTimeInterface::RFC3339_EXTENDED constant.

If you run:

echo DateTimeInterface::RFC3339_EXTENDED;

You will get

Y-m-d\TH:i:s.vP

In PHP docs' date format page where they list all the format tokens P is described as:

format character Description Example returned values
P Difference to Greenwich time (GMT) with colon between hours and minutes Example: +02:00

Reference: https://www.php.net/manual/en/datetime.format.php

So P should not match Z. Actually Z is meant to be accepted by using p instead (lower case p) if you look in the table within the link above.

I don't know why DateTime::createFromFormat(...) doesn't fail with this mismatch between the format string and the value provided. Might be a bug or some intended behavior to widen up date strings processing. Maybe you could try PHP's externals/internals mail list to learn the rationale around it.

But, in my opinion, it is not a bug in how Laravel handles this validation. Actually Laravel's validation for date and time format is pretty straightforward:

$date = DateTime::createFromFormat('!'.$format, $value);
return $date && $date->format($format) == $value;

Just for the record I tested without the leading exclamation mark and it still fails validation with the date ending in Z.

As a workaround, if you need to accept both formats you could try a custom rule:

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Str;
use Illuminate\Validation\Concerns\ValidatesAttributes;

class DateFormat implements Rule
{
    use ValidatesAttributes;

    private string $format;

    public function __construct(string $format)
    {
        $this->format = $format;
    }

    public function passes($attribute, $value)
    {
        if ($this->format === \DateTimeInterface::RFC3339_EXTENDED && Str::endsWith($value, 'Z')) {
            $value = Str::replaceLast('Z', '+00:00', $value);
        }

        return $this->validateDateFormat($attribute, $value, [$this->format]);
    }

    public function message()
    {
        return Str::replaceFirst(':format', $this->format, \trans('validation.date_format'));
    }
}

The use it as:

$array = [
    'mydate1' => '2018-01-29T20:36:01.123Z',
    'mydate2' => '2018-01-29T20:36:01.123+00:00',
];

$validator = \Illuminate\Support\Facades\Validator::make($array, [
    'mydate1' => ['required' , new \App\Rules\DateFormat(\DateTimeInterface::RFC3339_EXTENDED) ],
    'mydate2' => ['required' , new \App\Rules\DateFormat(\DateTimeInterface::RFC3339_EXTENDED) ],
])->validate();

return 'ok';

Hope this helps.

Thank you very much @rodrigopedra