'Return an ULID = 48-bit timestamp + 80-bit randomness
' (Timestamp = ms since 01/01/1970 (48-bit))
Public Sub GenerateULID As String
'USES: LongToBytes48 and EncodeBase32.
Dim ts As Long = DateTime.Now
Dim tsBytes() As Byte = LongToBytes48(ts)
' Randomness = 10 bytes (80-bit)
Dim rndBytes(10) As Byte
For i = 0 To 9
rndBytes(i) = Rnd(0, 256)
Next
' Concatenate timestamp + randomness
Dim all(16) As Byte
For i = 0 To 5
all(i) = tsBytes(i)
Next
For i = 0 To 9
all(6 + i) = rndBytes(i)
Next
' Encode using Crockford Base32 (unambiguous)
Return EncodeBase32(all)
End Sub
' ============================
' Convert Long → 48-bit
' ============================
Private Sub LongToBytes48(l As Long) As Byte()
Dim b(6) As Byte
For i = 5 To 0 Step -1
b(i) = Bit.And(l, 0xFF)
l = Bit.UnsignedShiftRight(l, 8)
Next
Return b
End Sub
' ============================
' Crockford Base32
' ============================
Private Sub EncodeBase32(data() As Byte) As String
Dim alphabet As String = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
Dim bits As Long = 0
Dim bitCount As Int = 0
Dim out As StringBuilder
out.Initialize
For Each b As Byte In data
bits = Bit.Or(Bit.ShiftLeft(bits, 8), Bit.And(b, 0xFF))
bitCount = bitCount + 8
Do While bitCount >= 5
bitCount = bitCount - 5
Dim index As Int = Bit.And(Bit.UnsignedShiftRight(bits, bitCount), 31)
out.Append(alphabet.CharAt(index))
Loop
Next
If bitCount > 0 Then
Dim index As Int = Bit.And(Bit.ShiftLeft(bits, (5 - bitCount)), 31)
out.Append(alphabet.CharAt(index))
End If
Return out.ToString
End Sub