using System.Runtime.InteropServices; using System.Security.Cryptography; namespace Just.Core; public static class GuidV8 { private const long TicksPrecision = TimeSpan.TicksPerMillisecond / 10; // 100-microsecond units [MethodImpl(MethodImplOptions.AggressiveInlining)] [ExcludeFromCodeCoverage] public static Guid NewGuid(RngEntropy entropy = RngEntropy.Strong) => NewGuid(DateTime.UtcNow, entropy); public static Guid NewGuid(DateTime dateTime, RngEntropy entropy = RngEntropy.Strong) { var epoch = dateTime.Subtract(DateTime.UnixEpoch); var timestamp = epoch.Ticks / TicksPrecision; // Negative timestamps can't be encoded correctly due to unsigned bit operations if (timestamp < 0) { throw new ArgumentException("Timestamp must be on or after UnixEpoch (1970-01-01 UTC).", nameof(dateTime)); } uint tsHigh = (uint)((timestamp >> 16) & 0xFFFFFFFF); ushort tsLow = (ushort)(timestamp & 0x0000FFFF); Span bytes = stackalloc byte[10]; if (entropy == RngEntropy.Strong) { RandomNumberGenerator.Fill(bytes); } else { Random.Shared.NextBytes(bytes); } bytes[0] = (byte)((bytes[0] & 0x0F) | 0x80); // Version 8 bytes[2] = (byte)((bytes[2] & 0x1F) | 0x80); // Variant 0b1000 ushort version = (ushort)((bytes[0] << 8) | bytes[1]); return new Guid( tsHigh, tsLow, version, bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9]); } /// /// Extracts the timestamp from a UUID v8 generated by . /// /// The UUID v8 to extract the timestamp from. /// The (UTC) encoded in the GUID's timestamp fields. /// /// Thrown if is not a UUID v8. /// /// /// The returned timestamp has 100-microsecond precision (the resolution used by /// ). Sub-100µs components of the original /// are not recoverable. /// [Pure] public static DateTime ExtractTimestamp(Guid guid) { Span bytes = stackalloc byte[16]; guid.TryWriteBytes(bytes); // Version is the high nibble of byte 7 (parameter 'c' high byte, little-endian byte 7) var version = bytes[7] >> 4; if (version != 8) throw new ArgumentException($"The provided GUID is not a UUID v8 (version={version}).", nameof(guid)); // tsHigh is stored as Int32 at bytes 0-3 uint tsHigh = MemoryMarshal.Read(bytes); // tsLow is stored as Int16 at bytes 4-5 ushort tsLow = MemoryMarshal.Read(bytes[4..]); long timestamp = ((long)tsHigh << 16) | tsLow; return DateTime.UnixEpoch.AddTicks(timestamp * TicksPrecision); } }