// Package ulid generates ULIDs (https://github.com/ulid/spec): 26-character // Crockford-base32 strings of a 48-bit millisecond timestamp followed by // 80 bits of cryptographic randomness. Lexicographic order equals creation // order at millisecond resolution, which the schema relies on for cursor // pagination and time-ordered message ids. package ulid import ( "crypto/rand" "fmt" "time" ) const alphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" // Len is the length of the canonical string form. const Len = 26 var decode [256]byte func init() { for i := range decode { decode[i] = 0xFF } for i := 0; i < len(alphabet); i++ { decode[alphabet[i]] = byte(i) // Crockford base32 is case-insensitive. decode[alphabet[i]|0x20] = byte(i) } } // New returns a fresh ULID for the current time. func New() string { return At(time.Now()) } // At returns a ULID whose timestamp component is t (randomness still fresh). func At(t time.Time) string { var b [16]byte ms := uint64(t.UnixMilli()) b[0] = byte(ms >> 40) b[1] = byte(ms >> 32) b[2] = byte(ms >> 24) b[3] = byte(ms >> 16) b[4] = byte(ms >> 8) b[5] = byte(ms) if _, err := rand.Read(b[6:]); err != nil { // crypto/rand never fails on supported platforms; if it does, the // process cannot safely generate ids at all. panic(fmt.Sprintf("ulid: crypto/rand failed: %v", err)) } // Base32-encode 128 bits into 26 chars (130-bit capacity, top 2 bits 0): // consume bytes from the least-significant end, emit 5-bit groups // right-to-left, which yields the canonical big-endian representation. var out [Len]byte acc, nbits, j := uint32(0), 0, Len-1 for i := 15; i >= 0; i-- { acc |= uint32(b[i]) << nbits nbits += 8 for nbits >= 5 && j > 0 { out[j] = alphabet[acc&31] acc >>= 5 nbits -= 5 j-- } } out[0] = alphabet[acc&31] // remaining 3 timestamp bits return string(out[:]) } // IsValid reports whether s is a well-formed ULID. func IsValid(s string) bool { if len(s) != Len { return false } for i := 0; i < Len; i++ { if decode[s[i]] == 0xFF { return false } } // First char encodes the top 3 bits of a 48-bit value; > '7' overflows. return decode[s[0]] <= 7 } // Timestamp extracts the embedded creation time. func Timestamp(s string) (time.Time, error) { if !IsValid(s) { return time.Time{}, fmt.Errorf("ulid: invalid id %q", s) } var ms uint64 for i := 0; i < 10; i++ { // first 10 chars = 50 bits, low 48 are the timestamp ms = ms<<5 | uint64(decode[s[i]]) } return time.UnixMilli(int64(ms)).UTC(), nil }