diff --git a/CRM/Import/Parser.php b/CRM/Import/Parser.php index 7ec35b7c9aa7..32e6909ad9dc 100644 --- a/CRM/Import/Parser.php +++ b/CRM/Import/Parser.php @@ -1354,7 +1354,7 @@ protected function getTransformedFieldValue(string $fieldName, $importedValue) { } if ($fieldMetadata['type'] === CRM_Utils_Type::T_DATE || $fieldMetadata['type'] === (CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME) || $fieldMetadata['type'] === CRM_Utils_Type::T_TIMESTAMP) { $value = CRM_Utils_Date::formatDate($importedValue, $this->getSubmittedValue('dateFormats')); - return ($value) ?: 'invalid_import_value'; + return $value ?: 'invalid_import_value'; } $options = $this->getFieldOptions($fieldName); if ($options !== FALSE) { diff --git a/CRM/Utils/Date.php b/CRM/Utils/Date.php index 98e723f60f10..8b5cd80da5b0 100644 --- a/CRM/Utils/Date.php +++ b/CRM/Utils/Date.php @@ -369,6 +369,7 @@ public static function customFormat($dateString, $format = NULL, $dateParts = NU $hour24 = (int) substr($dateString, 11, 2); $minute = (int) substr($dateString, 14, 2); + $second = (int) substr($dateString, 16, 2); } else { $year = (int) substr($dateString, 0, 4); @@ -377,6 +378,7 @@ public static function customFormat($dateString, $format = NULL, $dateParts = NU $hour24 = (int) substr($dateString, 8, 2); $minute = (int) substr($dateString, 10, 2); + $second = (int) substr($dateString, 12, 2); } if ($day % 10 == 1 and $day != 11) { @@ -430,13 +432,12 @@ public static function customFormat($dateString, $format = NULL, $dateParts = NU '%P' => $type, '%A' => $type, '%Y' => $year, + '%s' => str_pad($second, 2, 0, STR_PAD_LEFT), ]; return strtr($format, $date); } - else { - return ''; - } + return ''; } /** @@ -2101,15 +2102,23 @@ public static function getDateFormat($formatType = NULL) { } /** + * Date formatting for imports where date format is specified. + * + * Note this is used for imports (only) because the importer can + * specify the format. + * + * Tests are in CRM_Utils_DateTest::testFormatDate + * * @param $date + * Date string as entered. * @param $dateType + * One of the constants like CRM_Core_Form_Date::DATE_yyyy_mm_dd. * - * @return null|string + * @return false|string */ public static function formatDate($date, $dateType) { - $formattedDate = NULL; if (empty($date)) { - return $formattedDate; + return FALSE; } // 1. first convert date to default format. @@ -2122,32 +2131,28 @@ public static function formatDate($date, $dateType) { if (CRM_Utils_Date::convertToDefaultDate($dateParams, $dateType, $dateKey)) { $dateVal = $dateParams[$dateKey]; - $ruleName = 'date'; - if ($dateType == 1) { + if ($dateType === 1) { $matches = []; - if (preg_match("/(\s(([01]\d)|[2][0-3]):([0-5]\d):?[0-5]?\d?)$/", $date, $matches)) { - $ruleName = 'dateTime'; + // 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. - $valid = CRM_Utils_Rule::$ruleName($dateVal); - - if ($valid) { - // format date and time to default. - if ($ruleName == 'dateTime') { - $dateVal = CRM_Utils_Date::customFormat(preg_replace("/(:|\s)?/", "", $dateVal), '%Y%m%d%H%i'); - // hack to add seconds - $dateVal .= '00'; - } - $formattedDate = $dateVal; - } + return CRM_Utils_Rule::date($dateVal) ? $dateVal : FALSE; } - return $formattedDate; + return FALSE; } /** diff --git a/tests/phpunit/CRM/Utils/DateTest.php b/tests/phpunit/CRM/Utils/DateTest.php index b0917741ef28..8ab957fbbe41 100644 --- a/tests/phpunit/CRM/Utils/DateTest.php +++ b/tests/phpunit/CRM/Utils/DateTest.php @@ -68,7 +68,7 @@ public function testGetFromTo() { $cases = $this->fromToData(); foreach ($cases as $caseDescription => $case) { $obj = new CRM_Utils_Date(); - list($calculatedFrom, $calculatedTo) = $obj->getFromTo($case['relative'], $case['from'], $case['to']); + [$calculatedFrom, $calculatedTo] = $obj->getFromTo($case['relative'], $case['from'], $case['to']); $this->assertEquals($case['expectedFrom'], $calculatedFrom, "Expected From failed for case $caseDescription"); $this->assertEquals($case['expectedTo'], $calculatedTo, "Expected To failed for case $caseDescription"); } @@ -324,4 +324,29 @@ public function testLocalizeConsts() { } } + /** + * Test formatDate function. + * + * @dataProvider dateDataProvider + * + * Test the format function used in imports. Note most forms + * are able to format pre-submit but the import needs to parse the date. + */ + public function testFormatDate($date, $format, $expected): void { + $this->assertEquals($expected, CRM_Utils_Date::formatDate($date, $format)); + } + + /** + * Data provider for date formats. + * + * @return array[] + */ + public function dateDataProvider(): array { + return [ + ['date' => '2022-10-01', 'format' => CRM_Core_Form_Date::DATE_yyyy_mm_dd, 'expected' => '20221001'], + ['date' => '2022-10-01 15:54', 'format' => CRM_Core_Form_Date::DATE_yyyy_mm_dd, 'expected' => '20221001155400'], + ['date' => '2022-10-01 15:54:56', 'format' => CRM_Core_Form_Date::DATE_yyyy_mm_dd, 'expected' => '20221001155456'], + ]; + } + }