From 298474a46cf97bc91b121ec8506e93a0366e73a2 Mon Sep 17 00:00:00 2001 From: John Laswell Date: Fri, 21 Apr 2023 20:13:46 -0400 Subject: [PATCH] Better support RFC3339 (#47) --- .github/workflows/static.yml | 4 +- .gitignore | 1 + phpunit.xml.dist | 7 ++-- src/Utilities/TimeFormatter.php | 45 ++++++++++++++++------ tests/Unit/Utilities/TimeFormatterTest.php | 9 ++++- 5 files changed, 49 insertions(+), 17 deletions(-) diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index cb5e2d0..135bab5 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -17,7 +17,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '7.4' + php-version: '8.2' tools: composer:v2 coverage: none env: @@ -44,7 +44,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '7.4' + php-version: '8.2' tools: composer:v2 coverage: none env: 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..5884226 100644 --- a/src/Utilities/TimeFormatter.php +++ b/src/Utilities/TimeFormatter.php @@ -16,9 +16,6 @@ final class TimeFormatter private const TIME_FORMAT = 'Y-m-d\TH:i:s\Z'; private const TIME_ZONE = 'UTC'; - private const RFC3339_FORMAT = 'Y-m-d\TH:i:sP'; - private const RFC3339_EXTENDED_FORMAT = 'Y-m-d\TH:i:s.uP'; - public static function encode(?DateTimeImmutable $time): ?string { if ($time === null) { @@ -34,19 +31,45 @@ public static function decode(?string $time): ?DateTimeImmutable return null; } - /** @psalm-suppress UndefinedFunction */ - $decoded = DateTimeImmutable::createFromFormat( - \str_contains($time, '.') ? self::RFC3339_EXTENDED_FORMAT : self::RFC3339_FORMAT, - \strtoupper($time), - new DateTimeZone(self::TIME_ZONE) - ); + $time = self::trimMicroseconds($time); - if ($decoded === false) { + try { + $decoded = new DateTimeImmutable($time); + } catch (\Throwable $th) { throw new ValueError( \sprintf('%s(): Argument #1 ($time) is not a valid RFC3339 timestamp', __METHOD__) ); } - return $decoded; + return self::shiftWithTimezone($time, $decoded); + } + + private static function trimMicroseconds(string $time): string + { + $microseconds = explode('.', $time, 2); + if (isset($microseconds[1])) { + $microsecondsAndTimezone = explode('+', $microseconds[1], 2); + if (count($microsecondsAndTimezone) === 1) { + $microsecondsAndTimezone = explode('-', $microseconds[1], 2); + } + $timezone = isset($microsecondsAndTimezone[1]) ? sprintf('+%s', $microsecondsAndTimezone[1]) : ''; + $time = sprintf( + "%s.%s%s", + $microseconds[0], + substr($microsecondsAndTimezone[0], 0, 6), + $timezone + ); + } + + return $time; + } + + private static function shiftWithTimezone(string $time, DateTimeImmutable $datetime): DateTimeImmutable + { + if (\strpos($time, '+') === false && \strpos($time, '-') === false && \strtoupper(\substr($time, -1)) !== 'Z') { + return $datetime->setTimezone(new \DateTimeZone('UTC')); + } + + return $datetime; } } diff --git a/tests/Unit/Utilities/TimeFormatterTest.php b/tests/Unit/Utilities/TimeFormatterTest.php index be3514e..4152fd3 100644 --- a/tests/Unit/Utilities/TimeFormatterTest.php +++ b/tests/Unit/Utilities/TimeFormatterTest.php @@ -19,7 +19,7 @@ public function testEncode(): void ); } - public function providesDecodeCases(): array + public static function providesDecodeCases(): array { return [ // UTC @@ -36,6 +36,9 @@ 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'], @@ -51,6 +54,10 @@ 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'], + ['1985-04-12T22:20:50.123456Z', '1985-04-12T23:20:50.1234567890+01:00'], ]; }