From 58a9c4351d8b5a1b47f6d3a615abbd0ed50cd44f Mon Sep 17 00:00:00 2001 From: Sebastian Krupinski Date: Sat, 4 Jul 2026 18:21:10 -0400 Subject: [PATCH] refactor: use epoch timestamp Signed-off-by: Sebastian Krupinski --- .../Provider/Node/NodeBaseAbstract.php | 9 +- .../Provider/Node/NodeMutableAbstract.php | 16 ++-- shared/lib/Resource/Range/IRangeDate.php | 10 +-- shared/lib/Resource/Range/RangeDate.php | 25 +++--- shared/lib/Utile/Timestamp.php | 88 +++++++++++++++++++ 5 files changed, 120 insertions(+), 28 deletions(-) create mode 100644 shared/lib/Utile/Timestamp.php diff --git a/shared/lib/Resource/Provider/Node/NodeBaseAbstract.php b/shared/lib/Resource/Provider/Node/NodeBaseAbstract.php index 79fc9b2..cff083b 100644 --- a/shared/lib/Resource/Provider/Node/NodeBaseAbstract.php +++ b/shared/lib/Resource/Provider/Node/NodeBaseAbstract.php @@ -15,6 +15,7 @@ use KTXF\Resource\Identifier\CollectionIdentifierInterface; use KTXF\Resource\Identifier\EntityIdentifierInterface; use KTXF\Resource\Identifier\ResourceIdentifier; use KTXF\Resource\Identifier\ServiceIdentifier; +use KTXF\Utile\Timestamp; /** * Abstract Node Base Class @@ -119,18 +120,14 @@ abstract class NodeBaseAbstract implements NodeBaseInterface { * @inheritDoc */ public function created(): DateTimeImmutable|null { - return isset($this->data[static::PROPERTY_CREATED]) - ? new DateTimeImmutable($this->data[static::PROPERTY_CREATED]) - : null; + return Timestamp::toDateTime($this->data[static::PROPERTY_CREATED] ?? null); } /** * @inheritDoc */ public function modified(): DateTimeImmutable|null { - return isset($this->data[static::PROPERTY_MODIFIED]) - ? new DateTimeImmutable($this->data[static::PROPERTY_MODIFIED]) - : null; + return Timestamp::toDateTime($this->data[static::PROPERTY_MODIFIED] ?? null); } /** diff --git a/shared/lib/Resource/Provider/Node/NodeMutableAbstract.php b/shared/lib/Resource/Provider/Node/NodeMutableAbstract.php index 7634d53..4f4f93a 100644 --- a/shared/lib/Resource/Provider/Node/NodeMutableAbstract.php +++ b/shared/lib/Resource/Provider/Node/NodeMutableAbstract.php @@ -9,6 +9,8 @@ declare(strict_types=1); namespace KTXF\Resource\Provider\Node; +use KTXF\Utile\Timestamp; + /** * Abstract Node Mutable Class * @@ -56,19 +58,21 @@ abstract class NodeMutableAbstract extends NodeBaseAbstract implements NodeMutab } if (isset($data[static::PROPERTY_CREATED])) { - if (!is_string($data[static::PROPERTY_CREATED])) { - throw new \InvalidArgumentException("Created date must be a string in ISO 8601 format"); + $created = Timestamp::normalize($data[static::PROPERTY_CREATED]); + if ($created === null) { + throw new \InvalidArgumentException("Created date must be a valid epoch millisecond or ISO 8601 date"); } - $this->data[static::PROPERTY_CREATED] = $data[static::PROPERTY_CREATED]; + $this->data[static::PROPERTY_CREATED] = $created; } else { $this->data[static::PROPERTY_CREATED] = null; } if (isset($data[static::PROPERTY_MODIFIED])) { - if (!is_string($data[static::PROPERTY_MODIFIED])) { - throw new \InvalidArgumentException("Modified date must be a string in ISO 8601 format"); + $modified = Timestamp::normalize($data[static::PROPERTY_MODIFIED]); + if ($modified === null) { + throw new \InvalidArgumentException("Modified date must be a valid epoch millisecond or ISO 8601 date"); } - $this->data[static::PROPERTY_MODIFIED] = $data[static::PROPERTY_MODIFIED]; + $this->data[static::PROPERTY_MODIFIED] = $modified; } else { $this->data[static::PROPERTY_MODIFIED] = null; } diff --git a/shared/lib/Resource/Range/IRangeDate.php b/shared/lib/Resource/Range/IRangeDate.php index c9b221f..ace5d38 100644 --- a/shared/lib/Resource/Range/IRangeDate.php +++ b/shared/lib/Resource/Range/IRangeDate.php @@ -9,32 +9,30 @@ declare(strict_types=1); namespace KTXF\Resource\Range; -use DateTimeInterface; - interface IRangeDate extends IRange { /** * * @since 1.0.0 */ - public function getStart(): DateTimeInterface; + public function getStart(): int; /** * * @since 1.0.0 */ - public function setStart(DateTimeInterface $value): void; + public function setStart(int $value): void; /** * * @since 1.0.0 */ - public function getEnd(): DateTimeInterface; + public function getEnd(): int; /** * * @since 1.0.0 */ - public function setEnd(DateTimeInterface $value): void; + public function setEnd(int $value): void; } diff --git a/shared/lib/Resource/Range/RangeDate.php b/shared/lib/Resource/Range/RangeDate.php index 03d27c1..0344eb9 100644 --- a/shared/lib/Resource/Range/RangeDate.php +++ b/shared/lib/Resource/Range/RangeDate.php @@ -9,13 +9,12 @@ declare(strict_types=1); namespace KTXF\Resource\Range; -use DateTimeImmutable; -use DateTimeInterface; +use KTXF\Utile\Timestamp; class RangeDate extends Range implements IRangeDate { - protected DateTimeInterface $start; - protected DateTimeInterface $end; + protected int $start; + protected int $end; public function jsonDeserialize(array|string $data): static { if (is_string($data)) { @@ -23,10 +22,16 @@ class RangeDate extends Range implements IRangeDate { } if (isset($data['start'])) { - $this->setStart(new DateTimeImmutable($data['start'])); + $start = Timestamp::normalize($data['start']); + if ($start !== null) { + $this->setStart($start); + } } if (isset($data['end'])) { - $this->setEnd(new DateTimeImmutable($data['end'])); + $end = Timestamp::normalize($data['end']); + if ($end !== null) { + $this->setEnd($end); + } } return $this; @@ -46,7 +51,7 @@ class RangeDate extends Range implements IRangeDate { * * @since 1.0.0 */ - public function getStart(): DateTimeInterface { + public function getStart(): int { return $this->start; } @@ -55,7 +60,7 @@ class RangeDate extends Range implements IRangeDate { * * @since 1.0.0 */ - public function setStart(DateTimeInterface $value): void { + public function setStart(int $value): void { $this->start = $value; } @@ -64,7 +69,7 @@ class RangeDate extends Range implements IRangeDate { * * @since 1.0.0 */ - public function getEnd(): DateTimeInterface { + public function getEnd(): int { return $this->end; } @@ -73,7 +78,7 @@ class RangeDate extends Range implements IRangeDate { * * @since 1.0.0 */ - public function setEnd(DateTimeInterface $value): void { + public function setEnd(int $value): void { $this->end = $value; } diff --git a/shared/lib/Utile/Timestamp.php b/shared/lib/Utile/Timestamp.php new file mode 100644 index 0000000..14f8917 --- /dev/null +++ b/shared/lib/Utile/Timestamp.php @@ -0,0 +1,88 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace KTXF\Utile; + +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; + +final class Timestamp { + + public static function normalize(mixed $value): ?int { + if ($value instanceof DateTimeInterface) { + return self::toEpoch($value); + } + + if (is_int($value)) { + return $value; + } + + if (is_float($value)) { + return (int)$value; + } + + if (is_string($value)) { + if (is_numeric($value)) { + return (int)$value; + } + + try { + return self::toEpoch(new DateTimeImmutable($value)); + } catch (\Exception) { + return null; + } + } + + return null; + } + + public static function toEpoch(DateTimeInterface $moment): int { + $utcMoment = DateTimeImmutable::createFromInterface($moment) + ->setTimezone(new DateTimeZone('UTC')); + return ((int)$utcMoment->format('U') * 1000) + (int)$utcMoment->format('v'); + } + + public static function toDateTime(mixed $value): ?DateTimeImmutable { + if ($value instanceof DateTimeImmutable) { + return $value; + } + + if ($value instanceof DateTimeInterface) { + return DateTimeImmutable::createFromInterface($value); + } + + if (is_int($value) || is_float($value) || (is_string($value) && is_numeric($value))) { + $milliseconds = (int)$value; + $seconds = intdiv($milliseconds, 1000); + $microseconds = ($milliseconds % 1000) * 1000; + return DateTimeImmutable::createFromFormat( + 'U u', + sprintf('%d %06d', $seconds, $microseconds), + new DateTimeZone('UTC') + ) ?: null; + } + + if (is_string($value) && trim($value) !== '') { + try { + return new DateTimeImmutable($value); + } catch (\Exception) { + return null; + } + } + + return null; + } + + public static function toIso8601(int $value): ?string { + $moment = self::toDateTime($value); + return $moment?->format('Y-m-d\TH:i:s.v\Z'); + } + +}