diff --git a/README.md b/README.md
index 8b2f48c2..d6501e8e 100644
--- a/README.md
+++ b/README.md
@@ -11,11 +11,11 @@
-[![Static Badge](https://img.shields.io/badge/Rules-373-green?label=Total%20number%20of%20rules&labelColor=darkgreen&color=gray)](schema-examples/full.yml)
-[![Static Badge](https://img.shields.io/badge/Rules-159-green?label=Cell%20rules&labelColor=blue&color=gray)](src/Rules/Cell)
+[![Static Badge](https://img.shields.io/badge/Rules-379-green?label=Total%20number%20of%20rules&labelColor=darkgreen&color=gray)](schema-examples/full.yml)
+[![Static Badge](https://img.shields.io/badge/Rules-165-green?label=Cell%20rules&labelColor=blue&color=gray)](src/Rules/Cell)
[![Static Badge](https://img.shields.io/badge/Rules-206-green?label=Aggregate%20rules&labelColor=blue&color=gray)](src/Rules/Aggregate)
[![Static Badge](https://img.shields.io/badge/Rules-8-green?label=Extra%20checks&labelColor=blue&color=gray)](#extra-checks)
-[![Static Badge](https://img.shields.io/badge/Rules-31/54/9-green?label=Plan%20to%20add&labelColor=gray&color=gray)](tests/schemas/todo.yml)
+[![Static Badge](https://img.shields.io/badge/Rules-29/54/9-green?label=Plan%20to%20add&labelColor=gray&color=gray)](tests/schemas/todo.yml)
A console utility designed for validating CSV files against a strictly defined schema and validation rules outlined
@@ -419,6 +419,17 @@ columns:
date_interval_less: PT23H59M59S # 23 hours, 59 minutes, and 59 seconds
date_interval_max: P1Y # 1 year
+ # Check an arbitrary date in a CSV cell for age (years).
+ # Actually it calculates the difference between the date and the current date.
+ # Convenient to use for age restrictions based on birthday.
+ # See the description of `date_*` functions for details on date formats.
+ date_age_min: 1 # x >= 1
+ date_age_greater: 14 # x > 14
+ date_age_not: 18 # x != 18
+ date_age: 21 # x == 21
+ date_age_less: 99 # x < 99
+ date_age_max: 100 # x <= 100
+
# Specific formats
is_bool: true # Allow only boolean values "true" and "false", case-insensitive.
is_uuid: true # Validates whether the input is a valid UUID. It also supports validation of specific versions 1, 3, 4 and 5.
diff --git a/schema-examples/full.json b/schema-examples/full.json
index 2051f559..ba55b349 100644
--- a/schema-examples/full.json
+++ b/schema-examples/full.json
@@ -96,6 +96,13 @@
"date_interval_less" : "PT23H59M59S",
"date_interval_max" : "P1Y",
+ "date_age_min" : 1,
+ "date_age_greater" : 14,
+ "date_age_not" : 18,
+ "date_age" : 21,
+ "date_age_less" : 99,
+ "date_age_max" : 100,
+
"is_bool" : true,
"is_uuid" : true,
"is_slug" : true,
diff --git a/schema-examples/full.php b/schema-examples/full.php
index 5e4d1544..c69740d2 100644
--- a/schema-examples/full.php
+++ b/schema-examples/full.php
@@ -117,6 +117,13 @@
'date_interval_less' => 'PT23H59M59S',
'date_interval_max' => 'P1Y',
+ 'date_age_min' => 1,
+ 'date_age_greater' => 14,
+ 'date_age_not' => 18,
+ 'date_age' => 21,
+ 'date_age_less' => 99,
+ 'date_age_max' => 100,
+
'is_bool' => true,
'is_uuid' => true,
'is_slug' => true,
diff --git a/schema-examples/full.yml b/schema-examples/full.yml
index 2e1ab644..8b7b59ef 100644
--- a/schema-examples/full.yml
+++ b/schema-examples/full.yml
@@ -158,6 +158,17 @@ columns:
date_interval_less: PT23H59M59S # 23 hours, 59 minutes, and 59 seconds
date_interval_max: P1Y # 1 year
+ # Check an arbitrary date in a CSV cell for age (years).
+ # Actually it calculates the difference between the date and the current date.
+ # Convenient to use for age restrictions based on birthday.
+ # See the description of `date_*` functions for details on date formats.
+ date_age_min: 1 # x >= 1
+ date_age_greater: 14 # x > 14
+ date_age_not: 18 # x != 18
+ date_age: 21 # x == 21
+ date_age_less: 99 # x < 99
+ date_age_max: 100 # x <= 100
+
# Specific formats
is_bool: true # Allow only boolean values "true" and "false", case-insensitive.
is_uuid: true # Validates whether the input is a valid UUID. It also supports validation of specific versions 1, 3, 4 and 5.
diff --git a/schema-examples/full_clean.yml b/schema-examples/full_clean.yml
index cd73f8bd..90b00b7f 100644
--- a/schema-examples/full_clean.yml
+++ b/schema-examples/full_clean.yml
@@ -124,6 +124,13 @@ columns:
date_interval_less: PT23H59M59S
date_interval_max: P1Y
+ date_age_min: 1
+ date_age_greater: 14
+ date_age_not: 18
+ date_age: 21
+ date_age_less: 99
+ date_age_max: 100
+
is_bool: true
is_uuid: true
is_slug: true
diff --git a/src/Rules/Cell/ComboDateAge.php b/src/Rules/Cell/ComboDateAge.php
new file mode 100644
index 00000000..b672b569
--- /dev/null
+++ b/src/Rules/Cell/ComboDateAge.php
@@ -0,0 +1,85 @@
+ [1, 'x >= 1'],
+ self::GREATER => [14, 'x > 14'],
+ self::NOT => [18, 'x != 18'],
+ self::EQ => [21, 'x == 21'],
+ self::LESS => [99, 'x < 99'],
+ self::MAX => [100, 'x <= 100'],
+ ],
+ ];
+ }
+
+ protected function getActualCell(string $cellValue): float
+ {
+ try {
+ $years = self::calculateAge($cellValue);
+ } catch (\Exception) {
+ return self::INVALID_DATEINTERVAL_ACTUAL;
+ }
+
+ return $years;
+ }
+
+ protected function getExpected(): float
+ {
+ return $this->getOptionAsInt();
+ }
+
+ protected function getExpectedStr(): string
+ {
+ return "{$this->getOptionAsInt()} years";
+ }
+
+ protected function getCurrentStr(string $cellValue): string
+ {
+ try {
+ $years = self::calculateAge($cellValue);
+ } catch (\Exception $exception) {
+ return "{$exception->getMessage()}";
+ }
+
+ return "parsed as \"{$years}\" years";
+ }
+
+ private static function calculateAge(string $dateString): int
+ {
+ $birthDateTime = new \DateTimeImmutable($dateString);
+ $currentDateTime = new \DateTimeImmutable('now');
+
+ return $birthDateTime->diff($currentDateTime)->y; // Returns the total number of full years
+ }
+}
diff --git a/tests/Rules/Cell/ComboDateAgeTest.php b/tests/Rules/Cell/ComboDateAgeTest.php
new file mode 100644
index 00000000..22aeb0ff
--- /dev/null
+++ b/tests/Rules/Cell/ComboDateAgeTest.php
@@ -0,0 +1,74 @@
+create(0, Combo::EQ);
+
+ isSame('', $rule->test(''));
+ isSame('', $rule->test('now'));
+
+ isSame(
+ 'The age of the value "2020-10-02" is parsed as "3" years, ' .
+ 'which is not equal than the expected "0 years"',
+ $rule->test('2020-10-02'),
+ );
+
+ isSame(
+ 'The age of the value "qwerty" is ' .
+ 'Failed to parse time string (qwerty) at position 0 (q): ' .
+ 'The timezone could not be found in the database, ' .
+ 'which is not equal than the expected "0 years"',
+ $rule->test('qwerty', true),
+ );
+ }
+
+ public function testMin(): void
+ {
+ $rule = $this->create(21, Combo::MIN);
+
+ isSame('', $rule->test(''));
+ isSame('', $rule->test('+22 years'));
+ isSame('', $rule->test('2100-01-01'));
+ isSame('', $rule->test('2100-01'));
+
+ isSame(
+ 'The age of the value "2020-10-02" is parsed as "3" years, ' .
+ 'which is less than the expected "21 years"',
+ $rule->test('2020-10-02'),
+ );
+
+ isSame(
+ 'The age of the value "qwerty" is ' .
+ 'Failed to parse time string (qwerty) at position 0 (q): ' .
+ 'The timezone could not be found in the database, ' .
+ 'which is less than the expected "21 years"',
+ $rule->test('qwerty', true),
+ );
+ }
+}
diff --git a/tests/schemas/todo.yml b/tests/schemas/todo.yml
index 492e6338..3854c7b3 100644
--- a/tests/schemas/todo.yml
+++ b/tests/schemas/todo.yml
@@ -49,10 +49,6 @@ columns:
is_bool_value: true # https://github.com/Respect/Validation/blob/main/docs/rules/BoolVal.md
is_null: true # see empty_values
- # Dates
- age: 35
- dateperiod: 1
-
# Codes
subdivision_code: [ ] # https://github.com/Respect/Validation/blob/main/docs/rules/SubdivisionCode.md