@@ -121,6 +121,7 @@ module std.uuid;
121121}
122122
123123import core.time : dur;
124+ import std.bitmanip : bigEndianToNative, nativeToBigEndian;
124125import std.datetime.systime : SysTime;
125126import std.datetime : Clock , DateTime , UTC ;
126127import std.range.primitives ;
@@ -323,13 +324,16 @@ public struct UUID
323324 * random = UUID V7 has 74 bits of random data, which rounds to 10 ubyte's.
324325 * If no random data is given, random data is generated.
325326 */
326- @safe pure this (SysTime timestamp, ubyte [10 ] random = generateV7RandomData() )
327+ @safe pure this (SysTime timestamp, ubyte [10 ] random = generateV7RandomData! 10 )
327328 {
328- import std.bitmanip : nativeToBigEndian;
329+ ulong epoch = (timestamp - SysTime.fromUnixTime(0 )).total! " msecs" ;
330+ this (epoch, random);
331+ }
329332
330- ubyte [8 ] epoch = (timestamp - SysTime.fromUnixTime(0 ))
331- .total! " msecs"
332- .nativeToBigEndian;
333+ // / ditto
334+ @safe pure this (ulong epoch_msecs, ubyte [10 ] random = generateV7RandomData! 10 )
335+ {
336+ ubyte [8 ] epoch = epoch_msecs.nativeToBigEndian;
333337
334338 this .data[0 .. 6 ] = epoch[2 .. 8 ];
335339 this .data[6 .. $] = random;
@@ -557,7 +561,7 @@ public struct UUID
557561
558562 /**
559563 * If the UUID is of version 7 it has a timestamp that this function
560- * returns, otherwise and UUIDParsingException is thrown.
564+ * returns, otherwise an UUIDParsingException is thrown.
561565 */
562566 SysTime v7Timestamp () const {
563567 if (this .uuidVersion != Version.timestampRandom)
@@ -574,6 +578,25 @@ public struct UUID
574578 return SysTime (DateTime (1970 , 1 , 1 ), UTC ()) + dur! " msecs" (milli);
575579 }
576580
581+ /**
582+ * If the UUID is of version 7 it has a timestamp that this function
583+ * returns as described in RFC 9562 (Method 3), otherwise an
584+ * UUIDParsingException is thrown.
585+ */
586+ SysTime v7Timestamp_method3 () const {
587+ auto ret = v7Timestamp();
588+
589+ const ubyte [2 ] rand_a = [
590+ data[6 ] & 0x0f , // masks version bits
591+ data[7 ]
592+ ];
593+
594+ const float hnsecs = rand_a.bigEndianToNative! ushort / MonotonicUUIDsFactory.subMsecsPart;
595+ ret += dur! " hnsecs" (cast (ulong ) hnsecs);
596+
597+ return ret;
598+ }
599+
577600 /**
578601 * RFC 4122 defines different internal data layouts for UUIDs.
579602 * Returns the format used by this UUID.
@@ -1378,6 +1401,104 @@ if (isInputRange!RNG && isIntegral!(ElementType!RNG))
13781401 assert (u1.uuidVersion == UUID .Version.randomNumberBased);
13791402}
13801403
1404+ // /
1405+ class MonotonicUUIDsFactory
1406+ {
1407+ import core.sync.mutex ;
1408+ import std.datetime.stopwatch ;
1409+
1410+ private shared Mutex mtx;
1411+ private __gshared StopWatch epochTimePoint;
1412+
1413+ // /
1414+ this (in SysTime startTime = SysTime.fromUnixTime(0 )) shared
1415+ {
1416+ mtx = new shared Mutex ();
1417+
1418+ epochTimePoint.start();
1419+ epochTimePoint.setTimeElapsed = Clock .currTime - startTime;
1420+ }
1421+
1422+ // hnsecs is 1/10_000 of millisecond
1423+ // rand_a size is 12 bits (4096 values)
1424+ private enum float subMsecsPart = 1.0f / 10_000 * 4096 ;
1425+
1426+ /**
1427+ * Returns a monotonic timestamp + random based UUIDv7
1428+ * as described in RFC 9562 (Method 3).
1429+ */
1430+ UUID createUUIDv7_method3 (ubyte [8 ] rnd = generateV7RandomData! 8 ) shared
1431+ {
1432+ mtx.lock();
1433+ scope (exit) mtx.unlock();
1434+
1435+ const dur = epochTimePoint.peek;
1436+ const curr = dur.split! (" msecs" , " hnsecs" );
1437+ const qhnsecs = cast (ushort ) (curr.hnsecs * subMsecsPart);
1438+
1439+ ubyte [10 ] rand;
1440+
1441+ // Whole rand_a is 16 bit, but usable only 12 MSB.
1442+ // additional 4 less significant bits consumed
1443+ // by a version value
1444+ rand[0 .. 2 ] = qhnsecs.nativeToBigEndian;
1445+ rand[2 .. $] = rnd;
1446+
1447+ return UUID (curr.msecs, rand);
1448+ }
1449+ }
1450+
1451+ unittest
1452+ {
1453+ import std.conv : to;
1454+ import std.datetime ;
1455+
1456+ auto f = new shared MonotonicUUIDsFactory;
1457+
1458+ // trick to give reproducible testing
1459+ Duration setElapsedOffset (Duration dura){
1460+ if (f.epochTimePoint.running)
1461+ f.epochTimePoint.stop();
1462+
1463+ const st = SysTime(DateTime (2025 , 9 , 12 , 21 , 38 , 45 ), UTC ());
1464+ Duration ret = st - SysTime.fromUnixTime(0 ) + dura;
1465+ f.epochTimePoint.setTimeElapsed = ret;
1466+ return ret;
1467+ }
1468+
1469+ Duration d = dur! " msecs" (123 );
1470+ setElapsedOffset(d);
1471+
1472+ const uuidv7_milli = f.createUUIDv7_method3().v7Timestamp;
1473+
1474+ {
1475+ const st = f.createUUIDv7_method3().v7Timestamp_method3;
1476+ assert (cast (DateTime ) st == DateTime (2025 , 9 , 12 , 21 , 38 , 45 ), st.to! string );
1477+
1478+ const sp = st.fracSecs.split! (" msecs" , " usecs" );
1479+ assert (sp.msecs == 123 , sp.to! string );
1480+ assert (sp.usecs == 0 , sp.to! string );
1481+ }
1482+
1483+ // 0.3 usecs, but Method 3 precision is only 0.25 of usec,
1484+ // thus, expected value is 2
1485+ d += dur! " hnsecs" (3 );
1486+ setElapsedOffset(d);
1487+
1488+ const uuidv7_milli_2 = f.createUUIDv7_method3().v7Timestamp;
1489+ assert (uuidv7_milli == uuidv7_milli_2);
1490+
1491+ {
1492+ const st = f.createUUIDv7_method3().v7Timestamp_method3;
1493+ assert (cast (DateTime ) st == DateTime (2025 , 9 , 12 , 21 , 38 , 45 ), st.to! string );
1494+
1495+ const sp = st.fracSecs.split! (" msecs" , " usecs" , " hnsecs" );
1496+ assert (sp.msecs == 123 , sp.to! string );
1497+ assert (sp.usecs == 0 , sp.to! string );
1498+ assert (sp.hnsecs == 2 , sp.to! string );
1499+ }
1500+ }
1501+
13811502/**
13821503 * This function returns a timestamp + random based UUID aka. uuid v7.
13831504 */
@@ -1794,12 +1915,12 @@ enum uuidRegex = "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}"~
17941915 ]);
17951916}
17961917
1797- private ubyte [10 ] generateV7RandomData () {
1918+ private ubyte [Size ] generateV7RandomData ( ubyte Size) () {
17981919 import std.random : Random , uniform, unpredictableSeed;
17991920
18001921 auto rnd = Random (unpredictableSeed! (ubyte )());
18011922
1802- ubyte [10 ] bytes;
1923+ ubyte [Size ] bytes;
18031924 foreach (idx; 0 .. bytes.length)
18041925 {
18051926 bytes[idx] = uniform! (ubyte )(rnd);
@@ -1901,6 +2022,5 @@ public class UUIDParsingException : Exception
19012022{
19022023 import std.datetime : DateTime , SysTime;
19032024 UUID u = UUID (" 0198c2b2-c5a8-7a0f-a1db-86aac7906c7b" );
1904- auto d = DateTime (2025 ,8 ,19 );
1905- assert ((cast (DateTime ) u.v7Timestamp()).year == d.year);
2025+ assert (u.v7Timestamp.toISOExtString == " 2025-08-19T14:19:12.68Z" , u.v7Timestamp.toISOExtString);
19062026}
0 commit comments