diff --git a/README.md b/README.md index aacb82d..6548b2d 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ $event = new CloudEvent( source: 'https://example.com/user-service', id: uniqid(), subject: 'user-123', - time: date('c'), + time: CloudEvent::now(), datacontenttype: 'application/json', data: [ 'userId' => '123', @@ -51,6 +51,8 @@ $event = new CloudEvent( When using the constructor, only `type`, `source` and `id` are required. All other attributes are optional. Arrays passed to `CloudEvent::fromArray()` must also carry an explicit `specversion`. +`CloudEvent::now()` returns the current time as an RFC 3339 UTC timestamp with millisecond precision (e.g., `2025-11-07T10:00:00.123Z`), ready to use as the `time` attribute. + ### Converting to Array ```php diff --git a/src/CloudEvents/CloudEvent.php b/src/CloudEvents/CloudEvent.php index 0250d56..71bd6b5 100644 --- a/src/CloudEvents/CloudEvent.php +++ b/src/CloudEvents/CloudEvent.php @@ -2,6 +2,8 @@ namespace Utopia\CloudEvents; +use DateTimeImmutable; +use DateTimeZone; use InvalidArgumentException; use JsonException; @@ -11,6 +13,14 @@ */ class CloudEvent { + /** + * RFC 3339 (UTC, millisecond precision) format string. + * + * PHP's DATE_ATOM renders UTC as "+00:00" and carries no sub-second + * part, so it is not used here. + */ + public const TIME_FORMAT = 'Y-m-d\TH:i:s.v\Z'; + /** * Names reserved for core context attributes, which extension * attributes must not use. @@ -55,6 +65,19 @@ public function __construct( ) { } + /** + * Current time as an RFC 3339 UTC timestamp with milliseconds + * + * Produces e.g. "2025-11-07T10:00:00.123Z", which is what the time + * attribute expects. + * + * @return string + */ + public static function now(): string + { + return (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format(self::TIME_FORMAT); + } + /** * Create CloudEvent from array * diff --git a/tests/CloudEvents/CloudEventTest.php b/tests/CloudEvents/CloudEventTest.php index 1cde457..cb56aeb 100644 --- a/tests/CloudEvents/CloudEventTest.php +++ b/tests/CloudEvents/CloudEventTest.php @@ -431,6 +431,25 @@ public function testValidateWithoutTime(): void $this->assertTrue($event->validate()); } + public function testNow(): void + { + $time = CloudEvent::now(); + + $this->assertMatchesRegularExpression('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/', $time); + } + + public function testNowIsValidEventTime(): void + { + $event = new CloudEvent( + type: 'test.event', + source: 'test-service', + id: 'test-id', + time: CloudEvent::now() + ); + + $this->assertTrue($event->validate()); + } + public function testExtensions(): void { $event = new CloudEvent(