var generator = new CodeGenerator();
var dateTime = new DateTimeOffset(2000, 01, 01, 22, 59, 59, TimeSpan.Zero);
var code = generator.GenerateCode(dateTime);
generator.TryExtractDateTime(code, out var dateTimeFromCode);
Console.WriteLine(dateTimeFromCode);
public class CodeGenerator
public string GenerateCode() => GenerateCode(DateTimeOffset.UtcNow);
public string GenerateCode(DateTimeOffset datetime)
return TimeUuid.NewId(datetime.ToUniversalTime()).ToString();
public bool TryExtractDateTime(string code, out DateTimeOffset? dateTime)
if (Guid.TryParse(code, out _))
dateTime = TimeUuid.Parse(code).GetDate().UtcDateTime;
public readonly struct TimeUuid : IEquatable<TimeUuid>, IComparable<TimeUuid>
private static readonly DateTimeOffset GregorianCalendarTime = new DateTimeOffset(1582, 10, 15, 0, 0, 0, TimeSpan.Zero);
private static readonly Random RandomGenerator = new Random();
private static readonly object RandomLock = new object();
private static readonly byte[] MinNodeId = { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 };
private static readonly byte[] MinClockId = { 0x80, 0x80 };
private static readonly byte[] MaxNodeId = { 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f };
private static readonly byte[] MaxClockId = { 0x7f, 0x7f };
private readonly Guid _value;
private TimeUuid(Guid value)
private TimeUuid(byte[] nodeId, byte[] clockId, DateTimeOffset time)
if (nodeId == null || nodeId.Length != 6)
throw new ArgumentException("node Id should contain 6 bytes", nameof(nodeId));
if (clockId == null || clockId.Length != 2)
throw new ArgumentException("clock Id should contain 2 bytes", nameof(clockId));
var timeBytes = BitConverter.GetBytes((time - GregorianCalendarTime).Ticks);
if (!BitConverter.IsLittleEndian)
Array.Reverse(timeBytes);
var buffer = new byte[16];
Buffer.BlockCopy(timeBytes, 0, buffer, 0, 8);
Buffer.BlockCopy(clockId, 0, buffer, 8, 2);
Buffer.BlockCopy(nodeId, 0, buffer, 10, 6);
_value = new Guid(buffer);
public bool Equals(TimeUuid other)
return _value.Equals(other._value);
public override bool Equals(object obj)
var otherTimeUuid = obj as TimeUuid?;
return otherTimeUuid != null && Equals(otherTimeUuid.Value);
public DateTimeOffset GetDate()
var bytes = _value.ToByteArray();
if (!BitConverter.IsLittleEndian)
var timestamp = BitConverter.ToInt64(bytes, 0);
var ticks = timestamp + GregorianCalendarTime.Ticks;
return new DateTimeOffset(ticks, TimeSpan.Zero);
public override int GetHashCode()
return _value.GetHashCode();
public byte[] ToByteArray()
return _value.ToByteArray();
public int CompareTo(TimeUuid other)
return GetDate().CompareTo(other.GetDate());
public override string ToString()
return _value.ToString();
public string ToString(string format, IFormatProvider provider)
return _value.ToString(format, provider);
public string ToString(string format)
return _value.ToString(format);
public static TimeUuid Min(DateTimeOffset date)
return new TimeUuid(MinNodeId, MinClockId, date);
public static TimeUuid Max(DateTimeOffset date)
return new TimeUuid(MaxNodeId, MaxClockId, date);
public static TimeUuid NewId()
return NewId(DateTimeOffset.Now);
public static TimeUuid NewId(DateTimeOffset date)
RandomGenerator.NextBytes(nodeId);
RandomGenerator.NextBytes(clockId);
return new TimeUuid(nodeId, clockId, date);
public static TimeUuid NewId(byte[] nodeId, byte[] clockId, DateTimeOffset date)
return new TimeUuid(nodeId, clockId, date);
public static TimeUuid Parse(string input)
return new TimeUuid(Guid.Parse(input));
public static implicit operator Guid(TimeUuid value)
public static implicit operator TimeUuid(Guid value)
return new TimeUuid(value);
public static bool operator ==(TimeUuid id1, TimeUuid id2)
return id1.ToGuid() == id2.ToGuid();
public static bool operator !=(TimeUuid id1, TimeUuid id2)
return id1.ToGuid() != id2.ToGuid();