Browse Source

Merge pull request #13131 from montreux-oberland-bernois/issue-13088

#13088 - Validation::datetime
Mark Story 6 years ago
parent
commit
b92c010607
2 changed files with 90 additions and 1 deletions
  1. 40 1
      src/Validation/Validation.php
  2. 50 0
      tests/TestCase/Validation/ValidationTest.php

+ 40 - 1
src/Validation/Validation.php

@@ -77,6 +77,11 @@ class Validation
     const COMPARE_LESS_OR_EQUAL = '<=';
 
     /**
+     * Datetime ISO8601 format
+     */
+    const DATETIME_ISO8601 = 'iso8601';
+
+    /**
      * Some complex patterns needed in multiple places
      *
      * @var array
@@ -563,6 +568,7 @@ class Validation
      *
      * @param string|\DateTimeInterface $check Value to check
      * @param string|array $dateFormat Format of the date part. See Validation::date() for more information.
+     *      Or `Validation::DATETIME_ISO8601` to valid an ISO8601 datetime value
      * @param string|null $regex Regex for the date part. If a custom regular expression is used this is the only validation that will occur.
      * @return bool True if the value is valid, false otherwise
      * @see \Cake\Validation\Validation::date()
@@ -576,15 +582,24 @@ class Validation
         if (is_object($check)) {
             return false;
         }
+        if ($dateFormat === static::DATETIME_ISO8601 && !static::iso8601($check)) {
+            return false;
+        }
+
         $valid = false;
         if (is_array($check)) {
             $check = static::_getDateString($check);
             $dateFormat = 'ymd';
         }
-        $parts = explode(' ', $check);
+        $parts = preg_split("/[\sT]+/", $check);
         if (!empty($parts) && count($parts) > 1) {
             $date = rtrim(array_shift($parts), ',');
             $time = implode(' ', $parts);
+            if ($dateFormat === static::DATETIME_ISO8601) {
+                $dateFormat = 'ymd';
+                $time = preg_split("/[TZ\-\+\.]/", $time);
+                $time = array_shift($time);
+            }
             $valid = static::date($date, $dateFormat, $regex) && static::time($time);
         }
 
@@ -592,6 +607,30 @@ class Validation
     }
 
     /**
+     * Validates an iso8601 datetime format
+     * ISO8601 recognize datetime like 2019 as a valid date. To validate and check date integrity, use @see \Cake\Validation\Validation::datetime()
+     *
+     * @param string|\DateTimeInterface $check Value to check
+     *
+     * @return bool True if the value is valid, false otherwise
+     *
+     * @see Regex credits: https://www.myintervals.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/
+     */
+    public static function iso8601($check)
+    {
+        if ($check instanceof DateTimeInterface) {
+            return true;
+        }
+        if (is_object($check)) {
+            return false;
+        }
+
+        $regex = '/^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/';
+
+        return static::_check($check, $regex);
+    }
+
+    /**
      * Time validation, determines if the string passed is a valid time.
      * Validates time as 24hr (HH:MM) or am/pm ([H]H:MM[a|p]m)
      * Does not allow/validate seconds.

+ 50 - 0
tests/TestCase/Validation/ValidationTest.php

@@ -1590,6 +1590,56 @@ class ValidationTest extends TestCase
     }
 
     /**
+     * Tests that it is possible to pass a date with a T separator
+     *
+     * @return void
+     */
+    public function testDateTimeISO()
+    {
+        $this->assertTrue(Validation::dateTime('2007/10/04T01:50'));
+        $this->assertTrue(Validation::dateTime('2017/12/04T15:38'));
+        $this->assertTrue(Validation::dateTime('04.12.2017T15:38', ['dmy']));
+        $this->assertTrue(Validation::dateTime('24-02-2019T2:38am', ['dmy']));
+        $this->assertFalse(Validation::dateTime('2007/10/04T1:50'));
+        $this->assertFalse(Validation::dateTime('2007/10/04T58:38'));
+    }
+
+    /**
+     * Tests that it is possible to pass an ISO8601 value
+     *
+     * @return void
+     *
+     * @see Validation tests values credits: https://www.myintervals.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/
+     */
+    public function testDateTimeISO8601()
+    {
+        // Valid ISO8601
+        $this->assertTrue(Validation::iso8601('2007'));
+        $this->assertTrue(Validation::iso8601('2007-12'));
+        $this->assertTrue(Validation::iso8601('2009W511'));
+        $this->assertTrue(Validation::iso8601('2007-12-26'));
+        $this->assertTrue(Validation::iso8601('2007-12-26T15:20+02:00'));
+        $this->assertTrue(Validation::iso8601('2007-12-26T15:20:39+02:00'));
+        $this->assertTrue(Validation::iso8601('2007-12-26T15:20:39.59+02:00'));
+        // Invalid ISO8601
+        $this->assertFalse(Validation::iso8601('2009-'));
+        $this->assertFalse(Validation::iso8601('2009M511'));
+        $this->assertFalse(Validation::iso8601('2009-05-19T14a39r'));
+        $this->assertFalse(Validation::iso8601('2010-02-18T16:23.33.600'));
+        // Valid ISO8601 but incomplete date
+        $this->assertFalse(Validation::datetime('2007', 'iso8601'));
+        $this->assertFalse(Validation::datetime('2007-12', 'iso8601'));
+        $this->assertFalse(Validation::datetime('2009W511', 'iso8601'));
+        $this->assertFalse(Validation::datetime('2007-12-26', 'iso8601'));
+        // Valid ISO8601 and complete date and time
+        $this->assertTrue(Validation::datetime('2007-12-26T15:20+02:00', 'iso8601'));
+        $this->assertTrue(Validation::datetime('2007-12-26T15:20:39+02:00', 'iso8601'));
+        $this->assertTrue(Validation::datetime('2007-12-26T15:20:39.59+02:00', 'iso8601'));
+        // Valid ISO8601 and complete date and time BUT Weekdays are not validated by Validation::date()
+        $this->assertFalse(Validation::datetime('2009-W21-2T01:22', 'iso8601'));
+    }
+
+    /**
      * Test localizedTime
      *
      * @return void