Skip to content

Commit 40bbd21

Browse files
committed
Change of heart: we MUST accept trailing zero's
1 parent 348a56a commit 40bbd21

File tree

7 files changed

+68
-72
lines changed

7 files changed

+68
-72
lines changed

src/XML/Assert/DateTimeTrait.php

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,6 @@ trait DateTimeTrait
3131
* * '.' s+ (if present) represents the fractional seconds;
3232
* * zzzzzz (if present) represents the timezone (as described below).
3333
*
34-
* Except for trailing fractional zero digits in the seconds representation, '24:00:00' time representations,
35-
* and timezone (for timezoned values), the mapping from literals to values is one-to-one.
36-
* Where there is more than one possible representation, the canonical representation is as follows:
37-
38-
* The 2-digit numeral representing the hour must not be '24';
39-
* The fractional second string, if present, must not end in '0';
40-
* for timezoned values, the timezone must be represented with 'Z' (All timezoned dateTime values are UTC.).
41-
*
42-
*
4334
* Note: we're restricting decimal seconds to 12, although strictly the standards allow an infite number.
4435
*
4536
* We know for a fact that Apereo CAS v7.0.x uses 9 decimals
@@ -65,7 +56,7 @@ trait DateTimeTrait
6556
([0-1][0-9]|2[0-3])
6657
:(0[0-9]|[1-5][0-9])
6758
:(0[0-9]|[1-5][0-9])
68-
(\.[0-9]{0,11}[1-9])?
59+
(\.[0-9]{0,12})?
6960
(
7061
[+-]([0-1][0-9]|2[0-4])
7162
:(0[0-9]|[1-5][0-9])

src/XML/Assert/TimeTrait.php

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,6 @@ trait TimeTrait
2424
* * '.' s+ (if present) represents the fractional seconds;
2525
* * zzzzzz (if present) represents the timezone (as described below).
2626
*
27-
* Except for trailing fractional zero digits in the seconds representation, '24:00:00' time representations,
28-
* and timezone (for timezoned values), the mapping from literals to values is one-to-one.
29-
* Where there is more than one possible representation, the canonical representation is as follows:
30-
*
31-
* The 2-digit numeral representing the hour must not be '24';
32-
* The fractional second string, if present, must not end in '0';
33-
* for timezoned values, the timezone must be represented with 'Z' (All timezoned dateTime values are UTC.).
34-
*
3527
* Note: we're restricting decimal seconds to 12, although strictly the standards allow an infite number.
3628
*
3729
* We know for a fact that Apereo CAS v7.0.x uses 9 decimals
@@ -41,7 +33,7 @@ trait TimeTrait
4133
:
4234
(0[0-9]|[1-5][0-9])
4335
:(0[0-9]|[1-5][0-9])
44-
(\.[0-9]{0,11}[1-9])?
36+
(\.[0-9]{0,12})?
4537
([+-]([0-1][0-9]|2[0-4]):(0[0-9]|[1-5][0-9])|Z)?
4638
$/Dx';
4739

src/XMLSchema/Type/DateTimeValue.php

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@
1111
use SimpleSAML\XMLSchema\Exception\SchemaViolationException;
1212
use SimpleSAML\XMLSchema\Type\Interface\AbstractAnySimpleType;
1313

14-
use function rtrim;
15-
use function strlen;
16-
use function substr;
14+
use function preg_replace;
1715

1816
/**
1917
* @package simplesaml/xml-common
@@ -22,7 +20,7 @@ class DateTimeValue extends AbstractAnySimpleType
2220
{
2321
public const string SCHEMA_TYPE = 'dateTime';
2422

25-
public const string DATETIME_FORMAT = 'Y-m-d\\TH:i:sP';
23+
public const string DATETIME_FORMAT = 'Y-m-d\\TH:i:s.uP';
2624

2725

2826
/**
@@ -33,30 +31,11 @@ class DateTimeValue extends AbstractAnySimpleType
3331
protected function sanitizeValue(string $value): string
3432
{
3533
$normalized = static::collapseWhitespace(static::normalizeWhitespace($value));
36-
// Trim any trailing zero's from the sub-seconds
37-
$decimal = strrpos($normalized, '.');
38-
if ($decimal !== false) {
39-
@list($dateValue, $timeValue) = explode('T', $normalized);
40-
Assert::notNull($dateValue);
41-
Assert::notNull($timeValue);
42-
43-
$timezone = strrpos($timeValue, '+') ?: strrpos($timeValue, '-') ?: strrpos($timeValue, 'Z');
44-
if ($timezone !== false) {
45-
$subseconds = substr($timeValue, $decimal + $timezone, strlen($timeValue) - $timezone);
46-
} else {
47-
$subseconds = substr($timeValue, $decimal + 1);
48-
}
49-
50-
$subseconds = rtrim($subseconds, '0');
51-
if ($subseconds === '') {
52-
return substr($normalized, 0, $decimal);
53-
}
54-
return substr($normalized, 0, $decimal + 1)
55-
. $subseconds
56-
. (($timezone === false) ? '' : substr($normalized, $timezone));
57-
}
58-
59-
return $normalized;
34+
35+
$sanitized = preg_replace('/\.0+/', '', $normalized);
36+
$sanitized = preg_replace('/\.(?!\d)/', '', $sanitized);
37+
38+
return $sanitized;
6039
}
6140

6241

src/XMLSchema/Type/TimeValue.php

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
namespace SimpleSAML\XMLSchema\Type;
66

7+
use DateTimeImmutable;
8+
use DateTimeInterface;
9+
use Psr\Clock\ClockInterface;
710
use SimpleSAML\XML\Assert\Assert;
811
use SimpleSAML\XMLSchema\Exception\SchemaViolationException;
912
use SimpleSAML\XMLSchema\Type\Interface\AbstractAnySimpleType;
1013

11-
use function rtrim;
12-
use function strlen;
13-
use function substr;
14+
use function preg_replace;
1415

1516
/**
1617
* @package simplesaml/xml-common
@@ -19,6 +20,7 @@ class TimeValue extends AbstractAnySimpleType
1920
{
2021
public const string SCHEMA_TYPE = 'time';
2122

23+
public const string DATETIME_FORMAT = 'H:i:s.uP';
2224

2325
/**
2426
* Sanitize the value.
@@ -29,26 +31,10 @@ protected function sanitizeValue(string $value): string
2931
{
3032
$normalized = static::collapseWhitespace(static::normalizeWhitespace($value));
3133

32-
// Trim any trailing zero's from the sub-seconds
33-
$decimal = strrpos($normalized, '.');
34-
if ($decimal !== false) {
35-
$timezone = strrpos($normalized, '+') ?: strrpos($normalized, '-') ?: strrpos($normalized, 'Z');
36-
if ($timezone !== false) {
37-
$subseconds = substr($normalized, $decimal + $timezone, strlen($normalized) - $timezone);
38-
} else {
39-
$subseconds = substr($normalized, $decimal + 1);
40-
}
41-
42-
$subseconds = rtrim($subseconds, '0');
43-
if ($subseconds === '') {
44-
return substr($normalized, 0, $decimal);
45-
}
46-
return substr($normalized, 0, $decimal + 1)
47-
. $subseconds
48-
. (($timezone === false) ? '' : substr($normalized, $timezone));
49-
}
50-
51-
return $normalized;
34+
$sanitized = preg_replace('/\.0+/', '', $normalized);
35+
$sanitized = preg_replace('/\.(?!\d)/', '', $sanitized);
36+
37+
return $sanitized;
5238
}
5339

5440

@@ -62,4 +48,29 @@ protected function validateValue(string $value): void
6248
{
6349
Assert::validTime($this->sanitizeValue($value), SchemaViolationException::class);
6450
}
51+
52+
53+
/**
54+
*/
55+
public static function now(ClockInterface $clock): static
56+
{
57+
return static::fromDateTime($clock->now());
58+
}
59+
60+
61+
/**
62+
* @param \DateTimeInterface $value
63+
*/
64+
public static function fromDateTime(DateTimeInterface $value): static
65+
{
66+
return new static($value->format(static::DATETIME_FORMAT));
67+
}
68+
69+
70+
/**
71+
*/
72+
public function toDateTime(): DateTimeImmutable
73+
{
74+
return new DateTimeImmutable($this->getValue());
75+
}
6576
}

tests/XML/Assert/DateTimeTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public static function provideValidDateTime(): array
4949
'valid with subseconds' => [true, '2001-10-26T21:32:52.12679'],
5050
'valid with more than four digit year' => [true, '-22001-10-26T21:32:52+02:00'],
5151
'valid with up to twelve sub-seconds' => [true, '2001-10-26T21:32:52.126798764382'],
52+
'sub-seconds ending with zero' => [true, '2001-10-26T21:32:52.12670'],
5253
];
5354
}
5455

@@ -67,7 +68,6 @@ public static function provideInvalidDateTime(): array
6768
'prefixed zero' => [false, '02001-10-26T25:32:52+02:00'],
6869
'wrong format' => [false, '01-10-26T21:32'],
6970
'too many sub-seconds' => [false, '2001-10-26T21:32:52.1267987643821'],
70-
'sub-seconds ending with zero' => [false, '2001-10-26T21:32:52.12670'],
7171
];
7272
}
7373
}

tests/XML/Assert/TimeTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public static function provideValidTime(): array
4747
'valid time with 00:00 timezone' => [true, '19:32:52+00:00'],
4848
'valid time with sub-seconds' => [true, '21:32:52.12679'],
4949
'valid with up to twelve sub-seconds' => [true, '21:32:52.126798764382'],
50+
'sub-seconds ending with zero' => [true, '21:32:52.12670'],
5051
];
5152
}
5253

@@ -62,7 +63,6 @@ public static function provideInvalidTime(): array
6263
'invalid hour twenty-four' => [false, '24:25:10'],
6364
'invalid invalid format' => [false, '1:20:10'],
6465
'too many sub-seconds' => [false, '21:32:52.1267987643821'],
65-
'sub-seconds ending with zero' => [false, '21:32:52.12670'],
6666
];
6767
}
6868
}

tests/XMLSchema/Type/TimeValueTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace SimpleSAML\Test\XMLSchema\Type\Builtin;
66

7+
use DateTimeImmutable;
78
use PHPUnit\Framework\Attributes\CoversClass;
89
use PHPUnit\Framework\Attributes\DataProvider;
910
use PHPUnit\Framework\Attributes\DataProviderExternal;
@@ -40,6 +41,28 @@ public function testTime(bool $shouldPass, string $time): void
4041
}
4142

4243

44+
/**
45+
* Test the fromDateTime function
46+
*/
47+
#[DependsOnClass(TimeTest::class)]
48+
public function testFromDateTime(): void
49+
{
50+
$t = new DateTimeImmutable('00:00:00+00:00');
51+
52+
$timeValue = TimeValue::fromDateTime($t);
53+
$this->assertEquals('00:00:00+00:00', $timeValue->getValue());
54+
}
55+
56+
57+
/**
58+
*/
59+
public function testSubSeconds(): void
60+
{
61+
$timeValue = TimeValue::fromString('21:32:52.00');
62+
$this->assertEquals('21:32:52', $timeValue->getValue());
63+
}
64+
65+
4366
/**
4467
* @return array<string, array{0: true, 1: string}>
4568
*/

0 commit comments

Comments
 (0)