@@ -121,6 +121,7 @@ module std.uuid;
121
121
}
122
122
123
123
import core.time : dur;
124
+ import std.bitmanip : bigEndianToNative, nativeToBigEndian;
124
125
import std.datetime.systime : SysTime;
125
126
import std.datetime : Clock , DateTime , UTC ;
126
127
import std.range.primitives ;
@@ -323,13 +324,16 @@ public struct UUID
323
324
* random = UUID V7 has 74 bits of random data, which rounds to 10 ubyte's.
324
325
* If no random data is given, random data is generated.
325
326
*/
326
- @safe pure this (SysTime timestamp, ubyte [10 ] random = generateV7RandomData() )
327
+ @safe pure this (SysTime timestamp, ubyte [10 ] random = generateV7RandomData! 10 )
327
328
{
328
- import std.bitmanip : nativeToBigEndian;
329
+ ulong epoch = (timestamp - SysTime.fromUnixTime(0 )).total! " msecs" ;
330
+ this (epoch, random);
331
+ }
329
332
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;
333
337
334
338
this .data[0 .. 6 ] = epoch[2 .. 8 ];
335
339
this .data[6 .. $] = random;
@@ -557,7 +561,7 @@ public struct UUID
557
561
558
562
/**
559
563
* 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.
561
565
*/
562
566
SysTime v7Timestamp () const {
563
567
if (this .uuidVersion != Version.timestampRandom)
@@ -574,6 +578,25 @@ public struct UUID
574
578
return SysTime (DateTime (1970 , 1 , 1 ), UTC ()) + dur! " msecs" (milli);
575
579
}
576
580
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
+
577
600
/**
578
601
* RFC 4122 defines different internal data layouts for UUIDs.
579
602
* Returns the format used by this UUID.
@@ -1378,6 +1401,128 @@ if (isInputRange!RNG && isIntegral!(ElementType!RNG))
1378
1401
assert (u1.uuidVersion == UUID .Version.randomNumberBased);
1379
1402
}
1380
1403
1404
+ // /
1405
+ class MonotonicUUIDsFactory
1406
+ {
1407
+ import core.sync.mutex : Mutex ;
1408
+ import std.datetime.stopwatch : StopWatch;
1409
+
1410
+ private shared Mutex mtx;
1411
+ private StopWatch startTimePoint;
1412
+
1413
+ // Passthrough for old compilers
1414
+ version (unittest )
1415
+ ref __start () shared => cast () startTimePoint;
1416
+
1417
+ // /
1418
+ this (in SysTime startTime = SysTime.fromUnixTime(0 )) shared
1419
+ {
1420
+ mtx = new shared Mutex ();
1421
+
1422
+ (cast () startTimePoint).start();
1423
+ (cast () startTimePoint).setTimeElapsed = Clock .currTime - startTime;
1424
+ }
1425
+
1426
+ private auto peek () shared
1427
+ {
1428
+ mtx.lock();
1429
+ scope (exit) mtx.unlock();
1430
+
1431
+ return (cast () startTimePoint).peek;
1432
+ }
1433
+
1434
+ // hnsecs is 1/10_000 of millisecond
1435
+ // rand_a size is 12 bits (4096 values)
1436
+ private enum float subMsecsPart = 1.0f / 10_000 * 4096 ;
1437
+
1438
+ /**
1439
+ * Returns a monotonic timestamp + random based UUIDv7
1440
+ * as described in RFC 9562 (Method 3).
1441
+ */
1442
+ // FIXME: for some reason this method call causes SIGSEGV, but works as template
1443
+ UUID createUUIDv7_method3 ()(ubyte [8 ] rnd = generateV7RandomData! 8 ) shared
1444
+ {
1445
+ const curr = peek.split! (" msecs" , " hnsecs" );
1446
+ const qhnsecs = cast (ushort ) (curr.hnsecs * subMsecsPart);
1447
+
1448
+ ubyte [10 ] rand;
1449
+
1450
+ // Whole rand_a is 16 bit, but usable only 12 MSB.
1451
+ // additional 4 less significant bits consumed
1452
+ // by a version value
1453
+ rand[0 .. 2 ] = qhnsecs.nativeToBigEndian;
1454
+ rand[2 .. $] = rnd;
1455
+
1456
+ return UUID (curr.msecs, rand);
1457
+ }
1458
+ }
1459
+
1460
+ // /
1461
+ @system unittest
1462
+ {
1463
+ import std.conv : to;
1464
+ import std.datetime ;
1465
+
1466
+ scope f = new shared MonotonicUUIDsFactory;
1467
+
1468
+ // trick to give reproducible testing
1469
+ Duration setElapsedOffset (Duration dura){
1470
+ if (f.__start.running)
1471
+ f.__start.stop();
1472
+
1473
+ const st = SysTime(DateTime (2025 , 9 , 12 , 21 , 38 , 45 ), UTC ());
1474
+ Duration ret = st - SysTime.fromUnixTime(0 ) + dura;
1475
+ f.__start.setTimeElapsed = ret;
1476
+ return ret;
1477
+ }
1478
+
1479
+ Duration d = dur! " msecs" (123 );
1480
+ setElapsedOffset(d);
1481
+
1482
+ const uuidv7_milli = f.createUUIDv7_method3().v7Timestamp;
1483
+
1484
+ {
1485
+ const st = f.createUUIDv7_method3().v7Timestamp_method3;
1486
+ assert (cast (DateTime ) st == DateTime (2025 , 9 , 12 , 21 , 38 , 45 ), st.to! string );
1487
+
1488
+ const sp = st.fracSecs.split! (" msecs" , " usecs" );
1489
+ assert (sp.msecs == 123 , sp.to! string );
1490
+ assert (sp.usecs == 0 , sp.to! string );
1491
+ }
1492
+
1493
+ // 0.3 usecs, but Method 3 precision is only 0.25 of usec,
1494
+ // thus, expected value is 2
1495
+ d += dur! " hnsecs" (3 );
1496
+ setElapsedOffset(d);
1497
+
1498
+ const uuidv7_milli_2 = f.createUUIDv7_method3().v7Timestamp;
1499
+ assert (uuidv7_milli == uuidv7_milli_2);
1500
+
1501
+ {
1502
+ const st = f.createUUIDv7_method3().v7Timestamp_method3;
1503
+ assert (cast (DateTime ) st == DateTime (2025 , 9 , 12 , 21 , 38 , 45 ), st.to! string );
1504
+
1505
+ const sp = st.fracSecs.split! (" msecs" , " usecs" , " hnsecs" );
1506
+ assert (sp.msecs == 123 , sp.to! string );
1507
+ assert (sp.usecs == 0 , sp.to! string );
1508
+ assert (sp.hnsecs == 2 , sp.to! string );
1509
+ }
1510
+ }
1511
+
1512
+ // /
1513
+ @system unittest
1514
+ {
1515
+ scope f = new shared MonotonicUUIDsFactory;
1516
+
1517
+ UUID [100_000] uuids = void ;
1518
+
1519
+ foreach (ref u; uuids)
1520
+ u = f.createUUIDv7_method3;
1521
+
1522
+ foreach (i; 1 .. uuids.length)
1523
+ assert (uuids[i- 1 ].v7Timestamp_method3 < uuids[i].v7Timestamp_method3);
1524
+ }
1525
+
1381
1526
/**
1382
1527
* This function returns a timestamp + random based UUID aka. uuid v7.
1383
1528
*/
@@ -1794,12 +1939,12 @@ enum uuidRegex = "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}"~
1794
1939
]);
1795
1940
}
1796
1941
1797
- private ubyte [10 ] generateV7RandomData () {
1942
+ private ubyte [Size ] generateV7RandomData ( ubyte Size) () {
1798
1943
import std.random : Random , uniform, unpredictableSeed;
1799
1944
1800
1945
auto rnd = Random (unpredictableSeed! (ubyte )());
1801
1946
1802
- ubyte [10 ] bytes;
1947
+ ubyte [Size ] bytes;
1803
1948
foreach (idx; 0 .. bytes.length)
1804
1949
{
1805
1950
bytes[idx] = uniform! (ubyte )(rnd);
@@ -1901,6 +2046,5 @@ public class UUIDParsingException : Exception
1901
2046
{
1902
2047
import std.datetime : DateTime , SysTime;
1903
2048
UUID u = UUID (" 0198c2b2-c5a8-7a0f-a1db-86aac7906c7b" );
1904
- auto d = DateTime (2025 ,8 ,19 );
1905
- assert ((cast (DateTime ) u.v7Timestamp()).year == d.year);
2049
+ assert (u.v7Timestamp.toISOExtString == " 2025-08-19T14:19:12.68Z" , u.v7Timestamp.toISOExtString);
1906
2050
}
0 commit comments