=encoding utf8
=head1 NAME
ulid - Universally Unique Lexicographically Sortable Identifiers.
=head1 SYNOPSIS
from ulid import create_ulid, create_ulid_binary;
let text := create_ulid();
let raw := create_ulid_binary();
=head1 IMPLEMENTATION SUPPORT
This module is supported by all implementations of ZuzuScript.
=head1 DESCRIPTION
This module generates ULIDs using a 48-bit Unix timestamp in
milliseconds and 80 bits of cryptographically secure randomness.
The text form uses canonical uppercase Crockford Base32.
Identifiers generated within the same millisecond are monotonic: the
random component is incremented so that each later identifier sorts
after the previous one.
=head1 EXPORTS
=head2 Functions
=over
=item * C<create_ulid_binary()>
Parameters: none. Returns: C<BinaryString>. Returns a single ULID as 16
raw bytes in network byte order.
=item * C<create_ulid()>
Parameters: none. Returns: C<String>. Returns a single ULID as 26
uppercase Crockford Base32 characters.
=back
=head1 COPYRIGHT AND LICENCE
B<ulid> is copyright Toby Inkster.
It is free software; you may redistribute it and/or modify it under
the terms of either the Artistic License 1.0 or the GNU General Public
License version 2.
=cut
from std/secure import SecureRandom;
from std/string import substr;
from std/string/base64 import decode;
from std/time import Time;
const _B32_ALPHABET := "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
const _B64_ALPHABET :=
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const _MAX_TIMESTAMP := 281474976710655;
let _last_timestamp := -1;
let _last_random := [];
function _div_floor ( Number n, Number d ) {
return floor( n / d );
}
function _mod ( Number n, Number d ) {
return n - _div_floor( n, d ) * d;
}
function _bytes_to_binary ( Array bytes ) {
let out := "";
let i := 0;
let n := bytes.length();
while ( i < n ) {
let b0 := bytes[i];
let b1 := null;
let b2 := null;
if ( i + 1 < n ) {
b1 := bytes[i + 1];
}
if ( i + 2 < n ) {
b2 := bytes[i + 2];
}
let c0 := _div_floor( b0, 4 );
let c1 := _mod( b0, 4 ) * 16;
let c2 := 64;
let c3 := 64;
if ( b1 ≢ null ) {
c1 += _div_floor( b1, 16 );
c2 := _mod( b1, 16 ) * 4;
if ( b2 ≢ null ) {
c2 += _div_floor( b2, 64 );
c3 := _mod( b2, 64 );
}
}
out _= substr( _B64_ALPHABET, c0, 1 );
out _= substr( _B64_ALPHABET, c1, 1 );
out _= c2 ≡ 64
? "="
: substr( _B64_ALPHABET, c2, 1 );
out _= c3 ≡ 64
? "="
: substr( _B64_ALPHABET, c3, 1 );
i += 3;
}
return decode(out);
}
function _timestamp_to_bytes ( Number timestamp ) {
if (
timestamp < 0
or timestamp > _MAX_TIMESTAMP
or timestamp ≢ floor(timestamp)
) {
die "ULID timestamp must be an integer from 0 to 2^48 - 1";
}
return [
_mod( _div_floor( timestamp, 1099511627776 ), 256 ),
_mod( _div_floor( timestamp, 4294967296 ), 256 ),
_mod( _div_floor( timestamp, 16777216 ), 256 ),
_mod( _div_floor( timestamp, 65536 ), 256 ),
_mod( _div_floor( timestamp, 256 ), 256 ),
_mod( timestamp, 256 ),
];
}
function _random_bytes () {
let bytes := [];
while ( bytes.length() < 10 ) {
bytes.push( SecureRandom.int(256) );
}
return bytes;
}
function _increment_random ( Array bytes ) {
let i := 9;
while ( i >= 0 ) {
if ( bytes[i] < 255 ) {
bytes[i]++;
return bytes;
}
bytes[i] := 0;
i--;
}
die "ULID random component overflow";
}
function _create_ulid_bytes () {
let timestamp := floor( new Time().epoch() * 1000 );
let random := [];
if ( timestamp ≡ _last_timestamp ) {
random := _last_random.copy();
_increment_random(random);
}
else {
random := _random_bytes();
}
_last_timestamp := timestamp;
_last_random := random.copy();
let bytes := _timestamp_to_bytes(timestamp);
for ( let byte in random ) {
bytes.push(byte);
}
return bytes;
}
function _bytes_to_ulid_text ( Array bytes ) {
if ( bytes.length() ≢ 16 ) {
die "ULID binary value must contain exactly 16 bytes";
}
let out := "";
let buffer := 0;
let bits := 2;
for ( let byte in bytes ) {
if ( byte < 0 or byte > 255 or byte ≢ floor(byte) ) {
die "ULID byte values must be integers from 0 to 255";
}
buffer := buffer * 256 + byte;
bits += 8;
while ( bits >= 5 ) {
bits -= 5;
let divisor := 2 ** bits;
let digit := _div_floor( buffer, divisor );
buffer := _mod( buffer, divisor );
out _= substr( _B32_ALPHABET, digit, 1 );
}
}
return out;
}
function create_ulid_binary () {
return _bytes_to_binary( _create_ulid_bytes() );
}
function create_ulid () {
return _bytes_to_ulid_text( _create_ulid_bytes() );
}
modules/ulid.zzm
ulid-0.0.1 source code
Package
- Name
- ulid
- Version
- 0.0.1
- Uploaded
- 2026-07-30 00:39:59
- Repository
- https://github.com/tobyink/zuzu-ulid
- Dependencies
-
-
std/secure>= 0 -
std/string>= 0 -
std/string/base64>= 0 -
std/time>= 0
-
- Metadata
- zuzu-distribution.json
- Archive
- Download .tar.gz