Skip to content

ULID Generator — Ruby 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 Ruby 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: Ruby (3.1+, 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).

module Ulid
  CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ' # no I, L, O, U
  DECODE = CROCKFORD.chars.each_with_index.to_h { |c, i| [c, i] }.freeze

  module_function

  # Base32-encode the 128-bit big-endian value [time (48 bits) << 80 | random]
  # into the canonical 26 chars. Ruby Integers are arbitrary precision, so the
  # whole-value path replaces the byte-array long division of the other ports.
  # Because 32**16 == 2**80, the first 10 chars equal the TS/Go twins' separate
  # time encoding - the cross-language lock-step anchor.
  def encode(time_ms, random_bytes)
    value = ((time_ms & 0xFFFFFFFFFFFF) << 80) |
            random_bytes.bytes.reduce(0) { |acc, b| (acc << 8) | b }
    (+'').tap do |out|
      26.times do
        out.prepend(CROCKFORD[value & 31])
        value >>= 5
      end
    end
  end

  # New ULID for the epoch-ms; Random.bytes is Ruby's CSPRNG, the analogue of
  # Go's crypto/rand.
  def generate(time_ms)
    encode(time_ms, Random.bytes(10))
  end

  # Extract the 48-bit ms timestamp from the first 10 chars; raises on
  # malformed input (wrong length, or a char outside the alphabet such as I/L/O/U).
  def decode_time(ulid)
    raise ArgumentError, "ulid: malformed id: length #{ulid.length}, want 26" unless ulid.length == 26

    ulid[0, 10].each_char.reduce(0) do |ts, ch|
      v = DECODE[ch]
      raise ArgumentError, "ulid: invalid character #{ch.inspect}" unless v

      ts * 32 + v
    end
  end
end

ulid = Ulid.generate(1_722_000_000_000)
puts "ulid:     #{ulid}"
puts "time(ms): #{Ulid.decode_time(ulid)}  (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 →