A Swift implementation of ULID (Universally Unique Lexicographically Sortable Identifier) with UUID v7 support according to RFC 9562.
- ✅ ULID Generation: Create ULIDs with 128-bit compatibility
- ✅ Lexicographically Sortable: ULIDs sort naturally by creation time
- ✅ Crockford Base32 Encoding: Efficient 26-character string representation
- ✅ Foundation-style API: Familiar initializers matching Swift conventions
- ✅ UUID v7 Support: Generate and convert to/from UUID version 7
- ✅ Thread Safe: All operations are thread-safe with
Sendable
conformance - ✅ Swift Concurrency: Full async/await support
- ✅ Comprehensive Testing: Extensive test suite with 50+ tests
- ✅ Zero Dependencies: No external dependencies
ULID (Universally Unique Lexicographically Sortable Identifier) is a 128-bit identifier that combines:
- 48-bit timestamp: Millisecond precision Unix timestamp
- 80-bit randomness: Cryptographically secure random data
This results in identifiers that are:
- Sortable: Natural chronological ordering
- Compact: 26 characters (vs 36 for UUID)
- URL Safe: Uses Crockford's Base32 (no special characters)
- Case Insensitive: Handles common transcription errors
- Monotonic: Sequential within the same millisecond
01AN4Z07BY 79KA1307SR9X4MV3
|----------| |----------------|
Timestamp Randomness
48bits 80bits
Add this package to your Package.swift
:
dependencies: [
.package(url: "https://github.com/thoven87/ulid-swift.git", from: "1.0.0")
]
Or add it through Xcode: File → Add Package Dependencies and enter the repository URL.
import ULID
// Generate a new ULID with current timestamp
let ulid = ULID()
print(ulid.ulidString) // "01ARZ3NDEKTSV4RRFFQ69G5FAV"
// Generate with specific timestamp
let date = Date(timeIntervalSince1970: 1645557742)
let timedULID = ULID(timestamp: date)
print(timedULID.timestamp) // Original date
// Parse from string (case-insensitive)
if let ulid = ULID(ulidString: "01ARZ3NDEKTSV4RRFFQ69G5FAV") {
print("Timestamp: \(ulid.timestamp)")
print("Randomness: \(ulid.randomnessData.count) bytes")
}
// String literal support
let literalULID: ULID = "01ARZ3NDEKTSV4RRFFQ69G5FAV"
// Character substitutions are handled automatically
let withSubstitutions = ULID(ulidString: "01ARZ3NDEKTSV4RRFFQ69G5FOV") // O→0
Generate and work with UUID version 7 (RFC 9562):
// Generate UUID v7
let uuid7 = UUID(version7: ())
print(uuid7.uuidString) // "017F22E2-79B0-7CC3-98C4-DC0C0C07398F"
print(uuid7.isVersion7) // true
// Generate with specific timestamp
let timestamp: UInt64 = 1645557742000
let timedUUID7 = UUID(version7Timestamp: timestamp)
// Convert between ULID and UUID v7
let ulid = ULID()
let convertedUUID = ulid.uuidv7
if let backToULID = ULID(uuidv7: convertedUUID) {
print("Round-trip successful")
}
// Extract timestamp from UUID v7
if let extractedTimestamp = uuid7.version7Timestamp {
let date = Date(timeIntervalSince1970: TimeInterval(extractedTimestamp) / 1000)
print("Created at: \(date)")
}
// Create from binary data
let data = Data([0x01, 0x79, 0x22, 0xE2, 0x79, 0xB0, 0x7C, 0xC3,
0x98, 0xC4, 0xDC, 0x0C, 0x0C, 0x07, 0x39, 0x8F])
if let ulid = ULID(ulidData: data) {
print("ULID from binary: \(ulid)")
}
// Create with specific components
let timestamp: UInt64 = 1645557742000
let randomness = Data([0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x12, 0x34])
let customULID = try ULID(timestamp: timestamp, randomness: randomness)
// UUID conversion
let uuid = ulid.uuid
let fromUUID = ULID(uuid: uuid)
// Use custom RNG for testing or specialized use cases
import GameplayKit
var rng = GKLinearCongruentialRandomSource(seed: 12345)
let predictableULID = ULID(timestamp: Date(), generator: &rng)
// Primary initializers
ULID() // Current timestamp
ULID(timestamp: Date) // Specific timestamp
ULID(timestamp: Date, generator: inout T) // Custom RNG
// Parsing
ULID(ulidString: String) // From 26-char string
ULID(ulidData: Data) // From 16 bytes
// Advanced
ULID(timestamp: UInt64, randomness: Data) // Manual components
ULID(timestamp: UInt64, randomness: (UInt16, UInt64)) // Tuple format
ULID(uuid: UUID) // From UUID
let ulid = ULID()
// String representation
ulid.ulidString // "01ARZ3NDEKTSV4RRFFQ69G5FAV"
ulid.description // Same as ulidString
ulid.debugDescription // "ULID(01ARZ3NDEKTSV4RRFFQ69G5FAV)"
// Binary data
ulid.ulidData // Data (16 bytes)
// Timestamp extraction
ulid.timestamp // Date
ulid.date // Date (alias)
ulid.timestampMs // UInt64 (milliseconds since Unix epoch)
// Randomness extraction
ulid.randomnessData // Data (10 bytes)
ulid.randomness // (UInt16, UInt64) tuple
// UUID conversion
ulid.uuid // UUID
ulid.uuidv7 // UUID (version 7 format)
// Creation
UUID(version7: ()) // Current timestamp
UUID(version7Timestamp: UInt64) // Specific timestamp
// Validation and extraction
uuid.isVersion7 // Bool
uuid.version7Timestamp // UInt64?
uuid.version7Date // Date?
// ULID conversion
ULID(uuidv7: UUID) // UUID v7 → ULID
ULID conforms to essential Swift protocols:
// Equatable & Hashable
let set: Set<ULID> = [ulid1, ulid2, ulid3]
let dict: [ULID: String] = [ulid1: "first", ulid2: "second"]
// Comparable (lexicographic ordering)
let sorted = ulids.sorted() // Chronological order
print(ulid1 < ulid2) // true if ulid1 created before ulid2
// Codable (JSON serialization)
let encoder = JSONEncoder()
let data = try encoder.encode(ulid)
let decoded = try JSONDecoder().decode(ULID.self, from: data)
// ExpressibleByStringLiteral
let literalULID: ULID = "01ARZ3NDEKTSV4RRFFQ69G5FAV"
// CustomStringConvertible
print(ulid) // Prints the ULID string
// Sendable (thread-safe)
Task {
let ulid = ULID() // Safe to use in concurrent contexts
}
ULIDs maintain lexicographical sort order:
let ulids = [
ULID(timestamp: Date(timeIntervalSince1970: 1000)),
ULID(timestamp: Date(timeIntervalSince1970: 2000)),
ULID(timestamp: Date(timeIntervalSince1970: 3000))
]
// All these produce the same chronological order
let sortedByValue = ulids.sorted()
let sortedByString = ulids.sorted { $0.ulidString < $1.ulidString }
let sortedByTimestamp = ulids.sorted { $0.timestamp < $1.timestamp }
do {
let ulid = try ULID(timestamp: timestamp, randomness: randomness)
} catch ULIDError.timestampOverflow {
print("Timestamp too large (max 48 bits)")
} catch ULIDError.invalidLength {
print("Randomness data must be at least 10 bytes")
} catch ULIDError.invalidEncoding {
print("Invalid ULID string format")
}
Optimized for high-performance applications:
- Generation: ~100,000 ULIDs per second per core
- Parsing: ~50,000 parses per second per core
- Memory: Minimal allocations with efficient bit manipulation
- Thread Safety: Lock-free operations where possible
struct User {
let id: ULID = ULID()
let name: String
let email: String
var createdAt: Date {
return id.timestamp // Timestamp embedded in ID
}
}
// Natural chronological ordering without separate timestamp column
let users = User.fetchAll().sorted { $0.id < $1.id }
struct Event {
let id: ULID = ULID()
let type: String
let data: Data
// Events are naturally ordered by creation time
// No coordination needed between distributed nodes
}
// Events sort correctly across multiple services
let allEvents = [serviceA.events, serviceB.events, serviceC.events]
.flatMap { $0 }
.sorted { $0.id < $1.id }
struct APIRequest {
let id: ULID = ULID()
let endpoint: String
let method: HTTPMethod
// Request ID embeds timestamp for automatic chronological ordering
var requestTime: Date { return id.timestamp }
}
// Log analysis becomes trivial - no separate timestamp needed
let requests = logs.map { APIRequest(from: $0) }.sorted { $0.id < $1.id }
- ✅ 128-bit compatibility with UUID
- ✅ 1.21e+24 unique ULIDs per millisecond
- ✅ Lexicographically sortable
- ✅ Canonically encoded as 26-character string
- ✅ Uses Crockford's Base32 for encoding
- ✅ Case insensitive with character substitution
- ✅ No special characters (URL safe)
- ✅ Monotonic sort order within same millisecond
- ✅ 48-bit Unix timestamp in milliseconds
- ✅ Version 7 identifier (4 bits set to 0111)
- ✅ Variant bits (2 bits set to 10)
- ✅ 74 bits of randomness
- ✅ Lexicographically sortable
- ✅ Compatible with existing UUID infrastructure
- Swift: 6.1+
- Platforms: macOS 15+, iOS 18+, tvOS 18+, watchOS 11+, Linux
- Concurrency: Full Swift Concurrency support with
Sendable
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature
) - Add tests for new functionality
- Ensure all tests pass (
swift test
) - Submit a pull request
This project is licensed under the MIT License. See LICENSE file for details.
ULID Swift - Lexicographically sortable identifiers for modern Swift applications.