From 5a170b80ed904813f0d085d9439b9fcfe6be7654 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 12 Mar 2020 18:51:21 +0100 Subject: [PATCH] [Uid] make Uuid::getTime() return subseconds info --- src/Symfony/Component/Uid/InternalUtil.php | 85 ++++++++++++++++++++ src/Symfony/Component/Uid/Tests/UuidTest.php | 2 +- src/Symfony/Component/Uid/Uuid.php | 20 ++++- 3 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 src/Symfony/Component/Uid/InternalUtil.php diff --git a/src/Symfony/Component/Uid/InternalUtil.php b/src/Symfony/Component/Uid/InternalUtil.php new file mode 100644 index 0000000000..a63e15b778 --- /dev/null +++ b/src/Symfony/Component/Uid/InternalUtil.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid; + +/** + * @internal + * + * @author Nicolas Grekas + */ +class InternalUtil +{ + public static function toBinary(string $digits): string + { + $bytes = ''; + $len = \strlen($digits); + + while ($len > $i = strspn($digits, '0')) { + for ($j = 2, $r = 0; $i < $len; $i += $j, $j = 0) { + do { + $r *= 10; + $d = (int) substr($digits, $i, ++$j); + } while ($i + $j < $len && $r + $d < 256); + + $j = \strlen((string) $d); + $q = str_pad(($d += $r) >> 8, $j, '0', STR_PAD_LEFT); + $digits = substr_replace($digits, $q, $i, $j); + $r = $d % 256; + } + + $bytes .= \chr($r); + } + + return strrev($bytes); + } + + public static function toDecimal(string $bytes): string + { + $digits = ''; + $len = \strlen($bytes); + + while ($len > $i = strspn($bytes, "\0")) { + for ($r = 0; $i < $len; $i += $j) { + $j = $d = 0; + do { + $r <<= 8; + $d = ($d << 8) + \ord($bytes[$i + $j]); + } while ($i + ++$j < $len && $r + $d < 10); + + if (256 < $d) { + $q = intdiv($d += $r, 10); + $bytes[$i] = \chr($q >> 8); + $bytes[1 + $i] = \chr($q & 0xFF); + } else { + $bytes[$i] = \chr(intdiv($d += $r, 10)); + } + $r = $d % 10; + } + + $digits .= (string) $r; + } + + return strrev($digits); + } + + public static function binaryAdd(string $a, string $b): string + { + $sum = 0; + for ($i = 7; 0 <= $i; --$i) { + $sum += \ord($a[$i]) + \ord($b[$i]); + $a[$i] = \chr($sum & 0xFF); + $sum >>= 8; + } + + return $a; + } +} diff --git a/src/Symfony/Component/Uid/Tests/UuidTest.php b/src/Symfony/Component/Uid/Tests/UuidTest.php index e8a3a346a2..fb431e0089 100644 --- a/src/Symfony/Component/Uid/Tests/UuidTest.php +++ b/src/Symfony/Component/Uid/Tests/UuidTest.php @@ -117,7 +117,7 @@ class UuidTest extends TestCase $uuid = new Uuid(self::A_UUID_V1); $this->assertSame(Uuid::VARIANT_DCE, $uuid->getVariant()); - $this->assertSame(1583245966, $uuid->getTime()); + $this->assertSame(1583245966.746458, $uuid->getTime()); $this->assertSame('3499710062d0', $uuid->getMac()); $this->assertSame(self::A_UUID_V1, (string) $uuid); } diff --git a/src/Symfony/Component/Uid/Uuid.php b/src/Symfony/Component/Uid/Uuid.php index a86971eb5b..a6dd3536f3 100644 --- a/src/Symfony/Component/Uid/Uuid.php +++ b/src/Symfony/Component/Uid/Uuid.php @@ -28,6 +28,12 @@ class Uuid implements \JsonSerializable public const VARIANT_MICROSOFT = UUID_VARIANT_MICROSOFT; public const VARIANT_OTHER = UUID_VARIANT_OTHER; + // https://tools.ietf.org/html/rfc4122#section-4.1.4 + // 0x01b21dd213814000 is the number of 100-ns intervals between the + // UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00. + private const TIME_OFFSET_INT = 0x01b21dd213814000; + private const TIME_OFFSET_COM = "\xfe\x4d\xe2\x2d\xec\x7e\xc0\x00"; + private $uuid; public function __construct(string $uuid = null) @@ -105,13 +111,23 @@ class Uuid implements \JsonSerializable return uuid_variant($this->uuid); } - public function getTime(): int + public function getTime(): float { if (self::TYPE_1 !== $t = uuid_type($this->uuid)) { throw new \LogicException("UUID of type $t doesn't contain a time."); } - return uuid_time($this->uuid); + $time = '0'.substr($this->uuid, 15, 3).substr($this->uuid, 9, 4).substr($this->uuid, 0, 8); + + if (\PHP_INT_SIZE >= 8) { + return (hexdec($time) - self::TIME_OFFSET_INT) / 10000000; + } + + $time = str_pad(hex2bin($time), 8, "\0", STR_PAD_LEFT); + $time = InternalUtil::binaryAdd($time, self::TIME_OFFSET_COM); + $time[0] = $time[0] & "\x7F"; + + return InternalUtil::toDecimal($time) / 10000000; } public function getMac(): string