How does uuid::Builder::from_rfc4122_timestamp enable time-based UUID generation with millisecond precision?
uuid::Builder::from_rfc4122_timestamp creates UUID version 1 identifiers by encoding a 60-bit timestamp (100-nanosecond intervals since October 15, 1582), a clock sequence, and a node ID into the UUID structure, enabling deterministic, time-ordered identifiers with millisecond-level precision while maintaining RFC 4122 compliance. The function allows manual control over timestamp, clock sequence, and node ID components that would normally be generated automatically.
Understanding UUID Version 1 Structure
use uuid::Uuid;
fn uuid_v1_structure() {
// UUID v1 consists of:
// - 60-bit timestamp (100ns intervals since Gregorian epoch)
// - 14-bit clock sequence (prevents duplicates on clock regression)
// - 48-bit node ID (typically MAC address)
// The timestamp provides roughly millisecond-level granularity
// 100ns = 0.1 microseconds = 0.0001 milliseconds
// Total: 128 bits
// - 60 bits: timestamp
// - 14 bits: clock sequence
// - 48 bits: node ID
// - 6 bits: version/variant markers
}UUID v1 encodes time, clock sequence, and node into a unique identifier.
Basic from_rfc4122_timestamp Usage
use uuid::Builder;
use uuid::timestamp::{Timestamp, ClockSequence, Context};
fn basic_usage() {
// Create a timestamp from the current time
let context = Context::new(rand::random());
let ts = Timestamp::now(context);
// Build UUID from timestamp
let uuid = Builder::from_rfc4122_timestamp(&ts)
.into_uuid();
println!("UUID v1: {}", uuid);
println!("Version: {:?}", uuid.get_version());
// Version: Some(V1) - Time-based
}from_rfc4122_timestamp constructs a UUID from timestamp components.
Manual Timestamp Construction
use uuid::timestamp::{Timestamp, ClockSequence, Context};
use uuid::Builder;
fn manual_timestamp() {
// Create timestamp with specific clock sequence
let context = Context::new(42); // Clock sequence
let ts = Timestamp::now(context);
// Build UUID from this timestamp
let uuid = Builder::from_rfc4122_timestamp(&ts).into_uuid();
// The timestamp encodes:
// - time_low: bits 0-31 of timestamp
// - time_mid: bits 32-47 of timestamp
// - time_hi_and_version: bits 48-59 with version marker
}The timestamp is constructed with a clock sequence context.
Understanding the 100-Nanosecond Timestamp
use uuid::timestamp::{Timestamp, Context};
use uuid::Builder;
fn timestamp_precision() {
// RFC 4122 uses 100-nanosecond intervals since October 15, 1582
// This is the Gregorian epoch (start of Gregorian calendar)
// Precision: 100ns = 0.0001 milliseconds = 10,000 intervals per millisecond
// This is finer than millisecond precision!
// Convert from Unix timestamp (seconds since 1970)
// to RFC 4122 timestamp (100ns intervals since 1582)
// Unix epoch: January 1, 1970
// Gregorian epoch: October 15, 1582
// Difference: 122,192,928,000,000,000 100ns intervals
const GREGORIAN_UNIX_OFFSET: u64 = 122_192_928_000_000_000;
// Current time in 100ns intervals
let now_unix_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as u64;
let now_100ns = (now_unix_ms * 10_000) + GREGORIAN_UNIX_OFFSET;
// But Timestamp::now handles this conversion
}The timestamp uses 100-nanosecond precision, finer than milliseconds.
Extracting Timestamp from UUID
use uuid::Uuid;
use uuid::timestamp::Timestamp;
use std::time::SystemTime;
fn extract_timestamp() {
// Generate a v1 UUID
let uuid = Uuid::now_v1();
// Extract the timestamp
let ts = uuid.get_timestamp().unwrap();
// Get the raw counter (100ns intervals)
let counter = ts.counter();
println!("Timestamp counter: {}", counter);
// Convert to actual time
// The counter is 100ns intervals since Gregorian epoch
// Subtract Gregorian-Unix offset to get Unix timestamp
// For display purposes:
let seconds = counter / 10_000_000; // Convert 100ns intervals to seconds
let nanos_remaining = (counter % 10_000_000) * 100; // Nanoseconds
println!("Timestamp: {} seconds, {} nanoseconds", seconds, nanos_remaining);
}The timestamp can be extracted from an existing UUID v1.
Clock Sequence and Context
use uuid::timestamp::{Context, ClockSequence};
use uuid::Builder;
fn clock_sequence() {
// Clock sequence prevents duplicates when:
// - Clock goes backwards (NTP adjustment, manual change)
// - System restarts with stale clock
// - UUIDs generated on different machines might collide
// Context holds the clock sequence
let context1 = Context::new(0x1234);
let context2 = Context::new(0x5678);
// Same timestamp, different clock sequences = different UUIDs
let ts1 = uuid::timestamp::Timestamp::now(context1);
let ts2 = uuid::timestamp::Timestamp::now(context2);
let uuid1 = Builder::from_rfc4122_timestamp(&ts1).into_uuid();
let uuid2 = Builder::from_rfc4122_timestamp(&ts2).into_uuid();
// uuid1 and uuid2 have same time but different clock_seq
// This prevents collisions on different machines
}The clock sequence prevents collisions across machines.
Node ID Configuration
use uuid::timestamp::{Timestamp, Context, NodeId};
use uuid::Builder;
fn node_id() {
// Node ID is typically the MAC address (48 bits)
// But can be any unique identifier
// Automatic node ID (from MAC address if available)
let context = Context::new(rand::random());
// Custom node ID
let custom_node = NodeId::from_bytes([0x01, 0x23, 0x45, 0x67, 0x89, 0xAB]);
// Extract node from UUID
let uuid = Uuid::now_v1();
if let Some(node) = uuid.get_node_id() {
println!("Node ID: {:?}", node);
}
// The node ID combined with timestamp and clock sequence
// ensures global uniqueness
}The node ID provides machine uniqueness.
Building UUID with Custom Components
use uuid::Builder;
use uuid::timestamp::{Timestamp, Context};
fn custom_components() {
// Create a timestamp with specific values
let context = Context::new(0x1234); // Clock sequence
// Build from explicit timestamp
// Timestamp represents 100ns intervals since Gregorian epoch
let counter: u64 = 122_192_928_000_000_000 + (1_000_000 * 10_000); // 1M ms after Unix epoch
let ts = Timestamp::from_rfc4122(context, counter);
let uuid = Builder::from_rfc4122_timestamp(&ts)
.into_uuid();
println!("Custom timestamp UUID: {}", uuid);
}Custom components enable reproducible UUID generation.
Millisecond Precision in Practice
use uuid::Uuid;
use std::time::{SystemTime, Duration};
fn millisecond_generation() {
// Generate UUIDs at millisecond intervals
let uuid1 = Uuid::now_v1();
std::thread::sleep(Duration::from_millis(1));
let uuid2 = Uuid::now_v1();
// The timestamps will differ (at least 1ms = 10,000 100ns intervals)
let ts1 = uuid1.get_timestamp().unwrap();
let ts2 = uuid2.get_timestamp().unwrap();
let diff = ts2.counter().saturating_sub(ts1.counter());
println!("Counter difference: {} 100ns intervals", diff);
println!("Approximately {} ms", diff / 10_000);
}UUIDs generated milliseconds apart have distinguishable timestamps.
Time-Ordered UUIDs
use uuid::{Uuid, Builder};
use uuid::timestamp::Context;
fn time_ordering() {
// UUID v1 IDs are naturally time-ordered
let mut uuids: Vec<Uuid> = Vec::new();
for _ in 0..5 {
uuids.push(Uuid::now_v1());
std::thread::sleep(std::time::Duration::from_millis(10));
}
// UUIDs are sortable by timestamp
uuids.sort();
// Sorted UUIDs will be in chronological order
// This is useful for:
// - Database indexing (time-ordered inserts)
// - Event ordering
// - Log correlation
}UUID v1 timestamps enable natural time ordering.
Handling Clock Regression
use uuid::timestamp::{Timestamp, Context, ClockSequence};
use uuid::Builder;
fn clock_regression() {
// When system clock goes backwards:
// - NTP adjustment
// - Manual clock change
// - VM restore from snapshot
// The clock sequence prevents duplicates
// When regression is detected, clock_seq is incremented
// Example: Simulating regression
let context = Context::new(0x0000);
// "Current" time
let ts1 = Timestamp::from_rfc4122(context.clone(), 100_000_000);
// "Earlier" time (clock went backwards)
// The Context handles this by incrementing clock sequence
let ts2 = Timestamp::from_rfc4122(context, 50_000_000);
// Despite earlier timestamp, clock_seq changed
// So UUIDs remain unique
}The clock sequence handles clock regression gracefully.
Comparison with Other UUID Versions
use uuid::Uuid;
fn version_comparison() {
// UUID v1: Time-based (what we're discussing)
let v1 = Uuid::now_v1();
// Pros: Time-ordered, sortable
// Cons: Reveals timestamp, machine info
// UUID v4: Random
let v4 = Uuid::new_v4();
// Pros: No timing info revealed
// Cons: Not time-ordered, random sort
// UUID v7: Unix timestamp-based (newer)
// v7 uses milliseconds since Unix epoch directly
// Simpler than v1's Gregorian 100ns intervals
// v1 provides higher precision (100ns vs 1ms for v7)
// But v7 is simpler and uses Unix epoch
}UUID v1 offers higher precision than newer time-based alternatives.
Reproducible UUIDs for Testing
use uuid::Builder;
use uuid::timestamp::{Timestamp, Context, NodeId};
fn reproducible_uuids() {
// For testing, create reproducible UUIDs
let context = Context::new(0x1234); // Fixed clock sequence
let counter = 122_192_928_000_000_000u64 + 1_000_000 * 10_000; // Fixed timestamp
let ts = Timestamp::from_rfc4122(context, counter);
let uuid = Builder::from_rfc4122_timestamp(&ts).into_uuid();
// This UUID is deterministic - same inputs = same output
println!("Reproducible UUID: {}", uuid);
// Regenerate with same values
let context2 = Context::new(0x1234);
let ts2 = Timestamp::from_rfc4122(context2, counter);
let uuid2 = Builder::from_rfc4122_timestamp(&ts2).into_uuid();
assert_eq!(uuid, uuid2); // Always same
}Fixed inputs produce reproducible UUIDs for testing.
Security Considerations
use uuid::{Uuid, Builder};
use uuid::timestamp::Context;
fn security_considerations() {
// UUID v1 reveals:
// 1. Timestamp of generation
// 2. Node ID (often MAC address)
// 3. Potentially process/machine info
// This can be a privacy concern:
let uuid = Uuid::now_v1();
let ts = uuid.get_timestamp().unwrap();
let node = uuid.get_node_id().unwrap();
// Timestamp reveals when UUID was created
// Node ID reveals which machine created it
// For security-sensitive applications:
// - Use UUID v4 (random) for privacy
// - Use UUID v7 for time-ordering without machine info
// - Consider hashing the node ID
// Alternative: use custom random node
let random_node = uuid::timestamp::NodeId::from_bytes(rand::random::<[u8; 6]>());
}UUID v1 exposes timestamp and machine information.
Database Indexing Benefits
use uuid::Uuid;
fn database_benefits() {
// Time-ordered UUIDs improve database performance
// B-tree indexes benefit from sequential inserts
let mut uuids: Vec<Uuid> = Vec::new();
// Generate sequential UUIDs
for _ in 0..1000 {
uuids.push(Uuid::now_v1());
std::thread::sleep(std::time::Duration::from_micros(100));
}
// UUIDs are naturally ordered by time
// Inserting into indexed column:
// - B-tree stays balanced
// - Less page splitting
// - Better cache locality
// Compare with UUID v4:
// Random insertions cause more page splits
// B-tree requires more maintenance
}Time-ordered UUIDs improve database index performance.
Converting Between Timestamp Formats
use uuid::timestamp::{Timestamp, Context};
use uuid::Builder;
use std::time::SystemTime;
fn timestamp_conversion() {
// From SystemTime to RFC 4122 timestamp
let now = SystemTime::now();
let duration = now.duration_since(SystemTime::UNIX_EPOCH).unwrap();
// Unix timestamp (seconds since 1970) to 100ns intervals since 1582
// Formula: unix_nanos / 100 + GREGORIAN_UNIX_OFFSET
const GREGORIAN_UNIX_OFFSET_NS: u64 = 122_192_928_000_000_000; // In 100ns units
// Create timestamp manually
let unix_ns = duration.as_nanos() as u64;
let counter = (unix_ns / 100) + GREGORIAN_UNIX_OFFSET_NS;
let context = Context::new(rand::random());
let ts = Timestamp::from_rfc4122(context, counter);
let uuid = Builder::from_rfc4122_timestamp(&ts).into_uuid();
// The uuid crate handles this conversion in Timestamp::now
}Convert between Unix timestamp and RFC 4122 format.
Practical Example: Event Sourcing
use uuid::{Uuid, Builder};
use uuid::timestamp::{Timestamp, Context};
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct Event {
id: Uuid,
timestamp: u64, // Unix timestamp in milliseconds
data: String,
}
fn event_sourcing() {
let context = Context::new(rand::random());
// Create event with time-based UUID
let event = Event {
id: Uuid::now_v1(),
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as u64,
data: "User logged in".to_string(),
};
// UUID provides:
// 1. Unique identifier
// 2. Time ordering (for event replay)
// 3. Millisecond precision (sufficient for most events)
// Events can be sorted by UUID:
let mut events: Vec<Event> = get_events();
events.sort_by_key(|e| e.id); // Chronological order
}Time-based UUIDs enable ordered event sourcing.
Builder Pattern
use uuid::Builder;
use uuid::timestamp::Timestamp;
fn builder_pattern() {
// from_rfc4122_timestamp returns a Builder
// which can be further customized
let context = uuid::timestamp::Context::new(0x1234);
let ts = Timestamp::now(context);
let uuid = Builder::from_rfc4122_timestamp(&ts)
// Additional customization possible
.into_uuid(); // Finalize into UUID
// The Builder allows:
// - Setting version/variant bits
// - Customizing fields
// - Converting to UUID
// For v1, most fields are set from timestamp
}The builder pattern enables UUID customization.
Summary Table
use uuid::Uuid;
fn summary() {
// | Component | Size | Purpose |
// |------------------|---------|----------------------------------|
// | timestamp | 60 bits | Time in 100ns intervals |
// | clock_seq | 14 bits | Prevents duplicates on regression|
// | node_id | 48 bits | Machine identifier |
// | version/variant | 6 bits | UUID version markers |
// Precision: 100 nanoseconds = 0.0001 milliseconds
// Range: ~1582 to ~5236 (centuries)
// Key functions:
// - Uuid::now_v1(): Generate with current time
// - Builder::from_rfc4122_timestamp(): Build from components
// - uuid.get_timestamp(): Extract timestamp
}UUID v1 combines timestamp, clock sequence, and node ID.
Synthesis
Quick reference:
use uuid::{Uuid, Builder};
use uuid::timestamp::{Timestamp, Context};
// Generate UUID v1 with current time
let uuid = Uuid::now_v1();
// Build UUID from custom timestamp
let context = Context::new(0x1234);
let ts = Timestamp::now(context);
let uuid = Builder::from_rfc4122_timestamp(&ts).into_uuid();
// Extract timestamp from UUID
if let Some(ts) = uuid.get_timestamp() {
println!("Timestamp counter: {}", ts.counter());
println!("Clock sequence: {:?}", ts.clock_sequence());
}When to use UUID v1:
use uuid::Uuid;
// Use UUID v1 when you need:
// - Time-ordered identifiers
// - Millisecond precision
// - Database-friendly sequential inserts
// - Event ordering across systems
// Avoid UUID v1 when:
// - Privacy is important (reveals timestamp/machine)
// - You don't want sequential IDs
// - Security-sensitive applicationsKey insight: Builder::from_rfc4122_timestamp provides fine-grained control over UUID v1 generation by allowing manual specification of the timestamp, clock sequence, and node ID components. The RFC 4122 timestamp uses 100-nanosecond intervals since October 15, 1582 (the Gregorian epoch), which provides precision even finer than millisecondsā10,000 intervals per millisecond. This precision level enables generating multiple unique UUIDs within a single millisecond while maintaining time ordering. The clock sequence prevents collisions when clocks regress or systems restart, and the node ID ensures uniqueness across machines. While UUID v1's precision and ordering are valuable for database indexing and event ordering, the timestamp and node ID exposure is a privacy concernāUUID v4 (random) or v7 (Unix timestamp-based) may be preferable when privacy matters. The from_rfc4122_timestamp function enables reproducible UUID generation for testing by accepting fixed input values, while Uuid::now_v1() provides convenient generation with current timestamp and random clock sequence.
