Implementing

Variant 0: time-sortable

View this section in the spec ↗

Like UUIDv7: a millisecond timestamp, then random bits. Sorts by creation time within a name, as u128, UUID form, or TNID string.

Layout

1111.1111.1111.1111.1111.2222.2222.2222-
2222.2222.2222.2222-
3333.2222.2222.2222-
4455.2226.6666.6666-
6666.6666.6666.6666.6666.6666.6666.6666.6666.6666.6666.6666
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                  name                 |       unix_ts_ms      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           unix_ts_ms          |  ver  |       unix_ts_ms      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var| tv|  ms |                      random                     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                             random                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#FieldBitsMSB = 0Value
1Name200–19Name encoding
2Milliseconds since the Unix epoch43 = 28 + 12 + 320–47, 52–63, 68–70unix_ms mod 2^43
3UUID version448–510x8
4UUID variant264–65MUST be 0b10
5TNID variant266–67MUST be 0b00
6Random5771–127

As a 100-bit payload, split 28 + 12 + 60 across the payload runs:

payload = (unix_ms mod 2^43) << 57 | random_57

Worked example

user.Br2flcNDfF6LYICnT = d6157337-0ebc-8686-83ab-4075a34cdcde:

name                11010110000101010111
timestamp, upper    0011001101110000111010111100
UUID version        1000
timestamp, middle   011010000110
UUID variant        10
TNID variant        00
timestamp, lower    001
random              110101011010000000111010110100011010011001101110011011110
FieldValue
Name0xd6157 = user
TNID variant00 = variant 0
Timestamp1767502656561 = 2026-01-04 04:57:36.561 UTC
Random0x1ab4075a34cdcde

Goals and non-goals

  • Goals: millisecond time-sortable; reasonable for 99% of use cases (low chance of collision, low stakes if one happens).
  • Non-goals: astronomically low collision odds; extraordinary numbers of TNIDs in very short periods; fields such as the timestamp being useful after creation.

Caveats

Sorting is per name

The name precedes the timestamp, so TNIDs sort by time only within one name. Order within a millisecond is undefined; the random bits decide.

Collisions

Only TNIDs with the same name and millisecond can collide. 1 million in one millisecond: about 0.00035%, by the birthday bound with N = 2^57:

P(collision) ≈ 1 - e^(-n² / 2N)
             = 1 - e^(-(10^6)² / (2 × 2^57))
             ≈ 0.00035%

Hex sortability

UUID hex is case-insensitive, so one value sorts differently in different cases:

As strings:  "A" (ASCII 65) < "a" (ASCII 97)  →  "0xA1" < "0xa1"
As values:   0xA1 = 0xa1 = 161

Sorting hex by time needs consistent case, as for any hex data. Case-free alternative: sort the TNID string or the 128-bit value (as Postgres's uuid type does).

Future TNIDs

43 bits of milliseconds overflow around the year 2248. Implementations SHOULD wrap (keep the low 43 bits) rather than error; see the wrap test vector.