Skip to content

Commit f819b5f

Browse files
Better support RFC3339 (#48)
1 parent 298474a commit f819b5f

File tree

3 files changed

+48
-35
lines changed

3 files changed

+48
-35
lines changed

.github/workflows/static.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
- name: Setup PHP
1818
uses: shivammathur/setup-php@v2
1919
with:
20-
php-version: '8.2'
20+
php-version: '7.4'
2121
tools: composer:v2
2222
coverage: none
2323
env:
@@ -44,7 +44,7 @@ jobs:
4444
- name: Setup PHP
4545
uses: shivammathur/setup-php@v2
4646
with:
47-
php-version: '8.2'
47+
php-version: '7.4'
4848
tools: composer:v2
4949
coverage: none
5050
env:

src/Utilities/TimeFormatter.php

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ final class TimeFormatter
1616
private const TIME_FORMAT = 'Y-m-d\TH:i:s\Z';
1717
private const TIME_ZONE = 'UTC';
1818

19+
private const RFC3339_FORMAT = 'Y-m-d\TH:i:sP';
20+
private const RFC3339_EXTENDED_FORMAT = 'Y-m-d\TH:i:s.uP';
21+
1922
public static function encode(?DateTimeImmutable $time): ?string
2023
{
2124
if ($time === null) {
@@ -31,45 +34,32 @@ public static function decode(?string $time): ?DateTimeImmutable
3134
return null;
3235
}
3336

34-
$time = self::trimMicroseconds($time);
37+
$time = \strtoupper($time);
38+
39+
/** @psalm-suppress UndefinedFunction */
40+
$decoded = \str_contains($time, '.')
41+
? DateTimeImmutable::createFromFormat(self::RFC3339_EXTENDED_FORMAT, self::truncateOverPrecision($time), new DateTimeZone(self::TIME_ZONE))
42+
: DateTimeImmutable::createFromFormat(self::RFC3339_FORMAT, $time, new DateTimeZone(self::TIME_ZONE));
3543

36-
try {
37-
$decoded = new DateTimeImmutable($time);
38-
} catch (\Throwable $th) {
44+
if ($decoded === false) {
3945
throw new ValueError(
4046
\sprintf('%s(): Argument #1 ($time) is not a valid RFC3339 timestamp', __METHOD__)
4147
);
4248
}
4349

44-
return self::shiftWithTimezone($time, $decoded);
50+
return $decoded;
4551
}
4652

47-
private static function trimMicroseconds(string $time): string
53+
private static function truncateOverPrecision(string $time): string
4854
{
49-
$microseconds = explode('.', $time, 2);
50-
if (isset($microseconds[1])) {
51-
$microsecondsAndTimezone = explode('+', $microseconds[1], 2);
52-
if (count($microsecondsAndTimezone) === 1) {
53-
$microsecondsAndTimezone = explode('-', $microseconds[1], 2);
54-
}
55-
$timezone = isset($microsecondsAndTimezone[1]) ? sprintf('+%s', $microsecondsAndTimezone[1]) : '';
56-
$time = sprintf(
57-
"%s.%s%s",
58-
$microseconds[0],
59-
substr($microsecondsAndTimezone[0], 0, 6),
60-
$timezone
61-
);
62-
}
55+
[$fst, $snd] = explode('.', $time);
6356

64-
return $time;
65-
}
57+
// match the first n digits at the start
58+
\preg_match('/^\d+/', $snd, $matches);
6659

67-
private static function shiftWithTimezone(string $time, DateTimeImmutable $datetime): DateTimeImmutable
68-
{
69-
if (\strpos($time, '+') === false && \strpos($time, '-') === false && \strtoupper(\substr($time, -1)) !== 'Z') {
70-
return $datetime->setTimezone(new \DateTimeZone('UTC'));
71-
}
60+
$digits = $matches[0] ?? '';
7261

73-
return $datetime;
62+
// datetime portion + period + up to 6 digits + timezone string
63+
return $fst . '.' . substr($digits, 0, 6) . substr($snd, strlen($digits));
7464
}
7565
}

tests/Unit/Utilities/TimeFormatterTest.php

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ public function testEncode(): void
1919
);
2020
}
2121

22-
public static function providesDecodeCases(): array
22+
public static function providesValidDecodeCases(): array
2323
{
2424
return [
2525
// UTC
26+
['2018-04-05T17:31:00Z', '2018-04-05t17:31:00Z'],
2627
['2018-04-05T17:31:00Z', '2018-04-05T17:31:00Z'],
2728
['1985-04-12T23:20:50.100000Z', '1985-04-12T23:20:50.1Z'],
2829
['1985-04-12T23:20:50.100000Z', '1985-04-12T23:20:50.10Z'],
@@ -41,6 +42,7 @@ public static function providesDecodeCases(): array
4142
['1985-04-12T23:20:50.123456Z', '1985-04-12T23:20:50.123456789Z'],
4243

4344
// +01:00
45+
['2018-04-05T16:31:00Z', '2018-04-05t17:31:00+01:00'],
4446
['2018-04-05T16:31:00Z', '2018-04-05T17:31:00+01:00'],
4547
['1985-04-12T22:20:50.100000Z', '1985-04-12T23:20:50.1+01:00'],
4648
['1985-04-12T22:20:50.100000Z', '1985-04-12T23:20:50.10+01:00'],
@@ -57,12 +59,17 @@ public static function providesDecodeCases(): array
5759
['1985-04-12T22:20:50.123456Z', '1985-04-12T23:20:50.1234567+01:00'],
5860
['1985-04-12T22:20:50.123456Z', '1985-04-12T23:20:50.12345678+01:00'],
5961
['1985-04-12T22:20:50.123456Z', '1985-04-12T23:20:50.123456789+01:00'],
60-
['1985-04-12T22:20:50.123456Z', '1985-04-12T23:20:50.1234567890+01:00'],
62+
63+
// -05:00
64+
['2018-04-05T22:31:00Z', '2018-04-05t17:31:00-05:00'],
65+
['2018-04-05T22:31:00Z', '2018-04-05T17:31:00-05:00'],
66+
['1985-04-13T04:20:50.123456Z', '1985-04-12T23:20:50.123456-05:00'],
67+
['1985-04-13T04:20:50.123456Z', '1985-04-12T23:20:50.123456789-05:00'],
6168
];
6269
}
6370

6471
/**
65-
* @dataProvider providesDecodeCases
72+
* @dataProvider providesValidDecodeCases
6673
*/
6774
public function testDecode(string $expected, string $input): void
6875
{
@@ -88,14 +95,30 @@ public function testDecodeEmpty(): void
8895
);
8996
}
9097

91-
public function testDecodeInvalidTime(): void
98+
public static function providesInvalidDecodeCases(): array
99+
{
100+
return [
101+
[''],
102+
['123'],
103+
['2018asdsdsafd'],
104+
['2018-04-05'],
105+
['2018-04-05 17:31:00Z'],
106+
['2018-04-05T17:31:00.Z'],
107+
['2018-04-05T17:31:00ZZ'],
108+
];
109+
}
110+
111+
/**
112+
* @dataProvider providesInvalidDecodeCases
113+
*/
114+
public function testDecodeInvalidTime(string $input): void
92115
{
93116
$this->expectException(ValueError::class);
94117

95118
$this->expectExceptionMessage(
96119
'CloudEvents\\Utilities\\TimeFormatter::decode(): Argument #1 ($time) is not a valid RFC3339 timestamp'
97120
);
98121

99-
TimeFormatter::decode('2018asdsdsafd');
122+
TimeFormatter::decode($input);
100123
}
101124
}

0 commit comments

Comments
 (0)