From e24e977add08b2ea4679938d44805a58898c48a9 Mon Sep 17 00:00:00 2001 From: Andrew Coulton Date: Mon, 30 Apr 2018 11:11:35 +0100 Subject: [PATCH] Add StrictDate::on_or_after for date >= date ignoring time --- CHANGELOG.md | 4 ++ src/Validation/StrictDate.php | 51 +++++++++++++++++-- test/unit/Validation/StrictDateTest.php | 65 ++++++++++++++++++++++--- 3 files changed, 109 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc68592..fee6358 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ### Unreleased +### v0.1.4 (2018-04-30) + +* Add StrictDate::on_or_after for validating date >= date ignoring any time component + ### v0.1.3 (2018-02-22) * Extract the basic ValidNumber class for validating minimum diff --git a/src/Validation/StrictDate.php b/src/Validation/StrictDate.php index 1cd63aa..3621941 100644 --- a/src/Validation/StrictDate.php +++ b/src/Validation/StrictDate.php @@ -16,25 +16,65 @@ class StrictDate * Validate that value of one field is a date after the value of another * * @param \ArrayAccess $validation - * @param string $from_field - * @param string $to_field + * @param string $from_field + * @param string $to_field * * @return bool */ public static function date_after(\ArrayAccess $validation, $from_field, $to_field) + { + if ( ! $dates = static::get_valid_date_pair($validation, $from_field, $to_field)) { + // Return true if either value is empty or invalid, this will be picked up by other rules + return TRUE; + } + + list($from, $to) = $dates; + + return ($to > $from); + } + + /** + * Validate that value of one field is a date on or after value of another (>= ignoring time) + * + * @param \ArrayAccess $validation + * @param string $from_field + * @param string $to_field + * + * @return bool + */ + public static function date_on_or_after(\ArrayAccess $validation, $from_field, $to_field) + { + if ( ! $dates = static::get_valid_date_pair($validation, $from_field, $to_field)) { + // Return true if either value is empty or invalid, this will be picked up by other rules + return TRUE; + } + + list($from, $to) = $dates; + + return ($to->format('Y-m-d') >= $from->format('Y-m-d')); + } + + /** + * @param \ArrayAccess $validation + * @param string $from_field + * @param string $to_field + * + * @return \DateTimeImmutable[] + */ + protected static function get_valid_date_pair(\ArrayAccess $validation, $from_field, $to_field) { $from = $validation[$from_field]; $to = $validation[$to_field]; // Return true if either value is empty or invalid, this will be picked up by other rules if ($from instanceof InvalidUserDateTime OR ! $from instanceof \DateTimeImmutable) { - return TRUE; + return NULL; } if ($to instanceof InvalidUserDateTime OR ! $to instanceof \DateTimeImmutable) { - return TRUE; + return NULL; } - return ($to > $from); + return [$from, $to]; } /** @@ -107,6 +147,7 @@ public static function rule($rulename) { switch ($rulename) { case 'date_after': + case 'date_on_or_after': case 'date_immutable': case 'datetime_immutable': case 'iso_date': diff --git a/test/unit/Validation/StrictDateTest.php b/test/unit/Validation/StrictDateTest.php index 3f63e6f..d255487 100644 --- a/test/unit/Validation/StrictDateTest.php +++ b/test/unit/Validation/StrictDateTest.php @@ -71,7 +71,7 @@ public function test_it_validates_date_immutable_instance($value, $expect) $this->assertSame($expect, StrictDate::date_immutable($value)); } - public function provider_date_after_date() + public function provider_date_before_after_invalid_inputs() { return [ [['from' => NULL, 'to' => NULL], 'from', 'to', TRUE], @@ -89,6 +89,28 @@ public function provider_date_after_date() 'to', TRUE ], + ]; + } + + /** + * @dataProvider provider_date_before_after_invalid_inputs + */ + public function test_date_compare_funcs_validate_invalid_input($data, $from_field, $to_field) + { + // This is so that an invalid date just says "invalid date" rather than also "must be after" + $data = new \ArrayObject($data); + $this->assertTrue(StrictDate::date_after($data, $from_field, $to_field), 'date_after'); + $this->assertTrue( + StrictDate::date_on_or_after($data, $from_field, $to_field), + 'date_on_or_after' + ); + } + + public function provider_date_after_date() + { + return [ + // Invalid inputs all true as they should be caught by other rules + // Simple > the first one [ ['a' => new \DateTimeImmutable, 'b' => new \DateTimeImmutable('-5 mins')], 'a', @@ -105,16 +127,47 @@ public function provider_date_after_date() } /** - * @dataProvider provider_date_after_date + * @testWith ["", "-5 mins", false] + * ["-5 mins", "", true] */ - public function test_it_validates_date_after_date($data, $from_field, $to_field, $expect) + public function test_it_validates_date_after_date($from, $to, $expect) { $this->assertSame( $expect, StrictDate::date_after( - new \ArrayObject($data), - $from_field, - $to_field + new \ArrayObject( + [ + 'from' => new \DateTimeImmutable($from), + 'to' => new \DateTimeImmutable($to) + ] + ), + 'from', + 'to' + ) + ); + } + + /** + * @testWith ["2017-01-05 00:00:00", "2017-01-04 23:59:59", false] + * ["2017-01-04 10:00:00", "2017-01-04 23:59:59", true] + * ["2017-01-04 00:00:00", "2017-01-04 00:00:00", true] + * ["2017-01-04 10:00:00", "2017-01-04 08:00:00", true] + * ["2017-05-06 10:00:00", "2018-12-30 00:00:00", true] + * + */ + public function test_it_validates_date_on_or_after_date($from, $to, $expect) + { + $this->assertSame( + $expect, + StrictDate::date_on_or_after( + new \ArrayObject( + [ + 'from' => new \DateTimeImmutable($from), + 'to' => new \DateTimeImmutable($to) + ] + ), + 'from', + 'to' ) ); }