Skip to content

Commit 7cb94eb

Browse files
committed
Create assertion and type-class for xs:dateTime
1 parent 098d413 commit 7cb94eb

File tree

4 files changed

+204
-0
lines changed

4 files changed

+204
-0
lines changed

src/Assert/DateTimeTrait.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\XML\Assert;
6+
7+
use InvalidArgumentException;
8+
9+
/**
10+
* @package simplesamlphp/xml-common
11+
*/
12+
trait DateTimeTrait
13+
{
14+
/**
15+
* The ·lexical space· of dateTime consists of finite-length sequences of characters of the form:
16+
* '-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)?, where
17+
*
18+
* * '-'? yyyy is a four-or-more digit optionally negative-signed numeral that represents the year;
19+
* if more than four digits, leading zeros are prohibited, and '0000' is prohibited (see the Note above (§3.2.7);
20+
* also note that a plus sign is not permitted);
21+
* * the remaining '-'s are separators between parts of the date portion;
22+
* * the first mm is a two-digit numeral that represents the month;
23+
* * dd is a two-digit numeral that represents the day;
24+
* * 'T' is a separator indicating that time-of-day follows;
25+
* * hh is a two-digit numeral that represents the hour; '24' is permitted if the minutes and seconds represented
26+
* are zero, and the dateTime value so represented is the first instant of the following day
27+
* (the hour property of a dateTime object in the ·value space· cannot have a value greater than 23);
28+
* * ':' is a separator between parts of the time-of-day portion;
29+
* * the second mm is a two-digit numeral that represents the minute;
30+
* * ss is a two-integer-digit numeral that represents the whole seconds;
31+
* * '.' s+ (if present) represents the fractional seconds;
32+
* * zzzzzz (if present) represents the timezone (as described below).
33+
*
34+
* @var string
35+
*/
36+
private static string $datetime_regex = '/^-?([1-9][0-9]*|[0-9]{4})-(((0(1|3|5|7|8)|1(0|2))-(0[1-9]|(1|2)[0-9]|3[0-1]))|((0(4|6|9)|11)-(0[1-9]|(1|2)[0-9]|30))|(02-(0[1-9]|(1|2)[0-9])))T([0-1][0-9]|2[0-4]):(0[0-9]|[1-5][0-9]):(0[0-9]|[1-5][0-9])(\.[0-9]{0,6})?((\+|-)([0-1][0-9]|2[0-4]):(0[0-9]|[1-5][0-9])|Z)?$/Di';
37+
38+
/***********************************************************************************
39+
* NOTE: Custom assertions may be added below this line. *
40+
* They SHOULD be marked as `protected` to ensure the call is forced *
41+
* through __callStatic(). *
42+
* Assertions marked `public` are called directly and will *
43+
* not handle any custom exception passed to it. *
44+
***********************************************************************************/
45+
46+
47+
/**
48+
* @param string $value
49+
* @param string $message
50+
*/
51+
protected static function validDateTime(string $value, string $message = ''): void
52+
{
53+
parent::regex(
54+
$value,
55+
self::$datetime_regex,
56+
$message ?: '%s is not a valid xs:dateTime',
57+
InvalidArgumentException::class,
58+
);
59+
}
60+
}

src/Type/DateTimeValue.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\XML\Type;
6+
7+
use SimpleSAML\XML\Assert\Assert;
8+
use SimpleSAML\XML\Exception\SchemaViolationException;
9+
10+
/**
11+
* @package simplesaml/xml-common
12+
*/
13+
class DateTimeValue extends AbstractValueType
14+
{
15+
/**
16+
* Validate the value.
17+
*
18+
* @param string $value
19+
* @throws \SimpleSAML\XML\Exception\SchemaViolationException on failure
20+
* @return void
21+
*/
22+
protected function validateValue(string $value): void
23+
{
24+
Assert::validDateTime($value, SchemaViolationException::class);
25+
}
26+
}

tests/Assert/DateTimeTest.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\Test\XML\Assert;
6+
7+
use PHPUnit\Framework\Attributes\CoversClass;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use PHPUnit\Framework\TestCase;
10+
use SimpleSAML\Assert\AssertionFailedException;
11+
use SimpleSAML\XML\Assert\Assert;
12+
13+
/**
14+
* Class \SimpleSAML\Test\XML\Assert\DateTimeTest
15+
*
16+
* @package simplesamlphp/xml-common
17+
*/
18+
#[CoversClass(Assert::class)]
19+
final class DateTimeTest extends TestCase
20+
{
21+
/**
22+
* @param boolean $shouldPass
23+
* @param string $dateTime
24+
*/
25+
#[DataProvider('provideDateTime')]
26+
public function testValidDateTime(bool $shouldPass, string $dateTime): void
27+
{
28+
try {
29+
Assert::validDateTime($dateTime);
30+
$this->assertTrue($shouldPass);
31+
} catch (AssertionFailedException $e) {
32+
$this->assertFalse($shouldPass);
33+
}
34+
}
35+
36+
37+
/**
38+
* @return array<string, array{0: bool, 1: string}>
39+
*/
40+
public static function provideDateTime(): array
41+
{
42+
return [
43+
'valid' => [true, '2001-10-26T21:32:52'],
44+
'valid with numeric difference' => [true, '2001-10-26T21:32:52+02:00'],
45+
'valid with Zulu' => [true, '2001-10-26T19:32:52Z'],
46+
'valid with 00:00 difference' => [true, '2001-10-26T19:32:52+00:00'],
47+
'valid with negative value' => [true, '-2001-10-26T21:32:52'],
48+
'valid with subseconds' => [true, '2001-10-26T21:32:52.12679'],
49+
'valid with more than four digit year' => [true, '-22001-10-26T21:32:52+02:00'],
50+
'valid with sub-seconds' => [true, '2001-10-26T21:32:52.12679'],
51+
'missing time' => [false, '2001-10-26'],
52+
'missing second' => [false, '2001-10-26T21:32'],
53+
'hour out of range' => [false, '2001-10-26T25:32:52+02:00'],
54+
'year 0000' => [false, '0000-10-26T25:32:52+02:00'],
55+
'prefixed zero' => [false, '02001-10-26T25:32:52+02:00'],
56+
'wrong format' => [false, '01-10-26T21:32'],
57+
];
58+
}
59+
}

tests/Type/DateTimeValueTest.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\Test\XML\Type;
6+
7+
use PHPUnit\Framework\Attributes\CoversClass;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use PHPUnit\Framework\TestCase;
10+
use SimpleSAML\XML\Exception\SchemaViolationException;
11+
use SimpleSAML\XML\Type\DateTimeValue;
12+
13+
/**
14+
* Class \SimpleSAML\Test\Type\DateTimeValueTest
15+
*
16+
* @package simplesamlphp/xml-common
17+
*/
18+
#[CoversClass(DateTimeValue::class)]
19+
final class DateTimeValueTest extends TestCase
20+
{
21+
/**
22+
* @param boolean $shouldPass
23+
* @param string $dateTime
24+
*/
25+
#[DataProvider('provideDateTime')]
26+
public function testDateTime(bool $shouldPass, string $dateTime): void
27+
{
28+
try {
29+
DateTimeValue::fromString($dateTime);
30+
$this->assertTrue($shouldPass);
31+
} catch (SchemaViolationException $e) {
32+
$this->assertFalse($shouldPass);
33+
}
34+
}
35+
36+
37+
/**
38+
* @return array<string, array{0: bool, 1: string}>
39+
*/
40+
public static function provideDateTime(): array
41+
{
42+
return [
43+
'valid' => [true, '2001-10-26T21:32:52'],
44+
'valid with numeric difference' => [true, '2001-10-26T21:32:52+02:00'],
45+
'valid with Zulu' => [true, '2001-10-26T19:32:52Z'],
46+
'valid with 00:00 difference' => [true, '2001-10-26T19:32:52+00:00'],
47+
'valid with negative value' => [true, '-2001-10-26T21:32:52'],
48+
'valid with subseconds' => [true, '2001-10-26T21:32:52.12679'],
49+
'valid with more than four digit year' => [true, '-22001-10-26T21:32:52+02:00'],
50+
'valid with sub-seconds' => [true, '2001-10-26T21:32:52.12679'],
51+
'missing time' => [false, '2001-10-26'],
52+
'missing second' => [false, '2001-10-26T21:32'],
53+
'hour out of range' => [false, '2001-10-26T25:32:52+02:00'],
54+
'year 0000' => [false, '0000-10-26T25:32:52+02:00'],
55+
'prefixed zero' => [false, '02001-10-26T25:32:52+02:00'],
56+
'wrong format' => [false, '01-10-26T21:32'],
57+
];
58+
}
59+
}

0 commit comments

Comments
 (0)