Skip to content

Commit e7779e4

Browse files
authored
Merge pull request #104 from seth-shaw-asu/issue-103
fix/add tests for validation and ISO 8601
2 parents 20ec123 + d0867df commit e7779e4

File tree

3 files changed

+85
-29
lines changed

3 files changed

+85
-29
lines changed

src/EDTFConverter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public static function datetimeIso8601Value($data) {
3030
}
3131

3232
/**
33-
* Converts an EDTF text field into an ISO 8601 timestamp string.
33+
* Converts an EDTF text field into an ISO 8601 date.
3434
*
3535
* It assumes the earliest valid date for approximations and intervals.
3636
*

src/EDTFUtils.php

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ public static function validateDate($datetime_str, $strict = FALSE) {
183183
$msgs = [];
184184

185185
if (strpos($datetime_str, 'T') > -1) {
186-
list($date, $time) = explode('T', $datetime_str);
186+
[$date, $time] = explode('T', $datetime_str);
187187
}
188188
else {
189189
$date = (string) $datetime_str;
@@ -216,22 +216,29 @@ public static function validateDate($datetime_str, $strict = FALSE) {
216216
elseif (strlen(ltrim($parsed_date[self::YEAR_BASE], '-')) > 4) {
217217
$msgs[] = "Years longer than 4 digits must be prefixed with a 'Y'.";
218218
}
219-
elseif (strlen($parsed_date[self::YEAR_BASE]) < 4) {
220-
$msgs[] = "Years must be at least 4 characters long.";
221-
}
222219
$strict_pattern = 'Y';
223220

224221
// Month.
225222
if (array_key_exists(self::MONTH, $parsed_date) && !empty($parsed_date[self::MONTH])) {
226223
// Valid month values?
227224
if (
228-
// Month doesn't exist in mapping or does exist in mapping, but is > 12
229-
// and there is a day part.
230-
(!array_key_exists($parsed_date[self::MONTH], self::MONTHS_MAP) ||
231-
(array_key_exists($parsed_date[self::MONTH], self::MONTHS_MAP) &&
232-
array_key_exists(self::DAY, $parsed_date) &&
233-
$parsed_date[self::MONTH] > 12)) &&
234-
strpos($parsed_date[self::MONTH], 'X') === FALSE) {
225+
// Month doesn't exist in mapping
226+
// and isn't a valid unspecified month value.
227+
(
228+
!array_key_exists($parsed_date[self::MONTH], self::MONTHS_MAP) &&
229+
(intval(str_replace('X', '1', $parsed_date[self::MONTH])) > 12)
230+
) ||
231+
// Sub-year groupings with day values.
232+
(
233+
array_key_exists($parsed_date[self::MONTH], self::MONTHS_MAP) &&
234+
array_key_exists(self::DAY, $parsed_date) &&
235+
$parsed_date[self::MONTH] > 12
236+
) ||
237+
// Unspecifed character comes before number.
238+
(
239+
preg_match('/X+\d/', $parsed_date[self::MONTH])
240+
)
241+
) {
235242
$msgs[] = "Provided month value '" . $parsed_date[self::MONTH] . "' is not valid.";
236243
}
237244
$strict_pattern = 'Y-m';
@@ -327,8 +334,28 @@ public static function expandYear($year_full, $year_base, $year_exponent) {
327334
*/
328335
public static function iso8601Value(string $edtf) {
329336

337+
if (count(self::validate($edtf)) > 0) {
338+
return '';
339+
}
340+
// Sets.
341+
if (strpos($edtf, '[') !== FALSE || strpos($edtf, '{') !== FALSE) {
342+
// Use first in set.
343+
$dates = preg_split('/(,|\.\.)/', trim($edtf, '{}[]'));
344+
return self::iso8601Value(array_shift($dates));
345+
}
346+
// Intervals.
347+
if (str_contains($edtf, '/')) {
348+
$dates = explode('/', $edtf);
349+
return self::iso8601Value(array_shift($dates));
350+
}
351+
330352
$date_time = explode('T', $edtf);
331353

354+
// Valid EDTF values with time portions are already ISO 8601 timestamps.
355+
if (array_key_exists(1, $date_time) && !empty($date_time[1])) {
356+
return $edtf;
357+
}
358+
332359
preg_match(EDTFUtils::DATE_PARSE_REGEX, $date_time[0], $parsed_date);
333360

334361
$year = '';
@@ -358,17 +385,7 @@ public static function iso8601Value(string $edtf) {
358385
$day = str_replace('X', '0', $day);
359386
}
360387

361-
$formatted_date = implode('-', array_filter([$year, $month, $day]));
362-
363-
// Time.
364-
if (array_key_exists(1, $date_time) && !empty($date_time[1])) {
365-
$formatted_date .= 'T' . $date_time[1];
366-
}
367-
else {
368-
$formatted_date .= 'T00:00:00';
369-
}
370-
371-
return $formatted_date;
388+
return implode('-', array_filter([$year, $month, $day]));
372389

373390
}
374391

tests/src/Kernel/EdtfUtilsTest.php

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,26 @@ class EdtfUtilsTest extends KernelTestBase {
1919
*
2020
* @var array
2121
*/
22-
private $testCases = [
22+
private $singleDateValidations = [
2323
'1900' => [],
2424
'1900-01' => [],
2525
'1900-01-02' => [],
2626
'190X' => [],
2727
'1900-XX' => [],
2828
'1900-91' => ['Provided month value \'91\' is not valid.'],
2929
'1900-91-01' => ['Provided month value \'91\' is not valid.'],
30+
'1900-X1' => ['Provided month value \'X1\' is not valid.'],
3031
// No validation for months with X.
31-
'1900-3X' => [],
32+
'1900-3X' => ['Provided month value \'3X\' is not valid.'],
3233
// Month 31 without a day matches summer so it's valid.
3334
'1900-31' => [],
3435
'1900-31-01' => ['Provided month value \'31\' is not valid.'],
35-
'190X-5X-8X' => [],
36+
'190X-5X-8X' => ['Provided month value \'5X\' is not valid.'],
3637
'19000' => ['Years longer than 4 digits must be prefixed with a \'Y\'.'],
3738
'Y19000' => [],
3839
'190u' => ['Could not parse the date \'190u\'.'],
39-
'190' => ['Years must be at least 4 characters long.'],
40-
'190-99-52' => ['Years must be at least 4 characters long.',
40+
'190' => [],
41+
'190-99-52' => [
4142
'Provided month value \'99\' is not valid.',
4243
'Provided day value \'52\' is not valid.',
4344
],
@@ -53,9 +54,47 @@ class EdtfUtilsTest extends KernelTestBase {
5354
* @covers ::validate
5455
*/
5556
public function testEdtfValidate() {
56-
foreach ($this->testCases as $input => $expected) {
57+
foreach ($this->singleDateValidations as $input => $expected) {
5758
$this->assertEquals($expected, EDTFUtils::validate($input, FALSE, FALSE, FALSE));
5859
}
5960
}
6061

62+
/**
63+
* @covers ::iso8601Value
64+
*/
65+
public function testIso8601() {
66+
// EDTF value and ISO 8601 Timestamp results.
67+
// Empty values are invalid dates which return blank.
68+
$tests = [
69+
'1900' => '1900',
70+
'1900-01' => '1900-01',
71+
'1900-01-02' => '1900-01-02',
72+
'190X' => '1900',
73+
'1900-XX' => '1900-01',
74+
'1900-91' => '',
75+
'1900-91-01' => '',
76+
'1900-3X' => '',
77+
'1900-31' => '1900-03',
78+
'190X-5X-8X' => '',
79+
'19000' => '',
80+
'Y19000' => '19000',
81+
'190u' => '',
82+
'190' => '190',
83+
'190-99-52' => '',
84+
'1900-01-02T' => '',
85+
'1900-01-02T1:1:1' => '',
86+
'1900-01-02T01:22:33' => '1900-01-02T01:22:33',
87+
'1900-01-02T01:22:33Z' => '1900-01-02T01:22:33Z',
88+
'1900-01-02T01:22:33+' => '',
89+
'1900-01-02T01:22:33+05:00' => '1900-01-02T01:22:33+05:00',
90+
// Intervals and Sets should return the earliest value.
91+
'1900/2023' => '1900',
92+
'[1900,2023]' => '1900',
93+
'[1900,2023}' => '1900',
94+
];
95+
foreach ($tests as $date => $iso) {
96+
$this->assertEquals($iso, EDTFUtils::iso8601Value($date));
97+
}
98+
}
99+
61100
}

0 commit comments

Comments
 (0)