Skip to content

Commit 6ed5c63

Browse files
committed
Adds UUID v7 support
1 parent f879790 commit 6ed5c63

File tree

2 files changed

+138
-2
lines changed

2 files changed

+138
-2
lines changed

changelog/uuidv7.dd

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Add uuid v7 support to `std.uuid`
2+
3+
Add uuid v7 support to the UUID type located in `std.uuid`.
4+
The first 48 bits of v7 stores the milliseconds since the unix epoch
5+
(1970-01-01), additionally 74 bit are used to store random data.
6+
7+
Example:
8+
---
9+
SysTime st = DateTime(2025, 8, 19, 10, 38, 45);
10+
UUID u = UUID(st);
11+
SysTime o = u.v7Timestamp();
12+
assert(o == st);
13+
14+
string s = u.toString();
15+
UUID u2 = UUID(s);
16+
SysTime o2 = u2.v7Timestamp();
17+
assert(o2 == st);
18+
---

std/uuid.d

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ $(TR $(TDNW Generating UUIDs)
2323
$(TD $(MYREF sha1UUID)
2424
$(MYREF randomUUID)
2525
$(MYREF md5UUID)
26+
$(MYREF timestampRandomUUID)
2627
)
2728
)
2829
$(TR $(TDNW Using UUIDs)
@@ -119,6 +120,9 @@ module std.uuid;
119120
assert(id.empty);
120121
}
121122

123+
import core.time : dur;
124+
import std.datetime.systime : SysTime;
125+
import std.datetime : Clock, DateTime, UTC;
122126
import std.range.primitives;
123127
import std.traits;
124128

@@ -193,7 +197,9 @@ public struct UUID
193197
///Version 4 (Random)
194198
randomNumberBased = 4,
195199
///Version 5 (Name based + SHA-1)
196-
nameBasedSHA1 = 5
200+
nameBasedSHA1 = 5,
201+
///Version 7 (milliseconds since unix epoch + random)
202+
timestampRandom = 7
197203
}
198204

199205
union
@@ -309,6 +315,41 @@ public struct UUID
309315
assert(!__traits(compiles, typeof(UUID(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,1))));
310316
}
311317

318+
/**
319+
* UUID V7 constructor
320+
*
321+
* Params:
322+
* timestamp = the timestamp part of the UUID V7
323+
* random = UUID V7 has 74 bits of random data, which rounds to 10 ubyte's.
324+
* If no random data is given, random data is generated.
325+
*/
326+
@safe pure this(SysTime timestamp, ubyte[10] random = generateV7RandomData())
327+
{
328+
ulong epoch = (timestamp - SysTime.fromUnixTime(0)).total!"msecs";
329+
this.data[6 .. $] = random[];
330+
331+
this.data[0] = cast(ubyte)((epoch >> 40) & 0xFF);
332+
this.data[1] = cast(ubyte)((epoch >> 32) & 0xFF);
333+
this.data[2] = cast(ubyte)((epoch >> 24) & 0xFF);
334+
this.data[3] = cast(ubyte)((epoch >> 16) & 0xFF);
335+
this.data[4] = cast(ubyte)((epoch >> 8) & 0xFF);
336+
this.data[5] = cast(ubyte)(epoch & 0xFF);
337+
338+
// version and variant
339+
this.data[6] = (this.data[6] & 0x0F) | 0x70;
340+
this.data[8] = (this.data[8] & 0x3F) | 0x80;
341+
}
342+
343+
///
344+
unittest
345+
{
346+
import std.datetime : DateTime, SysTime;
347+
SysTime st = DateTime(2025, 8, 19, 10, 38, 45);
348+
UUID u = UUID(st);
349+
SysTime o = u.v7Timestamp();
350+
assert(o == st, st.toString() ~ " | " ~ o.toString());
351+
}
352+
312353
/**
313354
* <a name="UUID(string)"></a>
314355
* Parse a UUID from its canonical string form. An UUID in its
@@ -515,6 +556,25 @@ public struct UUID
515556
enum res = ctfeTest();
516557
}
517558

559+
/**
560+
* If the UUID is of version 7 it has a timestamp that this function
561+
* returns, otherwise and UUIDParsingException is thrown.
562+
*/
563+
SysTime v7Timestamp() const {
564+
if (this.uuidVersion != Version.timestampRandom)
565+
{
566+
throw new UUIDParsingException("The UUID is not of version" ~
567+
" v7 therefore no timestamp exist", 0);
568+
}
569+
ulong milli = (cast(ulong)(this.data[0]) << 40) |
570+
(cast(ulong)(this.data[1]) << 32) |
571+
(cast(ulong)(this.data[2]) << 24) |
572+
(cast(ulong)(this.data[3]) << 16) |
573+
(cast(ulong)(this.data[4]) << 8) |
574+
(cast(ulong)(this.data[5]));
575+
return SysTime(DateTime(1970, 1, 1), UTC()) + dur!"msecs"(milli);
576+
}
577+
518578
/**
519579
* RFC 4122 defines different internal data layouts for UUIDs.
520580
* Returns the format used by this UUID.
@@ -601,6 +661,8 @@ public struct UUID
601661
return Version.randomNumberBased;
602662
else if ((octet9 & 0xF0) == 0x50)
603663
return Version.nameBasedSHA1;
664+
else if ((octet9 & 0xF0) == 0x70)
665+
return Version.timestampRandom;
604666
else
605667
return Version.unknown;
606668
}
@@ -622,7 +684,7 @@ public struct UUID
622684
0x40 : UUID.Version.randomNumberBased,
623685
0x50 : UUID.Version.nameBasedSHA1,
624686
0x60 : UUID.Version.unknown,
625-
0x70 : UUID.Version.unknown,
687+
0x70 : UUID.Version.timestampRandom,
626688
0x80 : UUID.Version.unknown,
627689
0x90 : UUID.Version.unknown,
628690
0xa0 : UUID.Version.unknown,
@@ -1317,6 +1379,13 @@ if (isInputRange!RNG && isIntegral!(ElementType!RNG))
13171379
assert(u1.uuidVersion == UUID.Version.randomNumberBased);
13181380
}
13191381

1382+
/**
1383+
* This function returns a timestamp + random based UUID aka. uuid v7.
1384+
*/
1385+
UUID timestampRandomUUID() {
1386+
return UUID(Clock.currTime());
1387+
}
1388+
13201389
/**
13211390
* This is a less strict parser compared to the parser used in the
13221391
* UUID constructor. It enforces the following rules:
@@ -1718,6 +1787,19 @@ enum uuidRegex = "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}"~
17181787
]);
17191788
}
17201789

1790+
ubyte[10] generateV7RandomData() {
1791+
import std.random;
1792+
auto rnd = Random(unpredictableSeed!(ubyte)());
1793+
1794+
ubyte[10] bytes;
1795+
foreach (idx; 0 .. bytes.length)
1796+
{
1797+
bytes[idx] = uniform!(ubyte)(rnd);
1798+
rnd.popFront();
1799+
}
1800+
return bytes;
1801+
}
1802+
17211803
/**
17221804
* This exception is thrown if an error occurs when parsing a UUID
17231805
* from a string.
@@ -1777,3 +1859,39 @@ public class UUIDParsingException : Exception
17771859
assert(ex.position == 10);
17781860
assert(ex.reason == UUIDParsingException.Reason.tooMuch);
17791861
}
1862+
1863+
/// uuidv7
1864+
unittest
1865+
{
1866+
import std.datetime : DateTime, SysTime;
1867+
1868+
SysTime st = DateTime(2025, 8, 19, 10, 38, 45);
1869+
UUID u = UUID(st);
1870+
assert(u.uuidVersion == UUID.Version.timestampRandom);
1871+
SysTime o = u.v7Timestamp();
1872+
assert(o == st, st.toString() ~ " | " ~ o.toString());
1873+
string s = u.toString();
1874+
UUID u2 = UUID(s);
1875+
SysTime o2 = u2.v7Timestamp();
1876+
assert(o2 == st, st.toString() ~ " | " ~ o2.toString());
1877+
}
1878+
1879+
unittest
1880+
{
1881+
import std.datetime : SysTime;
1882+
1883+
UUID u = timestampRandomUUID();
1884+
assert(u.uuidVersion == UUID.Version.timestampRandom);
1885+
1886+
SysTime o = u.v7Timestamp();
1887+
assert(o.year > 2024);
1888+
assert(o.year < 3024);
1889+
}
1890+
1891+
unittest
1892+
{
1893+
UUID u = UUID("0198c2b2-c5a8-7a0f-a1db-86aac7906c7b");
1894+
import std.datetime : DateTime, SysTime;
1895+
auto d = DateTime(2025,8,19);
1896+
assert((cast(DateTime)u.v7Timestamp()).year == d.year);
1897+
}

0 commit comments

Comments
 (0)