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

[11.x] Add Customizable Date Validation Rule with Flexible Date Constraints #53465

Merged
merged 15 commits into from
Jan 23, 2025
Merged
11 changes: 11 additions & 0 deletions src/Illuminate/Validation/Rule.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Illuminate\Support\Traits\Macroable;
use Illuminate\Validation\Rules\ArrayRule;
use Illuminate\Validation\Rules\Can;
use Illuminate\Validation\Rules\Date;
use Illuminate\Validation\Rules\Dimensions;
use Illuminate\Validation\Rules\Email;
use Illuminate\Validation\Rules\Enum;
Expand Down Expand Up @@ -170,6 +171,16 @@ public static function prohibitedIf($callback)
return new ProhibitedIf($callback);
}

/**
* Get a date rule builder instance.
*
* @return \Illuminate\Validation\Rules\Date
*/
public static function date()
{
return new Date;
}

/**
* Get an email rule builder instance.
*
Expand Down
135 changes: 135 additions & 0 deletions src/Illuminate/Validation/Rules/Date.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php

namespace Illuminate\Validation\Rules;

use DateTimeInterface;
use Illuminate\Support\Arr;
use Illuminate\Support\Traits\Conditionable;
use Illuminate\Support\Traits\Macroable;
use Stringable;

class Date implements Stringable
{
use Conditionable, Macroable;

/**
* The constraints for the date rule.
*/
protected array $constraints = ['date'];

/**
* Ensure the date has the given format.
*/
public function format(string $format): static
{
return $this->addRule('date_format:'.$format);
}

/**
* Ensure the date is before today.
*/
public function beforeToday(): static
{
return $this->before('today');
}

/**
* Ensure the date is after today.
*/
public function afterToday(): static
{
return $this->after('today');
}

/**
* Ensure the date is before or equal to today.
*/
public function todayOrBefore(): static
{
return $this->beforeOrEqual('today');
}

/**
* Ensure the date is after or equal to today.
*/
public function todayOrAfter(): static
{
return $this->afterOrEqual('today');
}

/**
* Ensure the date is before the given date or date field.
*/
public function before(DateTimeInterface|string $date): static
{
return $this->addRule('before:'.$this->formatDate($date));
}

/**
* Ensure the date is after the given date or date field.
*/
public function after(DateTimeInterface|string $date): static
{
return $this->addRule('after:'.$this->formatDate($date));
}

/**
* Ensure the date is on or before the specified date or date field.
*/
public function beforeOrEqual(DateTimeInterface|string $date): static
{
return $this->addRule('before_or_equal:'.$this->formatDate($date));
}

/**
* Ensure the date is on or after the given date or date field.
*/
public function afterOrEqual(DateTimeInterface|string $date): static
{
return $this->addRule('after_or_equal:'.$this->formatDate($date));
}

/**
* Ensure the date is between two dates or date fields.
*/
public function between(DateTimeInterface|string $from, DateTimeInterface|string $to): static
{
return $this->after($from)->before($to);
}

/**
* Ensure the date is between or equal to two dates or date fields.
*/
public function betweenOrEqual(DateTimeInterface|string $from, DateTimeInterface|string $to): static
{
return $this->afterOrEqual($from)->beforeOrEqual($to);
}

/**
* Add custom rules to the validation rules array.
*/
protected function addRule(array|string $rules): static
{
$this->constraints = array_merge($this->constraints, Arr::wrap($rules));

return $this;
}

/**
* Format the date for the validation rule.
*/
protected function formatDate(DateTimeInterface|string $date): string
{
return $date instanceof DateTimeInterface
? $date->format('Y-m-d')
: $date;
}

/**
* Convert the rule to a validation string.
*/
public function __toString(): string
{
return implode('|', $this->constraints);
}
}
138 changes: 138 additions & 0 deletions tests/Validation/ValidationDateRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?php

namespace Tests\Unit\Rules;

use Illuminate\Support\Carbon;
use Illuminate\Translation\ArrayLoader;
use Illuminate\Translation\Translator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Date;
use Illuminate\Validation\Validator;
use PHPUnit\Framework\TestCase;

class ValidationDateRuleTest extends TestCase
{
public function testDefaultDateRule()
{
$rule = Rule::date();
$this->assertEquals('date', (string) $rule);

$rule = new Date;
$this->assertSame('date', (string) $rule);
}

public function testDateFormatRule()
{
$rule = Rule::date()->format('d/m/Y');
$this->assertEquals('date|date_format:d/m/Y', (string) $rule);
}

public function testAfterTodayRule()
{
$rule = Rule::date()->afterToday();
$this->assertEquals('date|after:today', (string) $rule);

$rule = Rule::date()->todayOrAfter();
$this->assertEquals('date|after_or_equal:today', (string) $rule);
}

public function testBeforeTodayRule()
{
$rule = Rule::date()->beforeToday();
$this->assertEquals('date|before:today', (string) $rule);

$rule = Rule::date()->todayOrBefore();
$this->assertEquals('date|before_or_equal:today', (string) $rule);
}

public function testAfterSpecificDateRule()
{
$rule = Rule::date()->after(Carbon::parse('2024-01-01'));
$this->assertEquals('date|after:2024-01-01', (string) $rule);
}

public function testBeforeSpecificDateRule()
{
$rule = Rule::date()->before(Carbon::parse('2024-01-01'));
$this->assertEquals('date|before:2024-01-01', (string) $rule);
}

public function testAfterOrEqualSpecificDateRule()
{
$rule = Rule::date()->afterOrEqual(Carbon::parse('2024-01-01'));
$this->assertEquals('date|after_or_equal:2024-01-01', (string) $rule);
}

public function testBeforeOrEqualSpecificDateRule()
{
$rule = Rule::date()->beforeOrEqual(Carbon::parse('2024-01-01'));
$this->assertEquals('date|before_or_equal:2024-01-01', (string) $rule);
}

public function testBetweenDatesRule()
{
$rule = Rule::date()->between(Carbon::parse('2024-01-01'), Carbon::parse('2024-02-01'));
$this->assertEquals('date|after:2024-01-01|before:2024-02-01', (string) $rule);
}

public function testBetweenOrEqualDatesRule()
{
$rule = Rule::date()->betweenOrEqual('2024-01-01', '2024-02-01');
$this->assertEquals('date|after_or_equal:2024-01-01|before_or_equal:2024-02-01', (string) $rule);
}

public function testChainedRules()
{
$rule = Rule::date('Y-m-d H:i:s')
->format('Y-m-d')
->after('2024-01-01 00:00:00')
->before('2025-01-01 00:00:00');
$this->assertEquals('date|date_format:Y-m-d|after:2024-01-01 00:00:00|before:2025-01-01 00:00:00', (string) $rule);

$rule = Rule::date()
->format('Y-m-d')
->when(true, function ($rule) {
$rule->after('2024-01-01');
})
->unless(true, function ($rule) {
$rule->before('2025-01-01');
});
$this->assertSame('date|date_format:Y-m-d|after:2024-01-01', (string) $rule);
}

public function testDateValidation()
{
$trans = new Translator(new ArrayLoader, 'en');

$rule = Rule::date();

$validator = new Validator(
$trans,
['date' => 'not a date'],
['date' => $rule]
);

$this->assertSame(
$trans->get('validation.date'),
$validator->errors()->first('date')
);

$validator = new Validator(
$trans,
['date' => '2024-01-01'],
['date' => $rule]
);

$this->assertEmpty($validator->errors()->first('date'));

$rule = Rule::date()->between('2024-01-01', '2025-01-01');

$validator = new Validator(
$trans,
['date' => '2024-02-01'],
['date' => (string) $rule]
);

$this->assertEmpty($validator->errors()->first('date'));
}
}