diff --git a/README.md b/README.md index eb8bc4d..fb2e0e0 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,12 @@ $uuid8_first = UUID::uuid8(); echo $uuid8_first . "\n"; // e.g. 017f22e2-79b0-8cc3-98c4-dc0c0c07398f $uuid8_second = UUID::uuid8(); var_dump($uuid8_first < $uuid8_second); // bool(true) + +$firstDate = \DateTime::createFromFormat('Y-m-d H:i:s.u', '2024-01-01 13:12:12.129817'); +$uuid8_from_date = UUID::uuid8($firstDate); +$uuid8_zero_value = UUID::firstUuid8($firstDate) // 018cc527-3c61-8d12-9000-000000000000 first uuid from this time +$uuid8_last_value = UUID::lastUuid8($firstDate) // 018cc527-3c61-8d12-9fff-ffffffffffff last uuid from this time +var_dump(($uuid8_zero_value < $uuid8_from_date) && ($uuid8_from_date < $uuid8_last_value)); // bool(true) ``` ### Tools diff --git a/src/UUID.php b/src/UUID.php index afce715..15a36ad 100644 --- a/src/UUID.php +++ b/src/UUID.php @@ -253,11 +253,17 @@ public static function uuid7(): string * Generate a version 8 UUID. A v8 UUID is lexicographically sortable and is * designed to encode a Unix timestamp with arbitrary sub-second precision. * + * @param \DateTime|null $dateTime The time for which the uuid is generated * @return string The string standard representation of the UUID */ - public static function uuid8(): string + public static function uuid8(\DateTime|null $dateTime=null): string { - [$unixts, $subsec] = self::getUnixTimeSubsec(); + if(!is_null($dateTime)){ + $unixts = $dateTime->getTimestamp(); // Получаем секунды + $subsec = (int) ($dateTime->format('u') . "0"); // Получаем микросекунды + } else { + [$unixts, $subsec] = self::getUnixTimeSubsec(); + } $unixtsms = $unixts * 1000 + intdiv($subsec, self::V8_SUBSEC_RANGE); $subsec = self::encodeSubsec($subsec % self::V8_SUBSEC_RANGE); $subsecA = $subsec >> 2; @@ -270,6 +276,36 @@ public static function uuid8(): string return self::uuidFromHex($uhex, 8); } + /** + * The first uuid for a given time is generated, which allows you to use the v8 uuid for time search + * A v8 UUID is lexicographically sortable and is + * designed to encode a Unix timestamp with arbitrary sub-second precision. + * + * @param \DateTime|null $dateTime + * @return string + */ + public static function firstUuid8(\DateTime|null $dateTime=null): string + { + $uuid = self::uuid8($dateTime); + $zero = '000-000000000000'; + return substr_replace($uuid, $zero, -strlen($zero)); + } + + /** + * Generate The last uuid for the given time is generated, which allows you to use uuidv8 for time search. + * A v8 UUID is lexicographically sortable and is + * designed to encode a Unix timestamp with arbitrary sub-second precision. + * + * @param \DateTime|null $dateTime + * @return string + */ + public static function lastUuid8(\DateTime|null $dateTime=null): string + { + $uuid = self::uuid8($dateTime); + $last = 'fff-ffffffffffff'; + return substr_replace($uuid, $last, -strlen($last)); + } + /** * Check if a string is a valid UUID. * diff --git a/tests/UuidTest.php b/tests/UuidTest.php index 48d1b0f..a96636c 100644 --- a/tests/UuidTest.php +++ b/tests/UuidTest.php @@ -108,6 +108,84 @@ public function testCanGenerateValidVersion8() } } + public function testCanGenerateValidVersion8FromTime() + { + $firstDate = \DateTime::createFromFormat('Y-m-d H:i:s', '2024-01-01 13:12:12'); + $uuid1 = UUID::uuid8($firstDate); + for ($x = 0; $x < 1000; $x++) { + $this->assertMatchesRegularExpression( + '/^[0-9a-f]{8}\-[0-9a-f]{4}\-8[0-9a-f]{3}\-[89ab][0-9a-f]{3}\-[0-9a-f]{12}$/', + $uuid1 + ); + $secondDate = clone $firstDate; // Клонируем первый объект + $secondDate->modify('+524 milliseconds'); + + $uuid2 = UUID::uuid8($secondDate); + $this->assertGreaterThan( + $uuid1, + $uuid2 + ); + $this->assertLessThan( + 0, + UUID::cmp($uuid1, $uuid2) + ); + $uuid1 = $uuid2; + $firstDate = clone $secondDate; + } + } + + public function testCanGenerateValidVersion8ZeroValue() + { + $firstDate = \DateTime::createFromFormat('Y-m-d H:i:s.u', '2024-01-01 13:12:12.129817'); + for ($x = 0; $x < 1000; $x++) { + $uuidZero = UUID::firstUuid8($firstDate); + $this->assertMatchesRegularExpression( + '/^[0-9a-f]{8}\-[0-9a-f]{4}\-8[0-9a-f]{3}\-[89ab][0-9a-f]{3}\-[0-9a-f]{12}$/', + $uuidZero + ); + $uuid = UUID::uuid8($firstDate); + $this->assertGreaterThan( + $uuidZero, + $uuid + ); + $firstDate->modify('+564 milliseconds'); + + } + } + + public function testCanGenerateValidVersion8LastVersion() + { + $firstDate = \DateTime::createFromFormat('Y-m-d H:i:s.u', '2024-01-01 13:12:12.129817'); + for ($x = 0; $x < 1000; $x++) { + $uuidZero = UUID::lastUuid8($firstDate); + $this->assertMatchesRegularExpression( + '/^[0-9a-f]{8}\-[0-9a-f]{4}\-8[0-9a-f]{3}\-[89ab][0-9a-f]{3}\-[0-9a-f]{12}$/', + $uuidZero + ); + $uuid = UUID::uuid8($firstDate); + $this->assertGreaterThan( + $uuid, + $uuidZero + ); + $firstDate->modify('+564 milliseconds'); + + } + } + + public function testCheckValidDateVersion8() + { + $firstDate = \DateTime::createFromFormat('Y-m-d H:i:s', '2024-01-01 13:12:12'); + for ($x = 0; $x < 100; $x++) { + $timestamp = $firstDate->getTimestamp(); + $microseconds = $firstDate->format('u'); + $checkTimestamp = $timestamp + ($microseconds / 1000000); + $uuid = UUID::uuid8($firstDate); + $timeFromUuid = UUID::getTime($uuid); + $this->assertEquals($checkTimestamp,$timeFromUuid,"UUIDV8 get not true date"); + $firstDate->modify('+564 milliseconds'); + } + } + public function testCannotBeCreatedFromInvalidNamespace() { $this->expectException(\InvalidArgumentException::class);