From 9761934727f2f874eade0a80dcb54dcde7689b1d Mon Sep 17 00:00:00 2001 From: eileen Date: Tue, 2 Jan 2024 14:06:13 +1300 Subject: [PATCH] Fix time validation for iso date format As this is a very simple format strtotime will get it right so we just fix our validation & simplify --- CRM/Utils/Date.php | 48 +++++++++++++--------------- tests/phpunit/CRM/Utils/DateTest.php | 7 ++-- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/CRM/Utils/Date.php b/CRM/Utils/Date.php index 8207fa46a3..2ed91f5305 100644 --- a/CRM/Utils/Date.php +++ b/CRM/Utils/Date.php @@ -599,7 +599,7 @@ class CRM_Utils_Date { $value = ''; if (!empty($params[$dateParam])) { // suppress hh:mm or hh:mm:ss if it exists CRM-7957 - $value = preg_replace("/(\s(([01]\d)|[2][0-3])(:([0-5]\d)){1,2})$/", "", $params[$dateParam]); + $value = preg_replace(self::getTimeRegex(), "", $params[$dateParam]); } if (!self::validateDateInput($params[$dateParam] ?? '', $dateType)) { return FALSE; @@ -745,9 +745,11 @@ class CRM_Utils_Date { protected static function validateDateInput(string $inputValue, int $dateType = self::DATE_yyyy_mm_dd): bool { // suppress hh:mm or hh:mm:ss if it exists CRM-7957 // @todo - fix regex instead. - $inputValue = preg_replace("/(\s(([01]\d)|[2][0-3])(:([0-5]\d)){1,2})$/", "", $inputValue); + $inputValue = preg_replace(self::getTimeRegex(), "", $inputValue); switch ($dateType) { case self::DATE_yyyy_mm_dd: + // 4 numbers separated by - followed by 1-2 numbers, separated by - + // followed by 1-2 numbers with optional time string. return preg_match('/^\d\d\d\d-?(\d|\d\d)-?(\d|\d\d)$/', $inputValue); case self::DATE_mm_dd_yy: @@ -768,6 +770,17 @@ class CRM_Utils_Date { return FALSE; } + /** + * Get the regex to extract the time portion. + * + * @internal + * + * @return string + */ + protected static function getTimeRegex(): string { + return "/(\s(([01]*\d)|[2][0-3])(:([0-5]\d)){1,2})$/"; + } + /** * Translate a TTL to a concrete expiration time. * @@ -2173,38 +2186,21 @@ class CRM_Utils_Date { * * @return null|string */ - public static function formatDate($date, int $dateType = self::DATE_yyyy_mm_dd) { - if (empty($date)) { + public static function formatDate($date, int $dateType = self::DATE_yyyy_mm_dd): ?string { + // This empty check is never hit in practice - hence it is ok to treat it the same as bad data. + if (empty($date) || !self::validateDateInput($date, $dateType)) { return NULL; } - - // 1. first convert date to default format. - // 2. append time to default formatted date (might be removed during format) - // 3. validate date / date time. - // 4. If date and time then convert to default date time format. + if ($dateType === self::DATE_yyyy_mm_dd) { + $timestamp = strtotime($date); + return $timestamp ? date('YmdHis', $timestamp) : NULL; + } $dateKey = 'date'; $dateParams = [$dateKey => $date]; if (CRM_Utils_Date::convertToDefaultDate($dateParams, $dateType, $dateKey)) { $dateVal = $dateParams[$dateKey]; - if ($dateType === self::DATE_yyyy_mm_dd) { - $matches = []; - // The seconds part of this regex is not quite right - but it does succeed - // in clarifying whether there is a time component or not - which is all it is meant - // to do. - if (preg_match('/(\s(([01]\d)|[2][0-3]):([0-5]\d):?[0-5]?\d?)$/', $date, $matches)) { - if (strpos($date, '-') !== FALSE) { - $dateVal .= array_shift($matches); - } - if (!CRM_Utils_Rule::dateTime($dateVal)) { - return NULL; - } - $dateVal = CRM_Utils_Date::customFormat(preg_replace("/(:|\s)?/", '', $dateVal), '%Y%m%d%H%i%s'); - return $dateVal; - } - } - // validate date. return CRM_Utils_Rule::date($dateVal) ? $dateVal : NULL; } diff --git a/tests/phpunit/CRM/Utils/DateTest.php b/tests/phpunit/CRM/Utils/DateTest.php index 78f0bb8207..a13b8ca567 100644 --- a/tests/phpunit/CRM/Utils/DateTest.php +++ b/tests/phpunit/CRM/Utils/DateTest.php @@ -2684,11 +2684,12 @@ class CRM_Utils_DateTest extends CiviUnitTestCase { public function dateDataProvider(): array { return [ // YYYY-mm-dd format - eg. 2022-10-01. - '2022-10-01' => ['date' => '2022-10-01', 'format' => CRM_Utils_Date::DATE_yyyy_mm_dd, 'expected' => '20221001'], + '2022-10-01' => ['date' => '2022-10-01', 'format' => CRM_Utils_Date::DATE_yyyy_mm_dd, 'expected' => '20221001000000'], + 'invalid_date_2022-25-01' => ['date' => '2022-25-01', 'format' => CRM_Utils_Date::DATE_yyyy_mm_dd, 'expected' => NULL], '2022-10-01 15:54' => ['date' => '2022-10-01 15:54', 'format' => CRM_Utils_Date::DATE_yyyy_mm_dd, 'expected' => '20221001155400'], - '2022-10-01 3:54' => ['date' => '2022-10-01 3:54', 'format' => CRM_Utils_Date::DATE_yyyy_mm_dd, 'expected' => '20221001155400', 'ignore_reason' => 'Truncated hour does not pass, yet.'], + '2022-10-01 3:54' => ['date' => '2022-10-01 3:54', 'format' => CRM_Utils_Date::DATE_yyyy_mm_dd, 'expected' => '20221001035400'], '2022-10-01 15:54:56' => ['date' => '2022-10-01 15:54:56', 'format' => CRM_Utils_Date::DATE_yyyy_mm_dd, 'expected' => '20221001155456'], - '2022-10-01 3:54:56' => ['date' => '2022-10-01 3:54:56', 'format' => CRM_Utils_Date::DATE_yyyy_mm_dd, 'expected' => '20221001155456', 'ignore_reason' => 'Truncated hour does not pass, yet.'], + '2022-10-01 3:54:56' => ['date' => '2022-10-01 3:54:56', 'format' => CRM_Utils_Date::DATE_yyyy_mm_dd, 'expected' => '20221001035456'], // mm_dd_yy format - eg. US Style 10-01-22 OR 10/01/22 where 10 is the month. 2 digit year. '10-01-22' => ['date' => '10-01-22', 'format' => CRM_Utils_Date::DATE_mm_dd_yy, 'expected' => '20221001'], -- 2.25.1