Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ jobs:
- uses: actions/checkout@v6

- name: Get composer cache directory
run: echo COMPOSER_CACHE="$(composer config cache-files-dir)" >> "$env:GITHUB_ENV"
run: echo COMPOSER_CACHE="$(composer config cache-files-dir)" >> "$GITHUB_ENV"

- name: Get COMPOSER_ROOT_VERSION from composer.json branch alias
shell: bash
Expand All @@ -154,7 +154,7 @@ jobs:
echo "Could not read extra.branch-alias.dev-master from composer.json" >&2
exit 1
fi
echo "COMPOSER_ROOT_VERSION=$ROOT_VERSION" >> "GITHUB_ENV"
echo COMPOSER_ROOT_VERSION="$ROOT_VERSION" >> "$GITHUB_ENV"

- name: Cache composer dependencies
uses: actions/cache@v5
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"simplesamlphp/composer-xmlprovider-installer": "~1.3"
},
"require-dev": {
"simplesamlphp/simplesamlphp-test-framework": "~1.11"
"simplesamlphp/simplesamlphp-test-framework": "~1.11.5"
},
"support": {
"issues": "https://github.com/simplesamlphp/xml-common/issues",
Expand Down
11 changes: 1 addition & 10 deletions src/XML/Assert/DateTimeTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,6 @@ trait DateTimeTrait
* * '.' s+ (if present) represents the fractional seconds;
* * zzzzzz (if present) represents the timezone (as described below).
*
* Except for trailing fractional zero digits in the seconds representation, '24:00:00' time representations,
* and timezone (for timezoned values), the mapping from literals to values is one-to-one.
* Where there is more than one possible representation, the canonical representation is as follows:

* The 2-digit numeral representing the hour must not be '24';
* The fractional second string, if present, must not end in '0';
* for timezoned values, the timezone must be represented with 'Z' (All timezoned dateTime values are UTC.).
*
*
* Note: we're restricting decimal seconds to 12, although strictly the standards allow an infite number.
*
* We know for a fact that Apereo CAS v7.0.x uses 9 decimals
Expand All @@ -65,7 +56,7 @@ trait DateTimeTrait
([0-1][0-9]|2[0-3])
:(0[0-9]|[1-5][0-9])
:(0[0-9]|[1-5][0-9])
(\.[0-9]{0,11}[1-9])?
(\.[0-9]{0,12})?
(
[+-]([0-1][0-9]|2[0-4])
:(0[0-9]|[1-5][0-9])
Expand Down
10 changes: 1 addition & 9 deletions src/XML/Assert/TimeTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,6 @@ trait TimeTrait
* * '.' s+ (if present) represents the fractional seconds;
* * zzzzzz (if present) represents the timezone (as described below).
*
* Except for trailing fractional zero digits in the seconds representation, '24:00:00' time representations,
* and timezone (for timezoned values), the mapping from literals to values is one-to-one.
* Where there is more than one possible representation, the canonical representation is as follows:
*
* The 2-digit numeral representing the hour must not be '24';
* The fractional second string, if present, must not end in '0';
* for timezoned values, the timezone must be represented with 'Z' (All timezoned dateTime values are UTC.).
*
* Note: we're restricting decimal seconds to 12, although strictly the standards allow an infite number.
*
* We know for a fact that Apereo CAS v7.0.x uses 9 decimals
Expand All @@ -41,7 +33,7 @@ trait TimeTrait
:
(0[0-9]|[1-5][0-9])
:(0[0-9]|[1-5][0-9])
(\.[0-9]{0,11}[1-9])?
(\.[0-9]{0,12})?
([+-]([0-1][0-9]|2[0-4]):(0[0-9]|[1-5][0-9])|Z)?
$/Dx';

Expand Down
11 changes: 9 additions & 2 deletions src/XMLSchema/Type/DateTimeValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@
use SimpleSAML\XMLSchema\Exception\SchemaViolationException;
use SimpleSAML\XMLSchema\Type\Interface\AbstractAnySimpleType;

use function preg_replace;

/**
* @package simplesaml/xml-common
*/
class DateTimeValue extends AbstractAnySimpleType
{
public const string SCHEMA_TYPE = 'dateTime';

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


/**
Expand All @@ -28,7 +30,12 @@ class DateTimeValue extends AbstractAnySimpleType
*/
protected function sanitizeValue(string $value): string
{
return static::collapseWhitespace(static::normalizeWhitespace($value));
$normalized = static::collapseWhitespace(static::normalizeWhitespace($value));
$sanitized = preg_replace('/\.(\d{0,6})\d*/', '.$1', $normalized);

// Remove all trailing zeros after the dot, and remove the dot if only zeros were present
$sanitized = preg_replace('/\.(?=\d)(?:\d*?[1-9])?\K0+(?=[^0-9]|$)/', '', $sanitized);
return preg_replace('/\.(?!\d)/', '', $sanitized);
}


Expand Down
39 changes: 38 additions & 1 deletion src/XMLSchema/Type/TimeValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,24 @@

namespace SimpleSAML\XMLSchema\Type;

use DateTimeImmutable;
use DateTimeInterface;
use Psr\Clock\ClockInterface;
use SimpleSAML\XML\Assert\Assert;
use SimpleSAML\XMLSchema\Exception\SchemaViolationException;
use SimpleSAML\XMLSchema\Type\Interface\AbstractAnySimpleType;

use function preg_replace;

/**
* @package simplesaml/xml-common
*/
class TimeValue extends AbstractAnySimpleType
{
public const string SCHEMA_TYPE = 'time';

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


/**
* Sanitize the value.
Expand All @@ -23,7 +30,12 @@ class TimeValue extends AbstractAnySimpleType
*/
protected function sanitizeValue(string $value): string
{
return static::collapseWhitespace(static::normalizeWhitespace($value));
$normalized = static::collapseWhitespace(static::normalizeWhitespace($value));
$sanitized = preg_replace('/\.(\d{0,6})\d*/', '.$1', $normalized);

// Remove all trailing zeros after the dot, and remove the dot if only zeros were present
$sanitized = preg_replace('/\.(?=\d)(?:\d*?[1-9])?\K0+(?=[^0-9]|$)/', '', $sanitized);
return preg_replace('/\.(?!\d)/', '', $sanitized);
}


Expand All @@ -37,4 +49,29 @@ protected function validateValue(string $value): void
{
Assert::validTime($this->sanitizeValue($value), SchemaViolationException::class);
}


/**
*/
public static function now(ClockInterface $clock): static
{
return static::fromDateTime($clock->now());
}


/**
* @param \DateTimeInterface $value
*/
public static function fromDateTime(DateTimeInterface $value): static
{
return new static($value->format(static::DATETIME_FORMAT));
}


/**
*/
public function toDateTime(): DateTimeImmutable
{
return new DateTimeImmutable($this->getValue());
}
}
2 changes: 1 addition & 1 deletion tests/XML/Assert/DateTimeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public static function provideValidDateTime(): array
'valid with subseconds' => [true, '2001-10-26T21:32:52.12679'],
'valid with more than four digit year' => [true, '-22001-10-26T21:32:52+02:00'],
'valid with up to twelve sub-seconds' => [true, '2001-10-26T21:32:52.126798764382'],
'sub-seconds ending with zero' => [true, '2001-10-26T21:32:52.12670'],
];
}

Expand All @@ -67,7 +68,6 @@ public static function provideInvalidDateTime(): array
'prefixed zero' => [false, '02001-10-26T25:32:52+02:00'],
'wrong format' => [false, '01-10-26T21:32'],
'too many sub-seconds' => [false, '2001-10-26T21:32:52.1267987643821'],
'sub-seconds ending with zero' => [false, '2001-10-26T21:32:52.12670'],
];
}
}
2 changes: 1 addition & 1 deletion tests/XML/Assert/TimeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public static function provideValidTime(): array
'valid time with 00:00 timezone' => [true, '19:32:52+00:00'],
'valid time with sub-seconds' => [true, '21:32:52.12679'],
'valid with up to twelve sub-seconds' => [true, '21:32:52.126798764382'],
'sub-seconds ending with zero' => [true, '21:32:52.12670'],
];
}

Expand All @@ -62,7 +63,6 @@ public static function provideInvalidTime(): array
'invalid hour twenty-four' => [false, '24:25:10'],
'invalid invalid format' => [false, '1:20:10'],
'too many sub-seconds' => [false, '21:32:52.1267987643821'],
'sub-seconds ending with zero' => [false, '21:32:52.12670'],
];
}
}
36 changes: 36 additions & 0 deletions tests/XMLSchema/Type/DateTimeValueTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,49 @@ public function testFromDateTime(): void
}


/**
*/
public function testSubSeconds(): void
{
// Strip sub-second trailing zero's and make sure the decimal sign is removed
$dateTimeValue = DateTimeValue::fromString('2001-10-26T21:32:52.00');
$this->assertEquals('2001-10-26T21:32:52', $dateTimeValue->getValue());

// Strip sub-second trailing zero's
$dateTimeValue = DateTimeValue::fromString('2001-10-26T21:32:52.12300');
$this->assertEquals('2001-10-26T21:32:52.123', $dateTimeValue->getValue());

// Strip sub-seconds over microsecond precision
$dateTimeValue = DateTimeValue::fromString('2001-10-26T21:32:52.1234567');
$this->assertEquals('2001-10-26T21:32:52.123456', $dateTimeValue->getValue());

// Strip sub-second trailing zero's and make sure the decimal sign is removed
$dateTimeValue = DateTimeValue::fromString('2001-10-26T21:32:52.00Z');
$this->assertEquals('2001-10-26T21:32:52Z', $dateTimeValue->getValue());

// Strip sub-seconds over microsecond precision with timezone
$dateTimeValue = DateTimeValue::fromString('2001-10-26T21:32:52.1234567+01:00');
$this->assertEquals('2001-10-26T21:32:52.123456+01:00', $dateTimeValue->getValue());

// Strip sub-seconds over microsecond precision with timezone Zulu
$dateTimeValue = DateTimeValue::fromString('2001-10-26T21:32:52.1234567Z');
$this->assertEquals('2001-10-26T21:32:52.123456Z', $dateTimeValue->getValue());
}


/**
* @return array<string, array{0: true, 1: string}>
*/
public static function provideValidDateTime(): array
{
return [
'whitespace collapse' => [true, ' 2001-10-26T21:32:52 '],
'trailing sub-second zero' => [true, '2001-10-26T21:32:52.1230'],
'trailing sub-second zero with timezone' => [true, '2001-10-26T21:32:52.1230+00:00'],
'trailing sub-second zero with timezone Zulu' => [true, '2001-10-26T21:32:52.1230Z'],
'all trailing sub-second zero' => [true, '2001-10-26T21:32:52.00'],
'all trailing sub-second zero with timezone' => [true, '2001-10-26T21:32:52.00+00:00'],
'all trailing sub-second zero with timezone Zulu' => [true, '2001-10-26T21:32:52.00Z'],
];
}

Expand Down
50 changes: 50 additions & 0 deletions tests/XMLSchema/Type/TimeValueTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace SimpleSAML\Test\XMLSchema\Type\Builtin;

use DateTimeImmutable;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\DataProviderExternal;
Expand Down Expand Up @@ -40,13 +41,62 @@ public function testTime(bool $shouldPass, string $time): void
}


/**
* Test the fromDateTime function
*/
#[DependsOnClass(TimeTest::class)]
public function testFromDateTime(): void
{
$t = new DateTimeImmutable('00:00:00+00:00');

$timeValue = TimeValue::fromDateTime($t);
$this->assertEquals('00:00:00+00:00', $timeValue->getValue());
}


/**
*/
public function testSubSeconds(): void
{
// Strip sub-second trailing zero's and make sure the decimal sign is removed
$timeValue = TimeValue::fromString('21:32:52.00');
$this->assertEquals('21:32:52', $timeValue->getValue());

// Strip sub-second trailing zero's
$timeValue = TimeValue::fromString('21:32:52.12300');
$this->assertEquals('21:32:52.123', $timeValue->getValue());

// Strip sub-seconds over microsecond precision
$timeValue = TimeValue::fromString('21:32:52.1234567');
$this->assertEquals('21:32:52.123456', $timeValue->getValue());

// Strip sub-second trailing zero's and make sure the decimal sign is removed
$timeValue = TimeValue::fromString('21:32:52.00Z');
$this->assertEquals('21:32:52Z', $timeValue->getValue());

// Strip sub-seconds over microsecond precision with timezone
$timeValue = TimeValue::fromString('21:32:52.1234567+01:00');
$this->assertEquals('21:32:52.123456+01:00', $timeValue->getValue());

// Strip sub-seconds over microsecond precision with timezone Zulu
$timeValue = TimeValue::fromString('21:32:52.1234567Z');
$this->assertEquals('21:32:52.123456Z', $timeValue->getValue());
}


/**
* @return array<string, array{0: true, 1: string}>
*/
public static function provideValidTime(): array
{
return [
'whitespace collapse' => [true, "\n 21:32:52.12679\t "],
'trailing sub-second zero' => [true, '21:32:52.1230'],
'trailing sub-second zero with timezone' => [true, '21:32:52.1230+00:00'],
'trailing sub-second zero with timezone Zulu' => [true, '21:32:52.1230Z'],
'all trailing sub-second all zero' => [true, '21:32:52.00'],
'all trailing sub-second all zero with timezone' => [true, '21:32:52.00+00:00'],
'all trailing sub-second all zero with timezone Zulu' => [true, '21:32:52.00Z'],
];
}

Expand Down
Loading