diff --git a/.gitignore b/.gitignore index 7ea05c8..4b21b4a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .phpunit.cache +.phpunit.result.cache composer.lock phpcs.xml diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 39df3df..0f08581 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,13 +1,14 @@ - + tests/Unit - + + src - + diff --git a/src/Utilities/TimeFormatter.php b/src/Utilities/TimeFormatter.php index b166608..6d6bbe4 100644 --- a/src/Utilities/TimeFormatter.php +++ b/src/Utilities/TimeFormatter.php @@ -34,12 +34,12 @@ public static function decode(?string $time): ?DateTimeImmutable return null; } + $time = \strtoupper($time); + /** @psalm-suppress UndefinedFunction */ - $decoded = DateTimeImmutable::createFromFormat( - \str_contains($time, '.') ? self::RFC3339_EXTENDED_FORMAT : self::RFC3339_FORMAT, - \strtoupper($time), - new DateTimeZone(self::TIME_ZONE) - ); + $decoded = \str_contains($time, '.') + ? DateTimeImmutable::createFromFormat(self::RFC3339_EXTENDED_FORMAT, self::truncateOverPrecision($time), new DateTimeZone(self::TIME_ZONE)) + : DateTimeImmutable::createFromFormat(self::RFC3339_FORMAT, $time, new DateTimeZone(self::TIME_ZONE)); if ($decoded === false) { throw new ValueError( @@ -49,4 +49,17 @@ public static function decode(?string $time): ?DateTimeImmutable return $decoded; } + + private static function truncateOverPrecision(string $time): string + { + [$fst, $snd] = explode('.', $time); + + // match the first n digits at the start + \preg_match('/^\d+/', $snd, $matches); + + $digits = $matches[0] ?? ''; + + // datetime portion + period + up to 6 digits + timezone string + return $fst . '.' . substr($digits, 0, 6) . substr($snd, strlen($digits)); + } } diff --git a/tests/Unit/Utilities/TimeFormatterTest.php b/tests/Unit/Utilities/TimeFormatterTest.php index be3514e..06d8d44 100644 --- a/tests/Unit/Utilities/TimeFormatterTest.php +++ b/tests/Unit/Utilities/TimeFormatterTest.php @@ -19,10 +19,11 @@ public function testEncode(): void ); } - public function providesDecodeCases(): array + public static function providesValidDecodeCases(): array { return [ // UTC + ['2018-04-05T17:31:00Z', '2018-04-05t17:31:00Z'], ['2018-04-05T17:31:00Z', '2018-04-05T17:31:00Z'], ['1985-04-12T23:20:50.100000Z', '1985-04-12T23:20:50.1Z'], ['1985-04-12T23:20:50.100000Z', '1985-04-12T23:20:50.10Z'], @@ -36,8 +37,12 @@ public function providesDecodeCases(): array ['1985-04-12T23:20:50.123450Z', '1985-04-12T23:20:50.12345Z'], ['1985-04-12T23:20:50.123450Z', '1985-04-12T23:20:50.123450Z'], ['1985-04-12T23:20:50.123456Z', '1985-04-12T23:20:50.123456Z'], + ['1985-04-12T23:20:50.123456Z', '1985-04-12T23:20:50.1234567Z'], + ['1985-04-12T23:20:50.123456Z', '1985-04-12T23:20:50.12345678Z'], + ['1985-04-12T23:20:50.123456Z', '1985-04-12T23:20:50.123456789Z'], // +01:00 + ['2018-04-05T16:31:00Z', '2018-04-05t17:31:00+01:00'], ['2018-04-05T16:31:00Z', '2018-04-05T17:31:00+01:00'], ['1985-04-12T22:20:50.100000Z', '1985-04-12T23:20:50.1+01:00'], ['1985-04-12T22:20:50.100000Z', '1985-04-12T23:20:50.10+01:00'], @@ -51,11 +56,20 @@ public function providesDecodeCases(): array ['1985-04-12T22:20:50.123450Z', '1985-04-12T23:20:50.12345+01:00'], ['1985-04-12T22:20:50.123450Z', '1985-04-12T23:20:50.123450+01:00'], ['1985-04-12T22:20:50.123456Z', '1985-04-12T23:20:50.123456+01:00'], + ['1985-04-12T22:20:50.123456Z', '1985-04-12T23:20:50.1234567+01:00'], + ['1985-04-12T22:20:50.123456Z', '1985-04-12T23:20:50.12345678+01:00'], + ['1985-04-12T22:20:50.123456Z', '1985-04-12T23:20:50.123456789+01:00'], + + // -05:00 + ['2018-04-05T22:31:00Z', '2018-04-05t17:31:00-05:00'], + ['2018-04-05T22:31:00Z', '2018-04-05T17:31:00-05:00'], + ['1985-04-13T04:20:50.123456Z', '1985-04-12T23:20:50.123456-05:00'], + ['1985-04-13T04:20:50.123456Z', '1985-04-12T23:20:50.123456789-05:00'], ]; } /** - * @dataProvider providesDecodeCases + * @dataProvider providesValidDecodeCases */ public function testDecode(string $expected, string $input): void { @@ -81,7 +95,23 @@ public function testDecodeEmpty(): void ); } - public function testDecodeInvalidTime(): void + public static function providesInvalidDecodeCases(): array + { + return [ + [''], + ['123'], + ['2018asdsdsafd'], + ['2018-04-05'], + ['2018-04-05 17:31:00Z'], + ['2018-04-05T17:31:00.Z'], + ['2018-04-05T17:31:00ZZ'], + ]; + } + + /** + * @dataProvider providesInvalidDecodeCases + */ + public function testDecodeInvalidTime(string $input): void { $this->expectException(ValueError::class); @@ -89,6 +119,6 @@ public function testDecodeInvalidTime(): void 'CloudEvents\\Utilities\\TimeFormatter::decode(): Argument #1 ($time) is not a valid RFC3339 timestamp' ); - TimeFormatter::decode('2018asdsdsafd'); + TimeFormatter::decode($input); } }