Skip to content

ULID Generator — Swift source

Generate Universally Unique Lexicographically Sortable Identifiers (ULID) - 26-character Crockford-base32 strings that sort by millisecond timestamp. Paste any ULID to decode its timestamp and randomness. Runs entirely in your browser.

This is the Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.

// ulid-generator - 26-char Crockford-base32 sortable ID: 10 chars of 48-bit ms timestamp + 16 chars of 80-bit randomness. Language: Swift (5.9+, stdlib only). Port of src/lib/ulid.ts (Go twin: cli/ulid-generator) - encode/generate/decode-time core; inspectUlid (date/random-hex view) lives in this dir's javascript.js (80-line budget).

let crockford = Array("0123456789ABCDEFGHJKMNPQRSTVWXYZ") // no I, L, O, U
let decodeMap: [Character: Int] = Dictionary(uniqueKeysWithValues: crockford.enumerated().map { ($1, $0) })

/// Divide the big-endian value in b (mutated in place) by 32; return the
/// remainder 0-31. Long division in base 256, exactly as in the Go twin.
func divBy32(_ b: inout [UInt8]) -> Int {
    var rem = 0
    for i in b.indices {
        let cur = (rem << 8) | Int(b[i])
        b[i] = UInt8(cur >> 5)
        rem = cur & 0x1F
    }
    return rem
}

/// Base32-encode the 128-bit big-endian [6 time bytes | 10 random bytes] into
/// the canonical 26 chars. Because 32^16 == 2^80, the first 10 chars equal the
/// TS/Go twins' separate time encoding - the cross-language lock-step anchor.
func encodeUlid(_ timeBytes: [UInt8], _ randomBytes: [UInt8]) -> String {
    var b = timeBytes + randomBytes
    var out = [Character](repeating: "0", count: 26)
    for i in stride(from: 25, through: 0, by: -1) { out[i] = crockford[divBy32(&b)] }
    return String(out)
}

/// 48-bit ms timestamp -> 6 big-endian bytes (MSB first), like TS timeBytes().
func timeBytes(_ ms: UInt64) -> [UInt8] {
    (0..<6).reversed().map { UInt8((ms >> (8 * UInt64($0))) & 0xFF) }
}

/// New ULID for the epoch-ms. SystemRandomNumberGenerator is Swift's stdlib
/// CSPRNG - the analogue of Go's crypto/rand.
func generateUlid(_ ms: UInt64) -> String {
    var rng = SystemRandomNumberGenerator()
    var rnd = [UInt8]()
    while rnd.count < 10 {
        let chunk = rng.next().littleEndian // 8 fresh random bytes
        withUnsafeBytes(of: chunk) { rnd.append(contentsOf: $0) }
    }
    return encodeUlid(timeBytes(ms), Array(rnd.prefix(10)))
}

/// Extract the 48-bit ms timestamp from the first 10 chars; nil on malformed
/// input (wrong length, or a char outside the alphabet such as I/L/O/U).
func decodeUlidTime(_ id: String) -> UInt64? {
    guard id.count == 26 else { return nil }
    var ts: UInt64 = 0
    for ch in id.prefix(10) {
        guard let v = decodeMap[ch] else { return nil }
        ts = ts * 32 + UInt64(v)
    }
    return ts
}

let ulid = generateUlid(1_722_000_000_000)
print("ulid:     \(ulid)")
if let ms = decodeUlidTime(ulid) { print("time(ms): \(ms)  (round-trip ok)") }

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →