* 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'); } }