date

Validates that a field is a valid, non-relative date according to PHP's strtotime function.

Signature
date

Errors

Nothing on its own — the failure is recorded as a message, and Illuminate\Validation\ValidationException is thrown only when validate() is called on a failing validator.

Changelog

  • PHP 12.xDate-based rules may be composed with the fluent builder Rule::date(), for example Rule::date()->format('Y-m-d') or Rule::date()->afterToday().

date checks that a field is a valid, non-relative date according to PHP's strtotime(). Absolute values such as 2026-03-01, 01/03/2026 and March 1, 2026 pass. Relative expressions that strtotime() would otherwise happily parse — tomorrow, +1 week, next friday — do not, and neither does an impossible calendar date such as 2026-02-30.

When to reach for it

  • A free-form date field where you are willing to accept several human formats.
  • As the prerequisite for the comparison rules: after, before, after_or_equal and date_equals all read the value through strtotime(), so pair them with date.

What trips people up

Accepting several formats is exactly the problem when the input shape is known. 01/03/2026 is ambiguous, and strtotime() resolves slash-separated dates as US month-first. If your form or API sends one specific format, use date_format:Y-m-d instead. The documentation is explicit that you should use either date or date_format for a field, never both — combining them produces contradictory results because date admits shapes date_format rejects.

Note also that the comparison rules accept relative strings as their argument (after:tomorrow is fine); it is only the submitted value that must be absolute.

Example

Example · php
<?php

use Illuminate\Support\Facades\Validator;

$validator = Validator::make([
    'published_at' => '2026-03-01',
    'expires_at'   => 'tomorrow',
    'closed_at'    => '2026-02-30',
], [
    'published_at' => ['required', 'date'],
    'expires_at'   => ['required', 'date'],
    'closed_at'    => ['required', 'date'],
]);

print_r($validator->errors()->toArray());

// Array
// (
//     [expires_at] => Array
//         (
//             [0] => The expires at field must be a valid date.
//         )
//     [closed_at] => Array
//         (
//             [0] => The closed at field must be a valid date.
//         )
// )

When to use it

  • Validate a date of birth typed into a free-form field, paired with before:today.
  • Check a campaign's start and end dates where end must come after start.
  • Accept a published_at value from a JSON API before casting it to a Carbon instance.
  • Bound a report's date range with after_or_equal and before_or_equal.

Frequently asked questions

Why does the date rule reject "tomorrow"?

Because the rule requires a valid, non-relative date — relative strtotime expressions such as tomorrow, +1 week and next friday all fail. Relative strings are still allowed as the argument of comparison rules, so after:tomorrow works fine.

Should I use date or date_format?

Use date_format when you know the exact shape the client sends, and date only when you deliberately accept several human formats. The Laravel documentation says to use one or the other for a field, never both.

Does the date rule return a Carbon instance?

No. The validated value is still the original string; convert it yourself with Carbon::createFromFormat() or a date cast on the Eloquent model.

More examples

A date range in a form request

after:starts_at compares against another field rather than a literal, which is how you express "the end must follow the start" without a custom rule. after_or_equal:today shows that the rule argument may be relative even though the submitted value may not be. Rule::date() is the fluent equivalent for these date rules.

Example · php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class StoreCampaignRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'starts_at' => ['required', 'date', 'after_or_equal:today'],
            'ends_at'   => ['required', 'date', 'after:starts_at'],

            // Same idea through the fluent builder.
            'reviewed_on' => ['nullable', Rule::date()->beforeToday()],
        ];
    }
}

date versus date_format

date accepts anything strtotime() resolves to an absolute calendar date, which includes ambiguous and loosely padded forms. When the client always sends one shape, date_format:Y-m-d is the rule that actually enforces it — and per the documentation you pick one of the two, not both.

Example · php
<?php

use Illuminate\Support\Facades\Validator;

$values = ['2026-03-01', '01/03/2026', 'March 1, 2026', '2026-3-1', 'tomorrow'];

foreach ($values as $value) {
    $loose  = Validator::make(['d' => $value], ['d' => 'date'])->passes();
    $strict = Validator::make(['d' => $value], ['d' => 'date_format:Y-m-d'])->passes();

    echo str_pad($value, 16),
         'date: ', $loose ? 'pass  ' : 'fail  ',
         'date_format: ', $strict ? "pass\n" : "fail\n";
}

// 2026-03-01      date: pass  date_format: pass
// 01/03/2026      date: pass  date_format: fail
// March 1, 2026   date: pass  date_format: fail
// 2026-3-1        date: pass  date_format: fail
// tomorrow        date: fail  date_format: fail

Casting after validation

Validation confirms the shape; it never hands back a Carbon instance. Parsing with createFromFormat rather than Carbon::parse keeps the interpretation identical to the rule that approved it, and the explicit startOfDay/endOfDay makes an inclusive range instead of silently ending at midnight.

Example · php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Carbon;

class ReportController extends Controller
{
    public function show(Request $request)
    {
        $data = $request->validate([
            'from' => ['required', 'date_format:Y-m-d'],
            'to'   => ['required', 'date_format:Y-m-d', 'after_or_equal:from'],
        ]);

        $from = Carbon::createFromFormat('Y-m-d', $data['from'])->startOfDay();
        $to   = Carbon::createFromFormat('Y-m-d', $data['to'])->endOfDay();

        echo $from->toDateTimeString(), ' .. ', $to->toDateTimeString(), "\n";
        // 2026-03-01 00:00:00 .. 2026-03-31 23:59:59
    }
}

Discussion

  • Be the first to comment on this lesson.