ULID Generator — Zig 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 Zig 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: Zig (0.12+). 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).
const std = @import("std");
const crockford = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; // no I, L, O, U
/// 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.
fn divBy32(b: []u8) u8 {
var rem: u16 = 0;
for (b) |*byte| {
const cur: u16 = (rem << 8) | byte.*;
byte.* = @intCast(cur >> 5);
rem = cur & 0x1F;
}
return @intCast(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.
fn encodeUlid(time_bytes: [6]u8, random_bytes: [10]u8) [26]u8 {
var b: [16]u8 = time_bytes ++ random_bytes;
var out: [26]u8 = undefined;
var i: usize = 26;
while (i > 0) {
i -= 1;
out[i] = crockford[divBy32(&b)];
}
return out;
}
/// 48-bit ms timestamp -> 6 big-endian bytes (MSB first), like TS timeBytes().
fn timeBytesOf(ms: u64) [6]u8 {
var t: [6]u8 = undefined;
var i: usize = 6;
while (i > 0) {
i -= 1;
t[i] = @truncate(ms >> @intCast(8 * i));
}
return t;
}
/// Extract the 48-bit ms timestamp from the first 10 chars; error.InvalidChar
/// on a char outside the alphabet (I/L/O/U), error.BadLength on wrong length.
fn decodeUlidTime(id: []const u8) !u64 {
if (id.len != 26) return error.BadLength;
var ts: u64 = 0;
for (id[0..10]) |c| {
const idx = std.mem.indexOfScalar(u8, crockford, c) orelse return error.InvalidChar;
ts = ts * 32 + idx;
}
return ts;
}
pub fn main() !void {
var rnd: [10]u8 = undefined;
std.crypto.random.bytes(&rnd); // stdlib CSPRNG - the analogue of Go's crypto/rand
const ulid = encodeUlid(timeBytesOf(1722000000000), rnd);
std.debug.print("ulid: {s}\n", .{ulid});
const ts = try decodeUlidTime(&ulid);
std.debug.print("time(ms): {d} (round-trip ok)\n", .{ts});
}
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 →