joby/tid

A lightweight time-ordered random ID system designed for human-scale applications.

v1.0.0-beta.1 2025-08-04 17:04 UTC

This package is auto-updated.

Last update: 2025-08-04 17:06:50 UTC


README

A simple and lightweight time-ordered random ID library designed for human-scale applications.

What is php-tid?

php-tid is a simple, lightweight library for generating unique, time-ordered, human-readable IDs. Unlike UUIDs or auto-incrementing database IDs, Tids (Time-ordered IDs) are:

  • Human-readable: Formatted with optional dashes for better readability (e.g., "abcd-efgh")
  • Time-ordered: Can be represented as integers or strings, and both are naturally sortable by rough creation time
  • Compact: Uses alphanumeric encoding for concise representation of the underlying integer (they are currently eight characters, but will expand slowly as time progresses)
  • URL-safe: No special characters that are hard to type or need encoding in URLs
  • Privacy-conscious: Drops timestamp precision to avoid leaking exact creation times
  • Ergonomic: Strings can be easily converted from the concise format back into the underlying integer, using dashes or not for readability

Why php-tid?

Not every project needs globally unique IDs or distributed systems. For many smaller applications, simpler solutions are often better:

  • Human scale: Designed for applications where IDs might be seen, shared, or even typed by humans
  • Simplicity: No external dependencies or complex setup
  • Lightweight: Minimal overhead and easy integration
  • Chronological: Natural time-based ordering at a resolution of about 72 hours, without revealing exact timestamps

Installation

composer require joby/tid

Basic Usage

Creating a new Tid

use Joby\Tid\Tid;

// Generate a new Tid
$tid = new Tid();
echo $tid; // Outputs something like "abcd-efgh"

// Get the compact representation (without dashes)
echo $tid->compactString(); // Outputs something like "abcdefgh"

Creating a Tid from an existing string

use Joby\Tid\Tid;

// Create a Tid from a string
$tid = Tid::fromString("abcd-efgh");
// or
$tid = Tid::fromString("abcdefgh"); // Dashes are optional when parsing

Working with timestamps

use Joby\Tid\Tid;

$tid = new Tid();

// Get the approximate timestamp when this Tid was created
$timestamp = $tid->time();
echo date('Y-m-d H:i:s', $timestamp);

// Get the entropy bits (random portion) of the Tid
$entropy = $tid->entropy();

Validation

use Joby\Tid\TidHelper;

// Validate a Tid string
if (TidHelper::validateString("abcd-efgh")) {
    echo "Valid Tid string!";
}

// Validate a Tid integer
$int = TidHelper::toInt("abcd-efgh");
if (TidHelper::validateInt($int)) {
    echo "Valid Tid integer!";
}

Advanced Usage

Using the underlying integer

use Joby\Tid\Tid;
use Joby\Tid\TidHelper;

// Create a Tid
$tid = new Tid();

// Get the underlying integer
$int = $tid->id;

// Convert back to a Tid
$sameTid = new Tid($int);
// or
$sameTid = Tid::fromInt($int);

Serialization

Tid objects can be serialized and unserialized:

use Joby\Tid\Tid;

$tid = new Tid();
$serialized = serialize($tid);
$unserialized = unserialize($serialized);

echo $tid === $unserialized; // false (different objects)
echo $tid->id === $unserialized->id; // true (same ID value)

Using with databases

Tids can be stored in your database as either strings or integers:

// Store as a string (more readable)
$db->query("INSERT INTO users (id, name) VALUES (?, ?)", [(string)$tid, "John"]);

// Store as an integer (more efficient)
$db->query("INSERT INTO users (id, name) VALUES (?, ?)", [$tid->id, "John"]);

How It Works

Each Tid consists of a single integer value with two parts:

  1. A timestamp component (with 18 precision bits dropped for privacy and to make room for entropy)
  2. Random entropy bits to ensure uniqueness

The combination is encoded in base-36 (alphanumeric) and formatted with dashes for readability.

Limitations

  • Not designed or suitable for distributed systems requiring guaranteed global uniqueness
  • Time ordering is approximate due to the dropped precision bits
  • No built-in collision detection (though collisions are extremely unlikely at human scale applications)

Collision probability

The time portion of a Tid resets every 2^18 seconds, which is roughly 72 hours. There are 32 bits of entropy. This leaves 2^14 Tids available per second. So the odds of any given Tid colliding can be calculated by the frequency they are being generated by in your app:

Frequency of generation Odds of collison (1 in) Odds of collision in 72h
1/second 16,384 ~100%
1/minute 983,040 ~0.4%
1/hour ~59 million ~0.00006%
1/day ~1.4 billion ~0.0000001%
1/week for intervals over 72h collisions are impossible

A rough rule of thumb is that to preserve better than one in a million odds of collisions per 72 hour window, you shouldn't use TIDs for systems generating more than about 1.3 IDs per hour. So it's perfectly acceptable for things like blog posts on personal sites, or even microblogging on a small scale. If you implement collision detection in your app they're even potentially viable for higher-volume things. They would never really be appropriate for things like logging though, where there might be thousands of entries being generated per hour or more.