ULID Generator — C 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 C 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: C (C99, 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). */
#include <stdint.h>
#include <stdio.h>
#include <string.h>
static const char CROCKFORD[33] = "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. */
static int div_by_32(uint8_t *b, int len) {
int rem = 0;
for (int i = 0; i < len; i++) {
int cur = (rem << 8) | b[i];
b[i] = (uint8_t)(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. */
static void encode_ulid(const uint8_t time_b[6], const uint8_t rnd_b[10], char out[27]) {
uint8_t b[16];
memcpy(b, time_b, 6);
memcpy(b + 6, rnd_b, 10);
for (int i = 25; i >= 0; i--) out[i] = CROCKFORD[div_by_32(b, 16)];
out[26] = '\0';
}
/* 48-bit ms timestamp -> 6 big-endian bytes (MSB first), like TS timeBytes(). */
static void time_bytes(uint64_t ms, uint8_t out[6]) {
for (int i = 5; i >= 0; i--, ms >>= 8) out[i] = (uint8_t)(ms & 0xFF);
}
/* Demo RNG: xorshift64*. C's stdlib has no CSPRNG - production code wants
getrandom(2)/arc4random_buf, the analogue of Go's crypto/rand. */
static uint64_t rng_state = 88172645463325252ULL;
static uint8_t rng_byte(void) {
rng_state ^= rng_state >> 12; rng_state ^= rng_state << 25; rng_state ^= rng_state >> 27;
return (uint8_t)((rng_state * 2685821657736338717ULL) >> 56);
}
/* Extract the 48-bit ms timestamp from the first 10 chars; -1 on malformed
input (wrong length, or a char outside the alphabet such as I/L/O/U). */
static int decode_ulid_time(const char *id, uint64_t *out) {
if (strlen(id) != 26) return -1;
uint64_t ts = 0;
for (int i = 0; i < 10; i++) {
const char *p = strchr(CROCKFORD, id[i]);
if (p == NULL) return -1;
ts = ts * 32 + (uint64_t)(p - CROCKFORD);
}
*out = ts;
return 0;
}
int main(void) {
uint8_t tb[6], rb[10];
time_bytes(1722000000000ULL, tb); /* fixed epoch-ms so the time chars are deterministic */
for (int i = 0; i < 10; i++) rb[i] = rng_byte();
char ulid[27];
encode_ulid(tb, rb, ulid);
printf("ulid: %s\n", ulid);
uint64_t ms;
if (decode_ulid_time(ulid, &ms) == 0)
printf("time(ms): %llu (round-trip ok)\n", (unsigned long long)ms);
return 0;
}
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 →