|
| 1 | +.. _rfc4122.version7: |
| 2 | + |
| 3 | +========================== |
| 4 | +Version 7: Unix Epoch Time |
| 5 | +========================== |
| 6 | + |
| 7 | +.. note:: |
| 8 | + |
| 9 | + Version 7, Unix Epoch time UUIDs are a new format of UUID, proposed in an |
| 10 | + `Internet-Draft under review`_ at the IETF. While the draft is still going |
| 11 | + through the IETF process, the version 7 format is not expected to change |
| 12 | + in any way that breaks compatibility. |
| 13 | + |
| 14 | +.. admonition:: ULIDs and Version 7 UUIDs |
| 15 | + :class: hint |
| 16 | + |
| 17 | + Version 7 UUIDs are binary-compatible with `ULIDs`_ (universally unique |
| 18 | + lexicographically-sortable identifiers). |
| 19 | + |
| 20 | + Both use a 48-bit timestamp in milliseconds since the Unix Epoch, filling |
| 21 | + the rest with random data. Version 7 UUIDs then add the version and variant |
| 22 | + bits required by the UUID specification, which reduces the randomness from |
| 23 | + 80 bits to 74. Otherwise, they are identical. |
| 24 | + |
| 25 | + You may even convert a version 7 UUID to a ULID. |
| 26 | + :ref:`See below for an example. <rfc4122.version7.ulid>` |
| 27 | + |
| 28 | +Version 7 UUIDs solve `two problems that have long existed`_ with the use of |
| 29 | +:ref:`version 1 <rfc4122.version1>` UUIDs: |
| 30 | + |
| 31 | +1. Scattered database records |
| 32 | +2. Inability to sort by an identifier in a meaningful way (i.e., insert order) |
| 33 | + |
| 34 | +To overcome these issues, we need the ability to generate UUIDs that are |
| 35 | +*monotonically increasing*. |
| 36 | + |
| 37 | +:ref:`Version 6 UUIDs <rfc4122.version6>` provide an excellent solution for |
| 38 | +those who need monotonically increasing, sortable UUIDs with the features of |
| 39 | +version 1 UUIDs (MAC address and clock sequence), but if those features aren't |
| 40 | +necessary for your application, using a version 6 UUID might be overkill. |
| 41 | + |
| 42 | +Version 7 UUIDs combine random data (like version 4 UUIDs) with a timestamp (in |
| 43 | +milliseconds since the Unix Epoch, i.e., 1970-01-01 00:00:00 UTC) to create a |
| 44 | +monotonically increasing, sortable UUID that doesn't have any privacy concerns, |
| 45 | +since it doesn't include a MAC address. |
| 46 | + |
| 47 | +For this reason, implementations should use version 7 UUIDs over versions 1 and |
| 48 | +6, if possible. |
| 49 | + |
| 50 | +.. code-block:: php |
| 51 | + :caption: Generate a version 7, Unix Epoch time UUID |
| 52 | + :name: rfc4122.version7.example |
| 53 | +
|
| 54 | + use Ramsey\Uuid\Uuid; |
| 55 | +
|
| 56 | + $uuid = Uuid::uuid7(); |
| 57 | +
|
| 58 | + printf( |
| 59 | + "UUID: %s\nVersion: %d\nDate: %s\n", |
| 60 | + $uuid->toString(), |
| 61 | + $uuid->getFields()->getVersion(), |
| 62 | + $uuid->getDateTime()->format('r'), |
| 63 | + ); |
| 64 | +
|
| 65 | +This will generate a version 7 UUID and print out its string representation and |
| 66 | +the time it was created. |
| 67 | + |
| 68 | +It will look something like this: |
| 69 | + |
| 70 | +.. code-block:: text |
| 71 | +
|
| 72 | + UUID: 01833ce0-3486-7bfd-84a1-ad157cf64005 |
| 73 | + Version: 7 |
| 74 | + Date: Wed, 14 Sep 2022 16:41:10 +0000 |
| 75 | +
|
| 76 | +To use an existing date and time to generate a version 7 UUID, you may pass a |
| 77 | +``\DateTimeInterface`` instance to the ``uuid7()`` method. |
| 78 | + |
| 79 | +.. code-block:: php |
| 80 | + :caption: Generate a version 7 UUID from an existing date and time |
| 81 | + :name: rfc4122.version7.example-datetime |
| 82 | +
|
| 83 | + use DateTimeImmutable; |
| 84 | + use Ramsey\Uuid\Uuid; |
| 85 | +
|
| 86 | + $dateTime = new DateTimeImmutable('@281474976710.655'); |
| 87 | + $uuid = Uuid::uuid7($dateTime); |
| 88 | +
|
| 89 | + printf( |
| 90 | + "UUID: %s\nVersion: %d\nDate: %s\n", |
| 91 | + $uuid->toString(), |
| 92 | + $uuid->getFields()->getVersion(), |
| 93 | + $uuid->getDateTime()->format('r'), |
| 94 | + ); |
| 95 | +
|
| 96 | +Which will print something like this: |
| 97 | + |
| 98 | +.. code-block:: text |
| 99 | +
|
| 100 | + UUID: ffffffff-ffff-7964-a8f6-001336ac20cb |
| 101 | + Version: 7 |
| 102 | + Date: Tue, 02 Aug 10889 05:31:50 +0000 |
| 103 | +
|
| 104 | +.. tip:: |
| 105 | + |
| 106 | + Version 7 UUIDs generated in ramsey/uuid are instances of UuidV7. Check out |
| 107 | + the :php:class:`Ramsey\\Uuid\\Rfc4122\\UuidV7` API documentation to learn |
| 108 | + more about what you can do with a UuidV7 instance. |
| 109 | + |
| 110 | + |
| 111 | +.. _rfc4122.version7.ulid: |
| 112 | + |
| 113 | +Convert a Version 7 UUID to a ULID |
| 114 | +################################## |
| 115 | + |
| 116 | +As mentioned in the callout above, version 7 UUIDs are binary-compatible with |
| 117 | +`ULIDs`_. This means you can encode a version 7 UUID using `Crockford's Base 32 |
| 118 | +algorithm`_ and it will be a valid ULID, timestamp and all. |
| 119 | + |
| 120 | +Using the third-party library `tuupola/base32`_, here's how we can encode a |
| 121 | +version 7 UUID as a ULID. Note that there's a little bit of work to perform the |
| 122 | +conversion, since you're working with different bases. |
| 123 | + |
| 124 | +.. code-block:: php |
| 125 | + :caption: Encode a version 7, Unix Epoch time UUID as a ULID |
| 126 | + :name: rfc4122.version7.example-ulid |
| 127 | +
|
| 128 | + use Ramsey\Uuid\Uuid; |
| 129 | + use Tuupola\Base32; |
| 130 | +
|
| 131 | + $crockford = new Base32([ |
| 132 | + 'characters' => Base32::CROCKFORD, |
| 133 | + 'padding' => false, |
| 134 | + 'crockford' => true, |
| 135 | + ]); |
| 136 | +
|
| 137 | + $uuid = Uuid::uuid7(); |
| 138 | +
|
| 139 | + // First, we must pad the 16-byte string to 20 bytes |
| 140 | + // for proper conversion without data loss. |
| 141 | + $bytes = str_pad($uuid->getBytes(), 20, "\x00", STR_PAD_LEFT); |
| 142 | +
|
| 143 | + // Use Crockford's Base 32 encoding algorithm. |
| 144 | + $encoded = $crockford->encode($bytes); |
| 145 | +
|
| 146 | + // That 20-byte string was encoded to 32 characters to avoid loss |
| 147 | + // of data. We must strip off the first 6 characters--which are |
| 148 | + // all zeros--to get a valid 26-character ULID string. |
| 149 | + $ulid = substr($encoded, 6); |
| 150 | +
|
| 151 | + printf("ULID: %s\n", $ulid); |
| 152 | +
|
| 153 | +This will print something like this: |
| 154 | + |
| 155 | +.. code-block:: text |
| 156 | +
|
| 157 | + ULID: 01GCZ05N3JFRKBRWKNGCQZGP44 |
| 158 | +
|
| 159 | +.. caution:: |
| 160 | + |
| 161 | + Be aware that all version 7 UUIDs may be converted to ULIDs but not all |
| 162 | + ULIDs may be converted to UUIDs. |
| 163 | + |
| 164 | + For that matter, all UUIDs of any version may be encoded as ULIDs, but they |
| 165 | + will not be monotonically increasing and sortable unless they are version 7 |
| 166 | + UUIDs. You will also not be able to extract a meaningful timestamp from the |
| 167 | + ULID, unless it was converted from a version 7 UUID. |
| 168 | + |
| 169 | +.. _ULIDs: https://github.com/ulid/spec |
| 170 | +.. _Internet-Draft under review: https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#section-5.2 |
| 171 | +.. _two problems that have long existed: https://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/ |
| 172 | +.. _Crockford's Base 32 algorithm: https://www.crockford.com/base32.html |
| 173 | +.. _tuupola/base32: https://packagist.org/packages/tuupola/base32 |
0 commit comments