Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
vlauciani opened this issue Nov 27, 2020 · 4 comments
Closed

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

vlauciani opened this issue Nov 27, 2020 · 4 comments

Comments

@vlauciani
Copy link

  • 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.

@driesvints
Copy link
Member

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

@vlauciani
Copy link
Author

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

@rodrigopedra
Copy link
Contributor

@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.

@vlauciani
Copy link
Author

Thank you very much @rodrigopedra

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants