diff --git a/src/EDTFUtils.php b/src/EDTFUtils.php index 0ae1eb9..c968d9e 100644 --- a/src/EDTFUtils.php +++ b/src/EDTFUtils.php @@ -183,7 +183,7 @@ public static function validateDate($datetime_str, $strict = FALSE) { $msgs = []; if (strpos($datetime_str, 'T') > -1) { - list($date, $time) = explode('T', $datetime_str); + [$date, $time] = explode('T', $datetime_str); } else { $date = (string) $datetime_str; @@ -216,22 +216,25 @@ public static function validateDate($datetime_str, $strict = FALSE) { elseif (strlen(ltrim($parsed_date[self::YEAR_BASE], '-')) > 4) { $msgs[] = "Years longer than 4 digits must be prefixed with a 'Y'."; } - elseif (strlen($parsed_date[self::YEAR_BASE]) < 4) { - $msgs[] = "Years must be at least 4 characters long."; - } $strict_pattern = 'Y'; // Month. if (array_key_exists(self::MONTH, $parsed_date) && !empty($parsed_date[self::MONTH])) { // Valid month values? if ( - // Month doesn't exist in mapping or does exist in mapping, but is > 12 - // and there is a day part. - (!array_key_exists($parsed_date[self::MONTH], self::MONTHS_MAP) || - (array_key_exists($parsed_date[self::MONTH], self::MONTHS_MAP) && - array_key_exists(self::DAY, $parsed_date) && - $parsed_date[self::MONTH] > 12)) && - strpos($parsed_date[self::MONTH], 'X') === FALSE) { + // Month doesn't exist in mapping + // and isn't a valid unspecified month value. + ( + !array_key_exists($parsed_date[self::MONTH], self::MONTHS_MAP) && + (intval(str_replace('X', '1', $parsed_date[self::MONTH])) > 12) + ) || + // Sub-year groupings with day values. + ( + array_key_exists($parsed_date[self::MONTH], self::MONTHS_MAP) && + array_key_exists(self::DAY, $parsed_date) && + $parsed_date[self::MONTH] > 12 + ) + ) { $msgs[] = "Provided month value '" . $parsed_date[self::MONTH] . "' is not valid."; } $strict_pattern = 'Y-m'; @@ -327,13 +330,33 @@ public static function expandYear($year_full, $year_base, $year_exponent) { */ public static function iso8601Value(string $edtf) { + if (count(self::validate($edtf)) > 0) { + return ''; + } + // Sets. + if (strpos($edtf, '[') !== FALSE || strpos($edtf, '{') !== FALSE) { + // Use first in set. + $dates = preg_split('/(,|\.\.)/', trim($edtf, '{}[]')); + return self::iso8601Value(array_shift($dates)); + } + // Intervals. + if (str_contains($edtf, '/')) { + $dates = explode('/', $edtf); + return self::iso8601Value(array_shift($dates)); + } + $date_time = explode('T', $edtf); + // Valid EDTF values with time portions are already ISO 8601 timestamps. + if (array_key_exists(1, $date_time) && !empty($date_time[1])) { + return $edtf; + } + preg_match(EDTFUtils::DATE_PARSE_REGEX, $date_time[0], $parsed_date); - $year = ''; - $month = ''; - $day = ''; + $year = '0'; + $month = '01'; + $day = '01'; // Expand the year if the Year Exponent exists. if (array_key_exists(EDTFUtils::YEAR_EXPONENT, $parsed_date) && !empty($parsed_date[EDTFUtils::YEAR_EXPONENT])) { @@ -358,15 +381,7 @@ public static function iso8601Value(string $edtf) { $day = str_replace('X', '0', $day); } - $formatted_date = implode('-', array_filter([$year, $month, $day])); - - // Time. - if (array_key_exists(1, $date_time) && !empty($date_time[1])) { - $formatted_date .= 'T' . $date_time[1]; - } - else { - $formatted_date .= 'T00:00:00'; - } + $formatted_date = implode('-', [$year, $month, $day]) . 'T00:00:00'; return $formatted_date; diff --git a/tests/src/Kernel/EdtfUtilsTest.php b/tests/src/Kernel/EdtfUtilsTest.php index 6fe17b3..0ac7fa6 100644 --- a/tests/src/Kernel/EdtfUtilsTest.php +++ b/tests/src/Kernel/EdtfUtilsTest.php @@ -19,7 +19,7 @@ class EdtfUtilsTest extends KernelTestBase { * * @var array */ - private $testCases = [ + private $singleDateValidations = [ '1900' => [], '1900-01' => [], '1900-01-02' => [], @@ -28,16 +28,16 @@ class EdtfUtilsTest extends KernelTestBase { '1900-91' => ['Provided month value \'91\' is not valid.'], '1900-91-01' => ['Provided month value \'91\' is not valid.'], // No validation for months with X. - '1900-3X' => [], + '1900-3X' => ['Provided month value \'3X\' is not valid.'], // Month 31 without a day matches summer so it's valid. '1900-31' => [], '1900-31-01' => ['Provided month value \'31\' is not valid.'], - '190X-5X-8X' => [], + '190X-5X-8X' => ['Provided month value \'5X\' is not valid.'], '19000' => ['Years longer than 4 digits must be prefixed with a \'Y\'.'], 'Y19000' => [], '190u' => ['Could not parse the date \'190u\'.'], - '190' => ['Years must be at least 4 characters long.'], - '190-99-52' => ['Years must be at least 4 characters long.', + '190' => [], + '190-99-52' => [ 'Provided month value \'99\' is not valid.', 'Provided day value \'52\' is not valid.', ], @@ -53,9 +53,47 @@ class EdtfUtilsTest extends KernelTestBase { * @covers ::validate */ public function testEdtfValidate() { - foreach ($this->testCases as $input => $expected) { + foreach ($this->singleDateValidations as $input => $expected) { $this->assertEquals($expected, EDTFUtils::validate($input, FALSE, FALSE, FALSE)); } } + /** + * @covers ::iso8601Value + */ + public function testIso8601() { + // EDTF value and ISO 8601 Timestamp results. + // Empty values are invalid dates which return blank. + $tests = [ + '1900' => '1900-01-01T00:00:00', + '1900-01' => '1900-01-01T00:00:00', + '1900-01-02' => '1900-01-02T00:00:00', + '190X' => '1900-01-01T00:00:00', + '1900-XX' => '1900-01-01T00:00:00', + '1900-91' => '', + '1900-91-01' => '', + '1900-3X' => '', + '1900-31' => '1900-03-01T00:00:00', + '190X-5X-8X' => '', + '19000' => '', + 'Y19000' => '19000-01-01T00:00:00', + '190u' => '', + '190' => '190-01-01T00:00:00', + '190-99-52' => '', + '1900-01-02T' => '', + '1900-01-02T1:1:1' => '', + '1900-01-02T01:22:33' => '1900-01-02T01:22:33', + '1900-01-02T01:22:33Z' => '1900-01-02T01:22:33Z', + '1900-01-02T01:22:33+' => '', + '1900-01-02T01:22:33+05:00' => '1900-01-02T01:22:33+05:00', + // Intervals and Sets should return the earliest value. + '1900/2023' => '1900-01-01T00:00:00', + '[1900,2023]' => '1900-01-01T00:00:00', + '[1900,2023}' => '1900-01-01T00:00:00', + ]; + foreach ($tests as $date => $iso) { + $this->assertEquals($iso, EDTFUtils::iso8601Value($date)); + } + } + }