Skip to content

Instantly share code, notes, and snippets.

@peterhirn
Last active July 24, 2026 13:57
Show Gist options
  • Select an option

  • Save peterhirn/41d59ac3c7629084e471a720a3370e25 to your computer and use it in GitHub Desktop.

Select an option

Save peterhirn/41d59ac3c7629084e471a720a3370e25 to your computer and use it in GitHub Desktop.
Minimal UUIDv7 C# implementation (.NET Core and .NET Framework)
// Ported from https://github.com/uuidjs/uuid (MIT)
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
public static class Uuid7
{
const byte Version = 0x70;
private static ulong Previous = 0;
private static uint Seq = 0;
#if NET9_0_OR_GREATER
private static readonly Lock Lock = new();
#else
private static readonly object Lock = new();
#endif
public static readonly DateTime Epoch = new(1970, 01, 01, 00, 00, 00, DateTimeKind.Utc);
#if !NET
private static readonly RandomNumberGenerator Generator = RandomNumberGenerator.Create();
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong Now() => (ulong)(DateTime.UtcNow - Epoch).TotalMilliseconds;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void FixByteOrder(Span<byte> uuid)
{
if (BitConverter.IsLittleEndian)
{
uuid.Slice(0, 4).Reverse();
uuid.Slice(4, 2).Reverse();
uuid.Slice(6, 2).Reverse();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static (ulong Now, uint Seq) UpdateState(ulong now, Span<byte> random)
{
lock (Lock)
{
if (now > Previous)
{
// Time has moved on! Pick a new random sequence number
Seq = BinaryPrimitives.ReadUInt32BigEndian(random.Slice(6));
Previous = now;
}
else
{
// Bump sequence counter w/ 32-bit rollover
Seq++;
// In case of rollover, bump timestamp to preserve monotonicity. This is
// allowed by the RFC and should self-correct as the system clock catches
// up. See https://www.rfc-editor.org/rfc/rfc9562.html#section-6.2-9.4
if (Seq == 0)
{
Previous++;
}
}
return (Previous, Seq);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Guid Create(ulong now, uint seq, Span<byte> random)
{
if (random.Length < 16)
throw new ArgumentException("Random bytes length must be >= 16", nameof(random));
Span<byte> buffer = stackalloc byte[18];
var uuid = buffer.Slice(2);
// byte 0-5: timestamp (48 bits)
BinaryPrimitives.WriteUInt64BigEndian(buffer, now);
// byte 6: `version` (4 bits) | sequence bits 28-31 (4 bits)
uuid[6] = (byte)(Version | ((seq >>> 28) & 0x0f));
// byte 7: sequence bits 20-27 (8 bits)
uuid[7] = (byte)((seq >>> 20) & 0xff);
// byte 8: `variant` (2 bits) | sequence bits 14-19 (6 bits)
uuid[8] = (byte)(0x80 | ((seq >>> 14) & 0x3f));
// byte 9: sequence bits 6-13 (8 bits)
uuid[9] = (byte)((seq >>> 6) & 0xff);
// byte 10: sequence bits 0-5 (6 bits) | random (2 bits)
uuid[10] = (byte)(((seq << 2) & 0xff) | (byte)(random[10] & 0x03));
// bytes 11-15: random (40 bits)
random.Slice(11, 5).CopyTo(uuid.Slice(11));
#if NET
return new(uuid, true);
#else
FixByteOrder(uuid);
return new(uuid.ToArray());
#endif
}
public static Guid Create()
{
#if NET
Span<byte> random = stackalloc byte[16];
RandomNumberGenerator.Fill(random);
#else
byte[] random = new byte[16];
Generator.GetBytes(random);
#endif
var (now, seq) = UpdateState(Now(), random);
return Create(now, seq, random);
}
public static DateTime Timestamp(Guid input)
{
Span<byte> buffer = stackalloc byte[18];
var uuid = buffer.Slice(2);
#if NET
input.TryWriteBytes(uuid, true, out _);
#else
MemoryExtensions.CopyTo(input.ToByteArray(), uuid);
FixByteOrder(uuid);
#endif
var version = (byte)(uuid[6] & 0xf0);
if (version != Version)
throw new ArgumentException($"Invalid UUID version ({version >> 4:x1})");
var timestamp = BinaryPrimitives.ReadUInt64BigEndian(buffer);
return Epoch + TimeSpan.FromMilliseconds(timestamp);
}
}
using System.Collections.Concurrent;
using System.Globalization;
using NUnit.Framework;
[TestFixture]
public class Uuid7Tests
{
// Fixture values for testing with the rfc v7 UUID example:
// https://www.rfc-editor.org/rfc/rfc9562.html#name-example-of-a-uuidv7-value
static readonly Guid RFC_V7 = new("017f22e2-79b0-7cc3-98c4-dc0c0c07398f");
const ulong RFC_NOW = 0x17f22e279b0;
const uint RFC_SEQ = ((uint)0x0cc3 << 20) | (0x98c4dc >> 2);
static readonly byte[] RFC_RANDOM = [
0x10,
0x91,
0x56,
0xbe,
0xc4,
0xfb,
0x0c,
0xc3,
0x18,
0xc4,
0x6c,
0x0c,
0x0c,
0x07,
0x39,
0x8f,
];
[Test]
public void Subsequent_Uuids_Are_Different()
{
var first = Uuid7.Create();
var second = Uuid7.Create();
Assert.That(first, Is.Not.EqualTo(second));
}
[Test]
public void Subsequent_Uuids_Are_Increasing()
{
var first = Uuid7.Create();
var second = Uuid7.Create();
Assert.That(second, Is.GreaterThan(first));
}
[Test]
public void Uuid_v7_RFC()
{
var rfc = Uuid7.Create(RFC_NOW, RFC_SEQ, RFC_RANDOM);
Assert.That(rfc, Is.EqualTo(RFC_V7));
}
[Test]
public void Concurrently_Created_Uuids_Are_Increasing()
{
var taskCount = 1_000;
var uuidCount = taskCount * 2;
var tasks = new Task[taskCount];
var results = new ConcurrentDictionary<Guid, byte>(Environment.ProcessorCount, uuidCount);
for (int i = 0; i < taskCount; i++)
{
tasks[i] = Task.Run(() =>
{
var first = Uuid7.Create();
var second = Uuid7.Create();
if (!results.TryAdd(first, 0))
throw new InvalidOperationException("Duplicated UUID v7");
if (!results.TryAdd(second, 0))
throw new InvalidOperationException("Duplicated UUID v7");
Assert.That(second, Is.GreaterThan(first));
});
}
Task.WaitAll(tasks);
Assert.That(results, Has.Count.EqualTo(uuidCount));
}
[Test]
public void Timestamp_Throws_Invalid_Version()
{
var v1 = new Guid("16bd5b94-829e-11f1-ab07-0242ac120002");
var v4 = new Guid("a82b4747-b341-4b76-bc97-112f53b9dc1c");
var nil = new Guid("00000000-0000-0000-0000-000000000000");
var max = new Guid("ffffffff-ffff-ffff-ffff-ffffffffffff");
using (Assert.EnterMultipleScope())
{
Assert.Throws(Is.TypeOf<ArgumentException>()
.And.Message.EqualTo("Invalid UUID version (1)"),
() => Uuid7.Timestamp(v1));
Assert.Throws(Is.TypeOf<ArgumentException>()
.And.Message.EqualTo("Invalid UUID version (4)"),
() => Uuid7.Timestamp(v4));
Assert.Throws(Is.TypeOf<ArgumentException>()
.And.Message.EqualTo("Invalid UUID version (0)"),
() => Uuid7.Timestamp(nil));
Assert.Throws(Is.TypeOf<ArgumentException>()
.And.Message.EqualTo("Invalid UUID version (f)"),
() => Uuid7.Timestamp(max));
}
}
[Test]
public void Timestamp_RFC()
{
var expected = Uuid7.Epoch + TimeSpan.FromMilliseconds(RFC_NOW);
Assert.That(Uuid7.Timestamp(RFC_V7), Is.EqualTo(expected));
}
[Test]
public void Timestamp_Example()
{
var guid = new Guid("019f74fd-d069-7776-9684-a259beccc9ac");
var actual = Uuid7.Timestamp(guid);
var expected =
DateTime.Parse(
"2026-07-18T11:30:18.857Z",
CultureInfo.InvariantCulture,
DateTimeStyles.RoundtripKind);
using (Assert.EnterMultipleScope())
{
Assert.That(expected.Kind, Is.EqualTo(DateTimeKind.Utc));
Assert.That(actual.Kind, Is.EqualTo(DateTimeKind.Utc));
Assert.That(actual, Is.EqualTo(expected));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment