Создание уникальных идентификаторов на базе даты и времени PHP

Раздел: PHP программирование -> Работа с датами и идентификаторами в PHP

Методы генерации идентификаторов на основе времени

Как создать компактный и достаточно уникальный ID на основе текущего времени и случайных данных?

Одним из наиболее эффективных решений является комбинация миллисекундной временной метки и случайного числа, преобразованная в шестнадцатеричный формат. Такой ID занимает 13 символов и практически исключает коллизии при разумных нагрузках.

<?php
function generateTimeBasedId(): string {
    $milliseconds = floor(microtime(true) * 1000);
    $random = random_int(0, 0xFFFFF);
    return sprintf('%08x%05x', $milliseconds, $random);
}
echo generateTimeBasedId();
?>

Date php id (php дата и id)

Например: 1f2a3b4c5d6e7

Пояснение шагов:

  • microtime(true) возвращает текущее время с микросекундами в виде числа с плавающей точкой.
  • Умножение на 1000 даёт миллисекунды. floor отбрасывает дробную часть.
  • random_int(0, 0xFFFFF) генерирует случайное число в диапазоне от 0 до 1 048 575 (20 бит).
  • sprintf('%08x%05x', ...) форматирует оба числа в hex: первое занимает 8 символов (32 бита миллисекунд), второе - 5 символов.

Возможные проблемы:

  • При очень высокой частоте запросов (более 1 000 000 в миллисекунду) может возникнуть коллизия. Решение - увеличить количество случайных бит (например, до 8 hex цифр).
  • Функция random_int требует PHP 7.0 или новее. В более старых версиях можно использовать mt_rand, но это менее безопасно.
  • Миллисекунды не гарантируют монотонность при переходе на летнее время или корректировке системного времени. Для строго возрастающих ID требуется хранить lastTime.

Основное преимущество - малый размер (13 символов) и отсутствие необходимости в хранилище состояния. Подходит для формирования ключей в URL, short IDs, идентификаторов сессий.

Вариант 1: Простейшая комбинация time() и rand()

Как быстро получить ID, содержащий дату и случайное число?

<?php
echo time() . '-' . rand(100, 999);
?>

Результат: 1705738492-457

Проблемы:

  • Коллизии при одновременных запросах в одну секунду, так как rand() генерирует лишь 900 возможных значений.
  • Небезопасный генератор rand(). Рекомендуется random_int().

Случаи использования: отладка, временные метки для логов, где уникальность не критична.

Вариант 2: date() с uniqid()

Как добавить к дате стандартный уникальный идентификатор uniqid?

<?php
echo date('YmdHis') . '-' . uniqid();
?>
20240218091532-65d1e2f4b3c6d

Функция uniqid() возвращает 13 символов на основе микросекунд, но может давать дубли при быстрых последовательных вызовах. Суффикс uniqid('', true) добавляет энтропию.

Проблемы:

  • uniqid не гарантирует уникальность в многопоточной среде или при вызовах с одинаковым микросекундным временем.
  • Предсказуемость: зная последовательность, можно угадать следующий ID.

Применение: подходит для генерации ключей в небольших проектах, где нагрузка невысока.

Вариант 3: UUID v1 на основе времени и MAC-адреса

Как создать стандартизированный 128-битный идентификатор, содержащий временную метку?

UUID версии 1 включает 60-бит timestamp, clock sequence и node (MAC). Реализация на чистом PHP требует получения MAC-адреса и учета часовых смещений.

<?php
function uuidV1(): string {
    $time = microtime(true);
    $ts = floor($time * 10000000) + 0x01b21dd213814000;
    $timeHex = str_pad(dechex($ts), 16, '0', STR_PAD_LEFT);
    $timeLow = substr($timeHex, 8, 8);
    $timeMid = substr($timeHex, 4, 4);
    $timeHi = substr($timeHex, 0, 4);
    $version = '1';
    $clockSeq = dechex(mt_rand(0, 0x3fff));
    $node = bin2hex(random_bytes(6));
    return sprintf('%s-%s-%s%s-%s-%s',
        $timeLow,
        $timeMid,
        $version . substr($timeHi, 1, 3),
        $clockSeq,
        $node
    );
}
echo uuidV1();
?>
1f2a3b4c-5d6e-7a8b-9c0d-ef0123456789

Проблемы:

  • Сложность реализации, трудно обеспечить монотонность и уникальность без сохранения состояния.
  • MAC-адрес может отсутствовать или быть недоступным, замена случайными байтами снижает уникальность.
  • Производительность: вычисления с большими числами могут быть медленными.

Используется в распределённых системах, где требуется уникальность без централизованного координатора (например, в базах данных). Лучше применять специализированные библиотеки (ramsey/uuid).

Вариант 4: ID с префиксом даты и последовательным номером

Как создать монотонно возрастающий ID с привязкой к дате, используя внешний счётчик?

Счётчик можно хранить в файле или базе данных. Каждый новый ID получает номер на основе текущей даты и инкремента.

<?php
function getNextId(): string {
    $date = date('Ymd');
    $counterFile = __DIR__ . "/counter_{$date}.txt";
    if (!file_exists($counterFile)) {
        file_put_contents($counterFile, '0');
    }
    $fp = fopen($counterFile, 'r+');
    if (flock($fp, LOCK_EX)) {
        $counter = (int) fread($fp, filesize($counterFile));
        $counter++;
        rewind($fp);
        fwrite($fp, (string)$counter);
        flock($fp, LOCK_UN);
    }
    fclose($fp);
    return $date . sprintf('%06d', $counter);
}
echo getNextId();
?>
20240218000001

Проблемы:

  • Блокировка файла снижает производительность при высоких нагрузках.
  • Счётчик сбрасывается каждый день, возможны дубли при одновременных запусках с разными датами.
  • Хранение в файле не подходит для распределённых систем.

Применение: генерация номеров заказов, билетов, где важна последовательность и читаемость.

Расширенные примеры генерации идентификаторов

Пример 1: ID в системе счисления base36

Использование base36 позволяет получить короткий ID, удобный для передачи в URL. Миллисекунды объединяются со случайным числом в одно целое и переводятся в base36 функцией gmp_strval.

Пример
<?php
function base36Id(): string {
    $milliseconds = floor(microtime(true) * 1000);
    $random = random_int(0, 0xFFFFF);
    $num = ($milliseconds << 20) | $random;
    return gmp_strval(gmp_init($num, 10), 36);
}
echo base36Id();
?>
1h7k9m0n

Для работы требуется расширение GMP.

Пример 2: Snowflake-подобный ID с worker ID

Алгоритм Snowflake (Twitter) генерит 64-битные ID: timestamp, worker ID, sequence. В примере используется 40 бит на миллисекунды внутри дня, 8 бит worker, 12 бит sequence. Результат в hex.

Пример
<?php
class SnowflakeId {
    private $workerId;
    private $lastTimestamp = 0;
    private $sequence = 0;
    public function __construct(int $workerId = 0) {
        $this->workerId = $workerId & 0xFF;
    }
    public function nextId(): string {
        $timestamp = (int)(microtime(true) * 1000);
        if ($timestamp === $this->lastTimestamp) {
            $this->sequence = ($this->sequence + 1) & 0xFFF;
        } else {
            $this->sequence = 0;
            $this->lastTimestamp = $timestamp;
        }
        $id = (($timestamp % 86400000) << 20) | ($this->workerId << 12) | $this->sequence;
        return dechex($id);
    }
}
$snow = new SnowflakeId(1);
echo $snow->nextId();
?>
3a1b2c3d4e5f

Подходит для распределённых систем, где каждый worker имеет уникальный идентификатор.

Пример 3: UUID v1 с помощью библиотеки ramsey/uuid

Установка через Composer: composer require ramsey/uuid. Библиотека предоставляет полноценную реализацию UUID v1 с правильным формированием временной метки, clock sequence и MAC-адреса.

Пример
<?php
require 'vendor/autoload.php';
use Ramsey\Uuid\Uuid;
$uuid1 = Uuid::uuid1();
echo $uuid1->toString();
?>
b50e3e2a-7c5a-11ee-b39a-3c4a92730000

Рекомендуется для проектов, требующих строгого соответствия стандарту UUID.

Пример 4: Монотонный ID с сохранением последнего значения

Для обеспечения строгого возрастания ID на основе микросекунд используется файл, в котором хранится последнее сгенерированное значение. Если текущее время меньше или равно последнему, ID увеличивается на 1.

Пример
<?php
function monotonicTimeId(): string {
    $micro = microtime(true);
    $key = (int)($micro * 1000);
    $stateFile = __DIR__ . '/last_micro.txt';
    $fp = fopen($stateFile, 'c+');
    flock($fp, LOCK_EX);
    $last = (int) fgets($fp);
    if ($key <= $last) {
        $key = $last + 1;
    }
    rewind($fp);
    fwrite($fp, (string)$key);
    flock($fp, LOCK_UN);
    fclose($fp);
    return dechex($key);
}
echo monotonicTimeId();
?>
1f2a3b4c5d

Такой подход гарантирует уникальность даже при очень высокой частоте генерации, но требует блокировок файла.

PHP дата и ID - comments

En
Date php id (php)