generateAt(time()); } /** * Generate a KSUID at a specific Unix timestamp */ public function generateAt(int $timestamp): Ksuid { if ($timestamp < Ksuid::EPOCH) { throw new InvalidArgumentException('Timestamp cannot be before KSUID epoch (2014-05-13)'); } // Generate 16 random bytes for payload $payload = $this->randomGenerator->bytes(Ksuid::PAYLOAD_BYTES); return Ksuid::fromTimestampAndPayload($timestamp, $payload); } /** * Generate a KSUID at a specific DateTime */ public function generateAtDateTime(DateTimeImmutable $dateTime): Ksuid { return $this->generateAt($dateTime->getTimestamp()); } /** * Generate a KSUID for the past (useful for testing) */ public function generateInPast(int $secondsAgo): Ksuid { $timestamp = time() - $secondsAgo; return $this->generateAt($timestamp); } /** * Generate a batch of KSUIDs with the same timestamp */ public function generateBatch(int $count, ?int $timestamp = null): array { if ($count <= 0) { throw new InvalidArgumentException('Count must be positive'); } if ($count > 10000) { throw new InvalidArgumentException('Batch size cannot exceed 10000'); } $timestamp ??= time(); $ksuids = []; $used = []; while (count($ksuids) < $count) { $payload = $this->randomGenerator->bytes(Ksuid::PAYLOAD_BYTES); $payloadHex = bin2hex($payload); // Ensure uniqueness within batch if (! isset($used[$payloadHex])) { $ksuids[] = Ksuid::fromTimestampAndPayload($timestamp, $payload); $used[$payloadHex] = true; } } return $ksuids; } /** * Generate a sequence of KSUIDs with incrementing timestamps */ public function generateSequence(int $count, int $intervalSeconds = 1): array { if ($count <= 0) { throw new InvalidArgumentException('Count must be positive'); } if ($count > 1000) { throw new InvalidArgumentException('Sequence size cannot exceed 1000'); } if ($intervalSeconds < 0) { throw new InvalidArgumentException('Interval must be non-negative'); } $ksuids = []; $timestamp = time(); for ($i = 0; $i < $count; $i++) { $ksuids[] = $this->generateAt($timestamp + ($i * $intervalSeconds)); } return $ksuids; } /** * Generate a KSUID with specific prefix in payload (for testing/debugging) */ public function generateWithPrefix(string $prefix): Ksuid { $maxPrefixLength = Ksuid::PAYLOAD_BYTES - 1; // Reserve at least 1 byte for randomness if (strlen($prefix) > $maxPrefixLength) { throw new InvalidArgumentException("Prefix cannot exceed {$maxPrefixLength} bytes"); } $remainingBytes = Ksuid::PAYLOAD_BYTES - strlen($prefix); $randomSuffix = $this->randomGenerator->bytes($remainingBytes); $payload = $prefix . $randomSuffix; return Ksuid::fromTimestampAndPayload(time(), $payload); } /** * Parse a KSUID string and validate it */ public function parse(string $ksuidString): Ksuid { return Ksuid::fromString($ksuidString); } /** * Validate if a string is a valid KSUID */ public function isValid(string $value): bool { if (strlen($value) !== Ksuid::ENCODED_LENGTH) { return false; } try { Ksuid::fromString($value); return true; } catch (InvalidArgumentException) { return false; } } /** * Get the minimum possible KSUID for a timestamp */ public function getMinForTimestamp(int $timestamp): Ksuid { $payload = str_repeat("\0", Ksuid::PAYLOAD_BYTES); return Ksuid::fromTimestampAndPayload($timestamp, $payload); } /** * Get the maximum possible KSUID for a timestamp */ public function getMaxForTimestamp(int $timestamp): Ksuid { $payload = str_repeat("\xFF", Ksuid::PAYLOAD_BYTES); return Ksuid::fromTimestampAndPayload($timestamp, $payload); } /**@return array{min: \App\Framework\Ksuid\Ksuid, max: \App\Framework\Ksuid\Ksuid} * Generate KSUIDs for a time range (useful for queries) */ public function generateTimeRange(int $startTimestamp, int $endTimestamp): array { if ($startTimestamp >= $endTimestamp) { throw new InvalidArgumentException('Start timestamp must be before end timestamp'); } return [ 'min' => $this->getMinForTimestamp($startTimestamp), 'max' => $this->getMaxForTimestamp($endTimestamp), ]; } /** * Create factory method */ public static function create(RandomGenerator $randomGenerator): self { return new self($randomGenerator); } }