Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save danielmarschall/7fafd270a3bc107d38e8449ce7420c25 to your computer and use it in GitHub Desktop.

Select an option

Save danielmarschall/7fafd270a3bc107d38e8449ce7420c25 to your computer and use it in GitHub Desktop.
SQL Server sortable UUID in C# (Version 3)
using System;
using System.Threading;
namespace GenerateSqlServerSortableTimeBasedGuid_App
{
internal class Program
{
static readonly Random random = new Random();
/**
* The sorting of SQL Server is rather confusing and incompatible with UUIDv6 and UUIDv7.
* Therefore this method generates UUID which are sortable by SQL Server.
* Version 1: Resolution of 1 milliseconds, random part of 16 bits, local timezone, 48 zero bits "signature", *NOT* UUIDv8 compliant.
* Version 2: DO NOT USE. Same as version 3, but wrong order due to a bug (block 2 and 3 accidentally used Big Endian instead of Little Endian).
* Version 3: Resolution of 1 milliseconds, random part of 16 bits, UTC time, 48 bit random "signature", UUIDv8 compliant.
* C# implementation: https://gist.github.com/danielmarschall/7fafd270a3bc107d38e8449ce7420c25
* PHP implementation: https://github.com/danielmarschall/uuid_mac_utils/blob/master/includes/uuid_utils.inc.php
*/
public static Guid GenerateSqlServerSortableTimeBasedGuid(int hickelUuidVersion = 3)
{
string block1, block2, block3, block4, block5;
DateTime dt;
// The sorting in SQL Server is like this:
// First Sort block 5, Big Endian
if (hickelUuidVersion == 1)
{
block5 = "000000000000";
}
else if (hickelUuidVersion == 2)
{
block5 = "5ce32bd83b96"; // UUIDv8 "Signature"
}
else if (hickelUuidVersion == 3)
{
block5 = "5ce32bd83b97"; // UUIDv8 "Signature"
}
else
{
throw new Exception("Invalid version");
}
// Then: Sort block 4, Big Endian
if (hickelUuidVersion == 1)
{
dt = DateTime.Now;
block4 = String.Format("{0:yy}{1:D2}", dt, (int)Math.Floor((decimal)dt.Year / 100)); // Example: 0x2420 = 2024
}
else
{
dt = DateTime.UtcNow;
int variant = 0x8; // First nibble needs to be 0b10__ (0x8-0xB) for RFC 9562.
int unused2bits = 0; // The two more bits CANNOT be used for random bits, because it would affect the sorting
block4 = String.Format("{0:X1}{1:X3}", variant + (unused2bits & 0x3), dt.Year);
}
// Then: Sort block 3, Little Endian
if (hickelUuidVersion == 1)
{
block3 = String.Format("{0:ddMM}", dt); // Little Endian is correct here
}
else
{
string ver = String.Format("{0:X1}", 8); // First nibble needs to be "8" for "UUIDv8 = Custom UUID"
string doy = String.Format("{0:X3}", dt.DayOfYear/*1..366*/);
if (hickelUuidVersion == 2)
{
block3 = ver + doy; // This is wrong! (Big Endian instead of Little Endian)
}
else
{
block3 = ver + doy.Substring(2, 1) + doy.Substring(0, 2); // doy 123 => 83 12 (Little Endian with UUID version at nibble 3)
}
}
// Then: Sort block 2, Little Endian
if (hickelUuidVersion == 1)
{
block2 = String.Format("{0:mmHH}", dt); // Little Endian is correct here
}
else
{
int minuteOfDay = (dt.Minute + dt.Hour * 60) + 1; // 1..1440
string mot = String.Format("{0:X4}", minuteOfDay);
if (hickelUuidVersion == 2)
{
block2 = mot; // This is wrong! (Big Endian instead of Little Endian)
}
else
{
block2 = mot.Substring(2, 2) + mot.Substring(0, 2); // mot 1234 => 34 12 (Little Endian)
}
}
// Then: Sort block 1, Little Endian
int millisecond8bits = (int)Math.Round(((decimal)dt.Millisecond / 999) * 255); // deviation -2ms..2ms
if (hickelUuidVersion == 1)
{
int rnd16bits = random.Next(0x0000, 0xFFFF);
block1 = String.Format("{0:X4}{1:X2}{2:ss}", rnd16bits, millisecond8bits, dt); // Little Endian is correct here
}
else
{
int rnd16bits = random.Next(0x0000, 0xFFFF + 1);
block1 = String.Format("{0:X4}{1:X2}{2:X2}", rnd16bits, millisecond8bits, dt.Second); // Little Endian is correct here
}
// Now build and parse UUID
string sGuid = String.Format("{0}-{1}-{2}-{3}-{4}", block1, block2, block3, block4, block5);
Thread.Sleep((int)Math.Ceiling((decimal)999 / 255)); // Make sure that "millisecond" is not repeated on this system
return Guid.Parse(sGuid);
}
static void Main(string[] args)
{
Console.WriteLine(DateTime.Now);
Guid test;
test = GenerateSqlServerSortableTimeBasedGuid(1);
Console.WriteLine(test);
test = GenerateSqlServerSortableTimeBasedGuid(2);
Console.WriteLine(test);
test = GenerateSqlServerSortableTimeBasedGuid(3);
Console.WriteLine(test);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment