Skip to content

Commit 1049c03

Browse files
[11.x] Add Customizable Date Validation Rule with Flexible Date Constraints (#53465)
* Add Flexible Date Validation Options: After, Before, and Custom Formats * Add tests * Formatting * Renaming * Fix separator * Added between date * Formatting * Enabled seamless handling of `Carbon` and `DateTime` instances. * Add tests * Add `afterOrEqualToday` and `beforeOrEqualToday` and `betweenOrEqual` * Added tests * Formatting * Fix typo * formatting --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent 94e6258 commit 1049c03

File tree

3 files changed

+284
-0
lines changed

3 files changed

+284
-0
lines changed

src/Illuminate/Validation/Rule.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Illuminate\Support\Traits\Macroable;
77
use Illuminate\Validation\Rules\ArrayRule;
88
use Illuminate\Validation\Rules\Can;
9+
use Illuminate\Validation\Rules\Date;
910
use Illuminate\Validation\Rules\Dimensions;
1011
use Illuminate\Validation\Rules\Email;
1112
use Illuminate\Validation\Rules\Enum;
@@ -170,6 +171,16 @@ public static function prohibitedIf($callback)
170171
return new ProhibitedIf($callback);
171172
}
172173

174+
/**
175+
* Get a date rule builder instance.
176+
*
177+
* @return \Illuminate\Validation\Rules\Date
178+
*/
179+
public static function date()
180+
{
181+
return new Date;
182+
}
183+
173184
/**
174185
* Get an email rule builder instance.
175186
*
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<?php
2+
3+
namespace Illuminate\Validation\Rules;
4+
5+
use DateTimeInterface;
6+
use Illuminate\Support\Arr;
7+
use Illuminate\Support\Traits\Conditionable;
8+
use Illuminate\Support\Traits\Macroable;
9+
use Stringable;
10+
11+
class Date implements Stringable
12+
{
13+
use Conditionable, Macroable;
14+
15+
/**
16+
* The constraints for the date rule.
17+
*/
18+
protected array $constraints = ['date'];
19+
20+
/**
21+
* Ensure the date has the given format.
22+
*/
23+
public function format(string $format): static
24+
{
25+
return $this->addRule('date_format:'.$format);
26+
}
27+
28+
/**
29+
* Ensure the date is before today.
30+
*/
31+
public function beforeToday(): static
32+
{
33+
return $this->before('today');
34+
}
35+
36+
/**
37+
* Ensure the date is after today.
38+
*/
39+
public function afterToday(): static
40+
{
41+
return $this->after('today');
42+
}
43+
44+
/**
45+
* Ensure the date is before or equal to today.
46+
*/
47+
public function todayOrBefore(): static
48+
{
49+
return $this->beforeOrEqual('today');
50+
}
51+
52+
/**
53+
* Ensure the date is after or equal to today.
54+
*/
55+
public function todayOrAfter(): static
56+
{
57+
return $this->afterOrEqual('today');
58+
}
59+
60+
/**
61+
* Ensure the date is before the given date or date field.
62+
*/
63+
public function before(DateTimeInterface|string $date): static
64+
{
65+
return $this->addRule('before:'.$this->formatDate($date));
66+
}
67+
68+
/**
69+
* Ensure the date is after the given date or date field.
70+
*/
71+
public function after(DateTimeInterface|string $date): static
72+
{
73+
return $this->addRule('after:'.$this->formatDate($date));
74+
}
75+
76+
/**
77+
* Ensure the date is on or before the specified date or date field.
78+
*/
79+
public function beforeOrEqual(DateTimeInterface|string $date): static
80+
{
81+
return $this->addRule('before_or_equal:'.$this->formatDate($date));
82+
}
83+
84+
/**
85+
* Ensure the date is on or after the given date or date field.
86+
*/
87+
public function afterOrEqual(DateTimeInterface|string $date): static
88+
{
89+
return $this->addRule('after_or_equal:'.$this->formatDate($date));
90+
}
91+
92+
/**
93+
* Ensure the date is between two dates or date fields.
94+
*/
95+
public function between(DateTimeInterface|string $from, DateTimeInterface|string $to): static
96+
{
97+
return $this->after($from)->before($to);
98+
}
99+
100+
/**
101+
* Ensure the date is between or equal to two dates or date fields.
102+
*/
103+
public function betweenOrEqual(DateTimeInterface|string $from, DateTimeInterface|string $to): static
104+
{
105+
return $this->afterOrEqual($from)->beforeOrEqual($to);
106+
}
107+
108+
/**
109+
* Add custom rules to the validation rules array.
110+
*/
111+
protected function addRule(array|string $rules): static
112+
{
113+
$this->constraints = array_merge($this->constraints, Arr::wrap($rules));
114+
115+
return $this;
116+
}
117+
118+
/**
119+
* Format the date for the validation rule.
120+
*/
121+
protected function formatDate(DateTimeInterface|string $date): string
122+
{
123+
return $date instanceof DateTimeInterface
124+
? $date->format('Y-m-d')
125+
: $date;
126+
}
127+
128+
/**
129+
* Convert the rule to a validation string.
130+
*/
131+
public function __toString(): string
132+
{
133+
return implode('|', $this->constraints);
134+
}
135+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<?php
2+
3+
namespace Tests\Unit\Rules;
4+
5+
use Illuminate\Support\Carbon;
6+
use Illuminate\Translation\ArrayLoader;
7+
use Illuminate\Translation\Translator;
8+
use Illuminate\Validation\Rule;
9+
use Illuminate\Validation\Rules\Date;
10+
use Illuminate\Validation\Validator;
11+
use PHPUnit\Framework\TestCase;
12+
13+
class ValidationDateRuleTest extends TestCase
14+
{
15+
public function testDefaultDateRule()
16+
{
17+
$rule = Rule::date();
18+
$this->assertEquals('date', (string) $rule);
19+
20+
$rule = new Date;
21+
$this->assertSame('date', (string) $rule);
22+
}
23+
24+
public function testDateFormatRule()
25+
{
26+
$rule = Rule::date()->format('d/m/Y');
27+
$this->assertEquals('date|date_format:d/m/Y', (string) $rule);
28+
}
29+
30+
public function testAfterTodayRule()
31+
{
32+
$rule = Rule::date()->afterToday();
33+
$this->assertEquals('date|after:today', (string) $rule);
34+
35+
$rule = Rule::date()->todayOrAfter();
36+
$this->assertEquals('date|after_or_equal:today', (string) $rule);
37+
}
38+
39+
public function testBeforeTodayRule()
40+
{
41+
$rule = Rule::date()->beforeToday();
42+
$this->assertEquals('date|before:today', (string) $rule);
43+
44+
$rule = Rule::date()->todayOrBefore();
45+
$this->assertEquals('date|before_or_equal:today', (string) $rule);
46+
}
47+
48+
public function testAfterSpecificDateRule()
49+
{
50+
$rule = Rule::date()->after(Carbon::parse('2024-01-01'));
51+
$this->assertEquals('date|after:2024-01-01', (string) $rule);
52+
}
53+
54+
public function testBeforeSpecificDateRule()
55+
{
56+
$rule = Rule::date()->before(Carbon::parse('2024-01-01'));
57+
$this->assertEquals('date|before:2024-01-01', (string) $rule);
58+
}
59+
60+
public function testAfterOrEqualSpecificDateRule()
61+
{
62+
$rule = Rule::date()->afterOrEqual(Carbon::parse('2024-01-01'));
63+
$this->assertEquals('date|after_or_equal:2024-01-01', (string) $rule);
64+
}
65+
66+
public function testBeforeOrEqualSpecificDateRule()
67+
{
68+
$rule = Rule::date()->beforeOrEqual(Carbon::parse('2024-01-01'));
69+
$this->assertEquals('date|before_or_equal:2024-01-01', (string) $rule);
70+
}
71+
72+
public function testBetweenDatesRule()
73+
{
74+
$rule = Rule::date()->between(Carbon::parse('2024-01-01'), Carbon::parse('2024-02-01'));
75+
$this->assertEquals('date|after:2024-01-01|before:2024-02-01', (string) $rule);
76+
}
77+
78+
public function testBetweenOrEqualDatesRule()
79+
{
80+
$rule = Rule::date()->betweenOrEqual('2024-01-01', '2024-02-01');
81+
$this->assertEquals('date|after_or_equal:2024-01-01|before_or_equal:2024-02-01', (string) $rule);
82+
}
83+
84+
public function testChainedRules()
85+
{
86+
$rule = Rule::date('Y-m-d H:i:s')
87+
->format('Y-m-d')
88+
->after('2024-01-01 00:00:00')
89+
->before('2025-01-01 00:00:00');
90+
$this->assertEquals('date|date_format:Y-m-d|after:2024-01-01 00:00:00|before:2025-01-01 00:00:00', (string) $rule);
91+
92+
$rule = Rule::date()
93+
->format('Y-m-d')
94+
->when(true, function ($rule) {
95+
$rule->after('2024-01-01');
96+
})
97+
->unless(true, function ($rule) {
98+
$rule->before('2025-01-01');
99+
});
100+
$this->assertSame('date|date_format:Y-m-d|after:2024-01-01', (string) $rule);
101+
}
102+
103+
public function testDateValidation()
104+
{
105+
$trans = new Translator(new ArrayLoader, 'en');
106+
107+
$rule = Rule::date();
108+
109+
$validator = new Validator(
110+
$trans,
111+
['date' => 'not a date'],
112+
['date' => $rule]
113+
);
114+
115+
$this->assertSame(
116+
$trans->get('validation.date'),
117+
$validator->errors()->first('date')
118+
);
119+
120+
$validator = new Validator(
121+
$trans,
122+
['date' => '2024-01-01'],
123+
['date' => $rule]
124+
);
125+
126+
$this->assertEmpty($validator->errors()->first('date'));
127+
128+
$rule = Rule::date()->between('2024-01-01', '2025-01-01');
129+
130+
$validator = new Validator(
131+
$trans,
132+
['date' => '2024-02-01'],
133+
['date' => (string) $rule]
134+
);
135+
136+
$this->assertEmpty($validator->errors()->first('date'));
137+
}
138+
}

0 commit comments

Comments
 (0)