From 4f4deb1dd6ca3f9522093c98327fde676bc4dbe7 Mon Sep 17 00:00:00 2001 From: Ben Ramsey Date: Wed, 14 Sep 2022 18:21:10 -0500 Subject: [PATCH] feat: add UUIDv7 documentation and customization --- docs/customize/ordered-time-codec.rst | 8 +- docs/customize/timestamp-first-comb-codec.rst | 7 + docs/database.rst | 23 ++- docs/introduction.rst | 7 +- docs/quickstart.rst | 2 + docs/reference.rst | 1 + docs/reference/rfc4122-uuidv7.rst | 20 ++ docs/rfc4122.rst | 16 +- docs/rfc4122/version1.rst | 6 + docs/rfc4122/version6.rst | 8 +- docs/rfc4122/version7.rst | 173 ++++++++++++++++++ src/Uuid.php | 8 +- src/UuidFactory.php | 27 ++- src/functions.php | 15 ++ .../Converter/Time/UnixTimeConverterTest.php | 4 +- tests/FunctionsTest.php | 39 +++- tests/UuidTest.php | 15 ++ 17 files changed, 348 insertions(+), 31 deletions(-) create mode 100644 docs/reference/rfc4122-uuidv7.rst create mode 100644 docs/rfc4122/version7.rst diff --git a/docs/customize/ordered-time-codec.rst b/docs/customize/ordered-time-codec.rst index b71a7eb..a25c5c7 100644 --- a/docs/customize/ordered-time-codec.rst +++ b/docs/customize/ordered-time-codec.rst @@ -4,10 +4,12 @@ Ordered-time Codec ================== -.. hint:: +.. attention:: - :ref:`Version 6, reordered time UUIDs ` are a - new version of UUID that take the place of ordered-time UUIDs. + :ref:`Version 6, reordered time UUIDs ` are a new version + of UUID that eliminate the need for the ordered-time codec. If you aren't + currently using the ordered-time codec, and you need time-based, sortable + UUIDs, consider using version 6 UUIDs. UUIDs arrange their bytes according to the standard recommended by `RFC 4122`_. Unfortunately, this means the bytes aren't in an arrangement that supports diff --git a/docs/customize/timestamp-first-comb-codec.rst b/docs/customize/timestamp-first-comb-codec.rst index 00f3377..5ca42a9 100644 --- a/docs/customize/timestamp-first-comb-codec.rst +++ b/docs/customize/timestamp-first-comb-codec.rst @@ -4,6 +4,13 @@ Timestamp-first COMB Codec ========================== +.. attention:: + + :ref:`Version 7, Unix Epoch time UUIDs ` are a new version + of UUID that eliminate the need for the timestamp-first COMB codec. If you + aren't currently using the timestamp-first COMB codec, and you need + time-based, sortable UUIDs, consider using version 7 UUIDs. + :ref:`Version 4, random UUIDs ` are doubly problematic when it comes to sorting and storing to databases (see :ref:`database.order`), since their values are random, and there is no timestamp associated with them that may diff --git a/docs/database.rst b/docs/database.rst index 9459862..ee401e3 100644 --- a/docs/database.rst +++ b/docs/database.rst @@ -229,29 +229,28 @@ small. Insertion Order and Sorting ########################### -UUIDs are not *monotonically increasing*. Even time-based UUIDs are not. If -using UUIDs as primary keys, the inserts will be random, and the data will be +UUID versions 1, 2, 3, 4, and 5 are not *monotonically increasing*. If using +these versions as primary keys, the inserts will be random, and the data will be scattered on disk (for InnoDB). Over time, as the database size grows, lookups will become slower and slower. -.. note:: +.. tip:: See Percona's "`Storing UUID Values in MySQL`_" post, for more details on the performance of UUIDs as primary keys. To minimize these problems, two solutions have been devised: -1. Timestamp first COMBs -2. Ordered-time UUIDs +1. :ref:`rfc4122.version6` UUIDs +2. :ref:`rfc4122.version7` UUIDs -:ref:`customize.timestamp-first-comb-codec` explains the first solution and how -to use ramsey/uuid to implement it, while :ref:`customize.ordered-time-codec` -explains how to use ramsey/uuid to implement the second solution. +.. note:: -.. hint:: - - :ref:`Version 6, reordered time UUIDs ` are a - new version of UUID that take the place of ordered-time UUIDs. + We previously recommended the use of the :ref:`timestamp-first COMB + ` or :ref:`ordered-time + ` codecs to solve these problems. However, + UUID versions 6 and 7 were defined to provide these solutions in a + standardized way. .. _ramsey/uuid-doctrine: https://github.com/ramsey/uuid-doctrine diff --git a/docs/introduction.rst b/docs/introduction.rst index 380f66b..5cb2dbd 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -5,9 +5,9 @@ Introduction ============ ramsey/uuid is a PHP library for generating and working with `RFC 4122`_ version -1, 2, 3, 4, and 5 universally unique identifiers (UUID). ramsey/uuid also -supports optional and non-standard features, such as `version 6 UUIDs`_, -GUIDs, and other approaches for encoding/decoding UUIDs. +1, 2, 3, 4, 5, 6, and 7 universally unique identifiers (UUID). ramsey/uuid also +supports optional and non-standard features, such as GUIDs and other approaches +for encoding/decoding UUIDs. What Is a UUID? ############### @@ -29,4 +29,3 @@ UUIDs can also be stored in binary format, as a string of 16 bytes. .. _RFC 4122: https://tools.ietf.org/html/rfc4122 -.. _version 6 UUIDs: http://gh.peabody.io/uuidv6/ diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 871fd1c..bdc310e 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -97,6 +97,8 @@ library. - This generates a :ref:`rfc4122.version5` UUID. * - :php:meth:`Uuid::uuid6() ` - This generates a :ref:`rfc4122.version6` UUID. + * - :php:meth:`Uuid::uuid7() ` + - This generates a :ref:`rfc4122.version7` UUID. * - :php:meth:`Uuid::isValid() ` - Checks whether a string is a valid UUID. * - :php:meth:`Uuid::fromString() ` diff --git a/docs/reference.rst b/docs/reference.rst index b04e1bd..55d5c9e 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -18,6 +18,7 @@ Reference reference/rfc4122-uuidv4 reference/rfc4122-uuidv5 reference/rfc4122-uuidv6 + reference/rfc4122-uuidv7 reference/guid-fields reference/guid-guid reference/nonstandard-fields diff --git a/docs/reference/rfc4122-uuidv7.rst b/docs/reference/rfc4122-uuidv7.rst new file mode 100644 index 0000000..e4041a8 --- /dev/null +++ b/docs/reference/rfc4122-uuidv7.rst @@ -0,0 +1,20 @@ +.. _reference.rfc4122.uuidv7: + +=============== +Rfc4122\\UuidV7 +=============== + +.. php:namespace:: Ramsey\Uuid\Rfc4122 + +.. php:class:: UuidV7 + + Implements :php:interface:`Ramsey\\Uuid\\Rfc4122\\UuidInterface`. + + UuidV7 represents a :ref:`version 7, Unix Epoch time UUID `. + In addition to providing the methods defined on the interface, this class + additionally provides the following methods. + + .. php:method:: getDateTime() + + :returns: A date object representing the timestamp associated with the UUID. + :returntype: ``\DateTimeInterface`` diff --git a/docs/rfc4122.rst b/docs/rfc4122.rst index d6c19d6..2520f97 100644 --- a/docs/rfc4122.rst +++ b/docs/rfc4122.rst @@ -14,11 +14,12 @@ RFC 4122 UUIDs rfc4122/version4 rfc4122/version5 rfc4122/version6 + rfc4122/version7 -`RFC 4122`_ defines five versions of UUID. Each version has different generation -algorithms and properties. Which one you choose to use depends on your use-case. -You can find out more about their applications on the specific page for that -version. +`RFC 4122`_ defines five versions of UUID, while a `new Internet-Draft under +review`_ defines three new versions. Each version has different generation +algorithms and properties. Which one you choose depends on your use-case. You +can find out more about their applications on the specific page for that version. Version 1: Gregorian Time This version of UUID combines a timestamp, node value (in the form of a MAC @@ -50,5 +51,12 @@ Version 6: Reordered Time :ref:`version 1 UUID ` with a *monotonically increasing* UUID. For more details, see :ref:`rfc4122.version6`. +Version 7: Unix Epoch Time + This version of UUID combines a timestamp--based on milliseconds elapsed + since the Unix Epoch--and random bytes to create a monotonically increasing, + sortable UUID without the privacy and entropy concerns associated with + version 1 and version 6 UUIDs. For more details, see :ref:`rfc4122.version7`. + .. _RFC 4122: https://tools.ietf.org/html/rfc4122 +.. _new Internet-Draft under review: https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04 diff --git a/docs/rfc4122/version1.rst b/docs/rfc4122/version1.rst index 8ac3147..b71a196 100644 --- a/docs/rfc4122/version1.rst +++ b/docs/rfc4122/version1.rst @@ -4,6 +4,12 @@ Version 1: Gregorian Time ========================= +.. attention:: + + If you need a time-based UUID, and you don't need the other features + included in version 1 UUIDs, we recommend using + :ref:`version 7 UUIDs `. + A version 1 UUID uses the current time, along with the MAC address (or *node*) for a network interface on the local machine. This serves two purposes: diff --git a/docs/rfc4122/version6.rst b/docs/rfc4122/version6.rst index 49e3ccf..e4b79bb 100644 --- a/docs/rfc4122/version6.rst +++ b/docs/rfc4122/version6.rst @@ -11,6 +11,12 @@ Version 6: Reordered Time through the IETF process, the version 6 format is not expected to change in any way that breaks compatibility. +.. attention:: + + If you need a time-based UUID, and you don't need the other features + included in version 6 UUIDs, we recommend using + :ref:`version 7 UUIDs `. + Version 6 UUIDs solve `two problems that have long existed`_ with the use of :ref:`version 1 ` UUIDs: @@ -199,7 +205,7 @@ machines, see :ref:`rfc4122.version6.nodes`. If you do not need an identifier with a node value embedded in it, but you still need the benefit of a monotonically increasing unique identifier, see -:ref:`customize.timestamp-first-comb-codec`. +:ref:`rfc4122.version7`. .. _Internet-Draft under review: https://datatracker.ietf.org/doc/draft-peabody-dispatch-new-uuid-format/ diff --git a/docs/rfc4122/version7.rst b/docs/rfc4122/version7.rst new file mode 100644 index 0000000..1143c7c --- /dev/null +++ b/docs/rfc4122/version7.rst @@ -0,0 +1,173 @@ +.. _rfc4122.version7: + +========================== +Version 7: Unix Epoch Time +========================== + +.. note:: + + Version 7, Unix Epoch time UUIDs are a new format of UUID, proposed in an + `Internet-Draft under review`_ at the IETF. While the draft is still going + through the IETF process, the version 7 format is not expected to change + in any way that breaks compatibility. + +.. admonition:: ULIDs and Version 7 UUIDs + :class: hint + + Version 7 UUIDs are binary-compatible with `ULIDs`_ (universally unique + lexicographically-sortable identifiers). + + Both use a 48-bit timestamp in milliseconds since the Unix Epoch, filling + the rest with random data. Version 7 UUIDs then add the version and variant + bits required by the UUID specification, which reduces the randomness from + 80 bits to 74. Otherwise, they are identical. + + You may even convert a version 7 UUID to a ULID. + :ref:`See below for an example. ` + +Version 7 UUIDs solve `two problems that have long existed`_ with the use of +:ref:`version 1 ` UUIDs: + +1. Scattered database records +2. Inability to sort by an identifier in a meaningful way (i.e., insert order) + +To overcome these issues, we need the ability to generate UUIDs that are +*monotonically increasing*. + +:ref:`Version 6 UUIDs ` provide an excellent solution for +those who need monotonically increasing, sortable UUIDs with the features of +version 1 UUIDs (MAC address and clock sequence), but if those features aren't +necessary for your application, using a version 6 UUID might be overkill. + +Version 7 UUIDs combine random data (like version 4 UUIDs) with a timestamp (in +milliseconds since the Unix Epoch, i.e., 1970-01-01 00:00:00 UTC) to create a +monotonically increasing, sortable UUID that doesn't have any privacy concerns, +since it doesn't include a MAC address. + +For this reason, implementations should use version 7 UUIDs over versions 1 and +6, if possible. + +.. code-block:: php + :caption: Generate a version 7, Unix Epoch time UUID + :name: rfc4122.version7.example + + use Ramsey\Uuid\Uuid; + + $uuid = Uuid::uuid7(); + + printf( + "UUID: %s\nVersion: %d\nDate: %s\n", + $uuid->toString(), + $uuid->getFields()->getVersion(), + $uuid->getDateTime()->format('r'), + ); + +This will generate a version 7 UUID and print out its string representation and +the time it was created. + +It will look something like this: + +.. code-block:: text + + UUID: 01833ce0-3486-7bfd-84a1-ad157cf64005 + Version: 7 + Date: Wed, 14 Sep 2022 16:41:10 +0000 + +To use an existing date and time to generate a version 7 UUID, you may pass a +``\DateTimeInterface`` instance to the ``uuid7()`` method. + +.. code-block:: php + :caption: Generate a version 7 UUID from an existing date and time + :name: rfc4122.version7.example-datetime + + use DateTimeImmutable; + use Ramsey\Uuid\Uuid; + + $dateTime = new DateTimeImmutable('@281474976710.655'); + $uuid = Uuid::uuid7($dateTime); + + printf( + "UUID: %s\nVersion: %d\nDate: %s\n", + $uuid->toString(), + $uuid->getFields()->getVersion(), + $uuid->getDateTime()->format('r'), + ); + +Which will print something like this: + +.. code-block:: text + + UUID: ffffffff-ffff-7964-a8f6-001336ac20cb + Version: 7 + Date: Tue, 02 Aug 10889 05:31:50 +0000 + +.. tip:: + + Version 7 UUIDs generated in ramsey/uuid are instances of UuidV7. Check out + the :php:class:`Ramsey\\Uuid\\Rfc4122\\UuidV7` API documentation to learn + more about what you can do with a UuidV7 instance. + + +.. _rfc4122.version7.ulid: + +Convert a Version 7 UUID to a ULID +################################## + +As mentioned in the callout above, version 7 UUIDs are binary-compatible with +`ULIDs`_. This means you can encode a version 7 UUID using `Crockford's Base 32 +algorithm`_ and it will be a valid ULID, timestamp and all. + +Using the third-party library `tuupola/base32`_, here's how we can encode a +version 7 UUID as a ULID. Note that there's a little bit of work to perform the +conversion, since you're working with different bases. + +.. code-block:: php + :caption: Encode a version 7, Unix Epoch time UUID as a ULID + :name: rfc4122.version7.example-ulid + + use Ramsey\Uuid\Uuid; + use Tuupola\Base32; + + $crockford = new Base32([ + 'characters' => Base32::CROCKFORD, + 'padding' => false, + 'crockford' => true, + ]); + + $uuid = Uuid::uuid7(); + + // First, we must pad the 16-byte string to 20 bytes + // for proper conversion without data loss. + $bytes = str_pad($uuid->getBytes(), 20, "\x00", STR_PAD_LEFT); + + // Use Crockford's Base 32 encoding algorithm. + $encoded = $crockford->encode($bytes); + + // That 20-byte string was encoded to 32 characters to avoid loss + // of data. We must strip off the first 6 characters--which are + // all zeros--to get a valid 26-character ULID string. + $ulid = substr($encoded, 6); + + printf("ULID: %s\n", $ulid); + +This will print something like this: + +.. code-block:: text + + ULID: 01GCZ05N3JFRKBRWKNGCQZGP44 + +.. caution:: + + Be aware that all version 7 UUIDs may be converted to ULIDs but not all + ULIDs may be converted to UUIDs. + + For that matter, all UUIDs of any version may be encoded as ULIDs, but they + will not be monotonically increasing and sortable unless they are version 7 + UUIDs. You will also not be able to extract a meaningful timestamp from the + ULID, unless it was converted from a version 7 UUID. + +.. _ULIDs: https://github.com/ulid/spec +.. _Internet-Draft under review: https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#section-5.2 +.. _two problems that have long existed: https://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/ +.. _Crockford's Base 32 algorithm: https://www.crockford.com/base32.html +.. _tuupola/base32: https://packagist.org/packages/tuupola/base32 diff --git a/src/Uuid.php b/src/Uuid.php index ee04da8..0397de1 100644 --- a/src/Uuid.php +++ b/src/Uuid.php @@ -683,16 +683,20 @@ class Uuid implements UuidInterface /** * Returns a version 7 (Unix Epoch time) UUID * + * @param DateTimeInterface|null $dateTime An optional date/time from which + * to create the version 7 UUID. If not provided, the UUID is generated + * using the current date/time. + * * @return UuidInterface A UuidInterface instance that represents a * version 7 UUID */ - public static function uuid7(): UuidInterface + public static function uuid7(?DateTimeInterface $dateTime = null): UuidInterface { $factory = self::getFactory(); if (method_exists($factory, 'uuid7')) { /** @var UuidInterface */ - return $factory->uuid7(); + return $factory->uuid7($dateTime); } throw new UnsupportedOperationException( diff --git a/src/UuidFactory.php b/src/UuidFactory.php index de3707c..f4fabdd 100644 --- a/src/UuidFactory.php +++ b/src/UuidFactory.php @@ -18,13 +18,16 @@ use DateTimeInterface; use Ramsey\Uuid\Builder\UuidBuilderInterface; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; +use Ramsey\Uuid\Converter\Time\UnixTimeConverter; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Generator\DceSecurityGeneratorInterface; use Ramsey\Uuid\Generator\DefaultTimeGenerator; use Ramsey\Uuid\Generator\NameGeneratorInterface; use Ramsey\Uuid\Generator\RandomGeneratorInterface; use Ramsey\Uuid\Generator\TimeGeneratorInterface; +use Ramsey\Uuid\Generator\UnixTimeGenerator; use Ramsey\Uuid\Lazy\LazyUuidFromString; +use Ramsey\Uuid\Math\BrickMathCalculator; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Provider\Time\FixedTimeProvider; use Ramsey\Uuid\Type\Hexadecimal; @@ -421,12 +424,32 @@ class UuidFactory implements UuidFactoryInterface /** * Returns a version 7 (Unix Epoch time) UUID * + * @param DateTimeInterface|null $dateTime An optional date/time from which + * to create the version 7 UUID. If not provided, the UUID is generated + * using the current date/time. + * * @return UuidInterface A UuidInterface instance that represents a * version 7 UUID */ - public function uuid7(): UuidInterface + public function uuid7(?DateTimeInterface $dateTime = null): UuidInterface { - return $this->uuidFromBytesAndVersion($this->unixTimeGenerator->generate(), Uuid::UUID_TYPE_UNIX_TIME); + if ($dateTime !== null) { + $timeProvider = new FixedTimeProvider( + new Time($dateTime->format('U'), $dateTime->format('u')) + ); + + $timeGenerator = new UnixTimeGenerator( + new UnixTimeConverter(new BrickMathCalculator()), + $timeProvider, + $this->randomGenerator, + ); + + $bytes = $timeGenerator->generate(); + } else { + $bytes = $this->unixTimeGenerator->generate(); + } + + return $this->uuidFromBytesAndVersion($bytes, Uuid::UUID_TYPE_UNIX_TIME); } /** diff --git a/src/functions.php b/src/functions.php index c3d3b49..fa80f4e 100644 --- a/src/functions.php +++ b/src/functions.php @@ -15,6 +15,7 @@ declare(strict_types=1); namespace Ramsey\Uuid; +use DateTimeInterface; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; @@ -121,3 +122,17 @@ function v6(?Hexadecimal $node = null, ?int $clockSeq = null): string { return Uuid::uuid6($node, $clockSeq)->toString(); } + +/** + * Returns a version 7 (Unix Epoch time) UUID + * + * @param DateTimeInterface|null $dateTime An optional date/time from which + * to create the version 7 UUID. If not provided, the UUID is generated + * using the current date/time. + * + * @return non-empty-string Version 7 UUID as a string + */ +function v7(?DateTimeInterface $dateTime = null): string +{ + return Uuid::uuid7($dateTime)->toString(); +} diff --git a/tests/Converter/Time/UnixTimeConverterTest.php b/tests/Converter/Time/UnixTimeConverterTest.php index 3a22f8a..2aff15f 100644 --- a/tests/Converter/Time/UnixTimeConverterTest.php +++ b/tests/Converter/Time/UnixTimeConverterTest.php @@ -52,7 +52,7 @@ class UnixTimeConverterTest extends TestCase 'microseconds' => '999000', ], - // This is the last possible time supported by v7 UUIDs. + // This is the last possible time supported by v7 UUIDs (2 ^ 48 - 1). // 10889-08-02 05:31:50.655 +00:00 [ 'uuidTimestamp' => new Hexadecimal('ffffffffffff'), @@ -155,7 +155,7 @@ class UnixTimeConverterTest extends TestCase 'expected' => '000000000000', ], - // This is the last possible time supported by v7 UUIDs: + // This is the last possible time supported by v7 UUIDs (2 ^ 48 - 1): // 10889-08-02 05:31:50.655 +00:00 [ 'seconds' => '281474976710', diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 357bd4f..bd368ee 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -4,7 +4,10 @@ declare(strict_types=1); namespace Ramsey\Uuid\Test; +use DateTimeImmutable; +use DateTimeInterface; use Ramsey\Uuid\Rfc4122\FieldsInterface; +use Ramsey\Uuid\Rfc4122\UuidV7; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\Uuid; @@ -15,6 +18,7 @@ use function Ramsey\Uuid\v3; use function Ramsey\Uuid\v4; use function Ramsey\Uuid\v5; use function Ramsey\Uuid\v6; +use function Ramsey\Uuid\v7; class FunctionsTest extends TestCase { @@ -79,6 +83,39 @@ class FunctionsTest extends TestCase $fields = Uuid::fromString($v6)->getFields(); $this->assertIsString($v6); - $this->assertSame(Uuid::UUID_TYPE_PEABODY, $fields->getVersion()); + $this->assertSame(Uuid::UUID_TYPE_REORDERED_TIME, $fields->getVersion()); + } + + public function testV7ReturnsVersion7UuidString(): void + { + $v7 = v7(); + + /** @var UuidV7 $uuid */ + $uuid = Uuid::fromString($v7); + + /** @var FieldsInterface $fields */ + $fields = $uuid->getFields(); + + $this->assertIsString($v7); + $this->assertSame(Uuid::UUID_TYPE_UNIX_TIME, $fields->getVersion()); + $this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime()); + } + + public function testV7WithCustomDateTimeReturnsVersion7UuidString(): void + { + $dateTime = new DateTimeImmutable('2022-09-14T22:44:33+00:00'); + + $v7 = v7($dateTime); + + /** @var UuidV7 $uuid */ + $uuid = Uuid::fromString($v7); + + /** @var FieldsInterface $fields */ + $fields = $uuid->getFields(); + + $this->assertIsString($v7); + $this->assertSame(Uuid::UUID_TYPE_UNIX_TIME, $fields->getVersion()); + $this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime()); + $this->assertSame(1663195473, $uuid->getDateTime()->getTimestamp()); } } diff --git a/tests/UuidTest.php b/tests/UuidTest.php index 4be8c72..5f71905 100644 --- a/tests/UuidTest.php +++ b/tests/UuidTest.php @@ -6,6 +6,7 @@ namespace Ramsey\Uuid\Test; use Brick\Math\BigDecimal; use Brick\Math\RoundingMode; +use DateTimeImmutable; use DateTimeInterface; use Mockery; use Mockery\MockInterface; @@ -728,6 +729,20 @@ class UuidTest extends TestCase Uuid::uuid7(); } + public function testUuid7WithDateTime(): void + { + $dateTime = new DateTimeImmutable('@281474976710.655'); + + $uuid = Uuid::uuid7($dateTime); + $this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime()); + $this->assertSame(2, $uuid->getVariant()); + $this->assertSame(7, $uuid->getVersion()); + $this->assertSame( + '10889-08-02T05:31:50.655+00:00', + $uuid->getDateTime()->format(DateTimeInterface::RFC3339_EXTENDED), + ); + } + /** * Tests known version-3 UUIDs *