Skip to content

Commit 45c5e1b

Browse files
Enable configuration of subsecond precision of timestamps (#54)
Signed-off-by: Graham Campbell <[email protected]>
1 parent 7e18f33 commit 45c5e1b

File tree

6 files changed

+102
-13
lines changed

6 files changed

+102
-13
lines changed

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@
4848
"DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:7.4-tests -f hack/7.4.Dockerfile hack",
4949
"DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:8.0-tests -f hack/8.0.Dockerfile hack",
5050
"DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:8.1-tests -f hack/8.1.Dockerfile hack",
51-
"DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:8.2-tests -f hack/8.1.Dockerfile hack",
52-
"DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:8.3-tests -f hack/8.1.Dockerfile hack"
51+
"DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:8.2-tests -f hack/8.2.Dockerfile hack",
52+
"DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:8.3-tests -f hack/8.3.Dockerfile hack"
5353
],
5454
"tests-docker": [
5555
"docker run -it -v $(pwd):/var/www cloudevents/sdk-php:7.4-tests --coverage-html=coverage",
@@ -76,7 +76,7 @@
7676
},
7777
"extra": {
7878
"branch-alias": {
79-
"dev-main": "1.0-dev"
79+
"dev-main": "1.1-dev"
8080
}
8181
},
8282
"minimum-stability": "dev",

src/Serializers/Normalizers/V1/Normalizer.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,26 @@
1010

1111
final class Normalizer implements NormalizerInterface
1212
{
13+
/**
14+
* @var array{subsecondPrecision?: int<0, 6>}
15+
*/
16+
private array $configuration;
17+
18+
/**
19+
* @param array{subsecondPrecision?: int<0, 6>} $configuration
20+
*/
21+
public function __construct(array $configuration = [])
22+
{
23+
$this->configuration = $configuration;
24+
}
25+
1326
/**
1427
* @return array<string, mixed>
1528
*/
1629
public function normalize(CloudEventInterface $cloudEvent, bool $rawData): array
1730
{
1831
return array_merge(
19-
AttributeConverter::toArray($cloudEvent),
32+
AttributeConverter::toArray($cloudEvent, $this->configuration),
2033
DataFormatter::encode($cloudEvent->getData(), $rawData)
2134
);
2235
}

src/Utilities/AttributeConverter.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
final class AttributeConverter
1414
{
1515
/**
16+
* @param array{subsecondPrecision?: int<0, 6>} $configuration
17+
*
1618
* @return array<string, bool|int|string>
1719
*/
18-
public static function toArray(CloudEventInterface $cloudEvent): array
20+
public static function toArray(CloudEventInterface $cloudEvent, array $configuration): array
1921
{
2022
/** @var array<string, bool|int|string> */
2123
$attributes = array_filter([
@@ -26,7 +28,7 @@ public static function toArray(CloudEventInterface $cloudEvent): array
2628
'datacontenttype' => $cloudEvent->getDataContentType(),
2729
'dataschema' => $cloudEvent->getDataSchema(),
2830
'subject' => $cloudEvent->getSubject(),
29-
'time' => TimeFormatter::encode($cloudEvent->getTime()),
31+
'time' => TimeFormatter::encode($cloudEvent->getTime(), $configuration['subsecondPrecision'] ?? 0),
3032
], fn ($attr) => $attr !== null);
3133

3234
return array_merge($attributes, $cloudEvent->getExtensions());

src/Utilities/TimeFormatter.php

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,41 @@
1313
*/
1414
final class TimeFormatter
1515
{
16-
private const TIME_FORMAT = 'Y-m-d\TH:i:s\Z';
16+
private const TIME_FORMAT = 'Y-m-d\TH:i:s';
17+
private const TIME_FORMAT_EXTENDED = 'Y-m-d\TH:i:s.u';
1718
private const TIME_ZONE = 'UTC';
1819

1920
private const RFC3339_FORMAT = 'Y-m-d\TH:i:sP';
2021
private const RFC3339_EXTENDED_FORMAT = 'Y-m-d\TH:i:s.uP';
2122

22-
public static function encode(?DateTimeImmutable $time): ?string
23+
/**
24+
* @param int<0, 6> $subsecondPrecision
25+
*/
26+
public static function encode(?DateTimeImmutable $time, int $subsecondPrecision): ?string
2327
{
2428
if ($time === null) {
2529
return null;
2630
}
2731

28-
return $time->setTimezone(new DateTimeZone(self::TIME_ZONE))->format(self::TIME_FORMAT);
32+
return sprintf('%sZ', self::encodeWithoutTimezone($time, $subsecondPrecision));
33+
}
34+
35+
/**
36+
* @param int<0, 6> $subsecondPrecision
37+
*/
38+
private static function encodeWithoutTimezone(DateTimeImmutable $time, int $subsecondPrecision): string
39+
{
40+
$utcTime = $time->setTimezone(new DateTimeZone(self::TIME_ZONE));
41+
42+
if ($subsecondPrecision <= 0) {
43+
return $utcTime->format(self::TIME_FORMAT);
44+
}
45+
46+
if ($subsecondPrecision >= 6) {
47+
return $utcTime->format(self::TIME_FORMAT_EXTENDED);
48+
}
49+
50+
return substr($utcTime->format(self::TIME_FORMAT_EXTENDED), 0, $subsecondPrecision - 6);
2951
}
3052

3153
public static function decode(?string $time): ?DateTimeImmutable

tests/Unit/Serializers/Normalizers/V1/NormalizerTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,40 @@ public function testNormalizerWithUnsetAttributes(): void
8080
$formatter->normalize($event, false)
8181
);
8282
}
83+
84+
public function testNormalizerWithSubsecondPrecisionConfiguration(): void
85+
{
86+
/** @var CloudEventInterface|Stub $event */
87+
$event = $this->createStub(CloudEventInterface::class);
88+
$event->method('getSpecVersion')->willReturn('1.0');
89+
$event->method('getId')->willReturn('1234-1234-1234');
90+
$event->method('getSource')->willReturn('/var/data');
91+
$event->method('getType')->willReturn('com.example.someevent');
92+
$event->method('getDataContentType')->willReturn('application/json');
93+
$event->method('getDataSchema')->willReturn('com.example/schema');
94+
$event->method('getSubject')->willReturn('larger-context');
95+
$event->method('getTime')->willReturn(new DateTimeImmutable('2018-04-05T17:31:00.123456Z'));
96+
$event->method('getData')->willReturn(['key' => 'value']);
97+
$event->method('getExtensions')->willReturn(['comacme' => 'foo']);
98+
99+
$formatter = new Normalizer(['subsecondPrecision' => 3]);
100+
101+
self::assertSame(
102+
[
103+
'specversion' => '1.0',
104+
'id' => '1234-1234-1234',
105+
'source' => '/var/data',
106+
'type' => 'com.example.someevent',
107+
'datacontenttype' => 'application/json',
108+
'dataschema' => 'com.example/schema',
109+
'subject' => 'larger-context',
110+
'time' => '2018-04-05T17:31:00.123Z',
111+
'comacme' => 'foo',
112+
'data' => [
113+
'key' => 'value',
114+
],
115+
],
116+
$formatter->normalize($event, false)
117+
);
118+
}
83119
}

tests/Unit/Utilities/TimeFormatterTest.php

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,27 @@
1111

1212
class TimeFormatterTest extends TestCase
1313
{
14-
public function testEncode(): void
14+
public static function providesValidEncodeCases(): array
15+
{
16+
return [
17+
['2018-04-05T17:31:00Z', '2018-04-05T17:31:00.123456Z', 0],
18+
['2018-04-05T17:31:00.1Z', '2018-04-05T17:31:00.123456Z', 1],
19+
['2018-04-05T17:31:00.12Z', '2018-04-05T17:31:00.123456Z', 2],
20+
['2018-04-05T17:31:00.123Z', '2018-04-05T17:31:00.123456Z', 3],
21+
['2018-04-05T17:31:00.1234Z', '2018-04-05T17:31:00.123456Z', 4],
22+
['2018-04-05T17:31:00.12345Z', '2018-04-05T17:31:00.123456Z', 5],
23+
['2018-04-05T17:31:00.123456Z', '2018-04-05T17:31:00.123456Z', 6],
24+
];
25+
}
26+
27+
/**
28+
* @dataProvider providesValidEncodeCases
29+
*/
30+
public function testEncode(string $expected, string $input, int $subsecondPrecision): void
1531
{
1632
self::assertEquals(
17-
'2018-04-05T17:31:00Z',
18-
TimeFormatter::encode(new DateTimeImmutable('2018-04-05T17:31:00Z'))
33+
$expected,
34+
TimeFormatter::encode(new DateTimeImmutable($input), $subsecondPrecision)
1935
);
2036
}
2137

@@ -83,7 +99,7 @@ public function testEncodeEmpty(): void
8399
{
84100
self::assertEquals(
85101
null,
86-
TimeFormatter::encode(null)
102+
TimeFormatter::encode(null, 0)
87103
);
88104
}
89105

0 commit comments

Comments
 (0)