Timestamp в миллисекундах и секундах

Валидация временных значений в схемах Joi строится вокруг работы с типом Date, при этом часто возникает необходимость принимать и обрабатывать числовые представления времени. На практике используются два основных формата: Unix timestamp в секундах и JavaScript timestamp в миллисекундах. Их различие критично для корректной валидации входных данных и предотвращения логических ошибок.

Unix timestamp и JavaScript timestamp

Unix timestamp (секунды) представляет собой количество секунд, прошедших с 1 января 1970 года (UTC). Этот формат широко используется в API, базах данных и внешних сервисах.

JavaScript timestamp (миллисекунды) — это количество миллисекунд с той же эпохи. Именно в таком виде работает встроенный объект Date.now() и большинство методов Date в JavaScript.

Ключевое различие:

  • Unix: 1715330000
  • Jav * aScript: 1715330000000

Разница в масштабировании составляет фактор 1000, и это является частой причиной ошибок при интеграции систем.

Joi.date().timestamp()

В Joi поддержка временных меток реализована через метод timestamp(), который позволяет валидировать числовые значения как дату.

Базовая сигнатура:

Joi.date().timestamp([type])

Параметр type определяет формат входного timestamp.

Режим javascript (миллисекунды)

Режим 'javascript' ожидает значение в миллисекундах.

import Joi from 'joi';

const schema = Joi.object({
  createdAt: Joi.date().timestamp('javascript')
});

schema.validate({
  createdAt: 1715330000000
});

Поведение:

  • входное число интерпретируется как миллисекунды
  • результат преобразуется в объект Date
  • строковые значения автоматически приводятся к числу при возможности

Режим unix (секунды)

Режим 'unix' работает с секундной точностью.

const schema = Joi.object({
  createdAt: Joi.date().timestamp('unix')
});

schema.validate({
  createdAt: 1715330000
});

Особенности:

  • значение умножается на 1000 внутри Joi
  • поддерживается только целочисленный формат секунд
  • дробные значения интерпретируются как ошибка или округляются в зависимости от контекста преобразования

Автоматическое преобразование типов

Joi допускает неявное приведение типов, если включена соответствующая конфигурация:

const schema = Joi.object({
  createdAt: Joi.date().timestamp('unix')
});

schema.validate({
  createdAt: "1715330000"
});

В этом случае строка будет преобразована в число перед валидацией. Однако поведение зависит от настроек convert:

Joi.object({...}).validate(data, { convert: true });

При convert: false любые строковые значения будут считаться невалидными.

Внутреннее представление результата

После успешной валидации Joi всегда возвращает объект Date, независимо от входного формата:

const { value } = schema.validate({
  createdAt: 1715330000
});

console.log(value.createdAt instanceof Date); // true

Это важно учитывать при дальнейшем использовании данных, так как исходное числовое значение теряется.

Потеря точности и особенности миллисекунд

При работе с javascript timestamp проблем с точностью обычно не возникает, так как JavaScript использует миллисекунды как базовую единицу времени.

В режиме unix возможны нюансы:

  • преобразование seconds → milliseconds всегда линейное
  • дробные секунды могут приводить к неожиданному округлению
  • высокоточные временные данные (например, микросекунды) теряются

Валидация диапазонов времени

Joi позволяет комбинировать timestamp() с другими методами проверки:

const schema = Joi.object({
  createdAt: Joi.date()
    .timestamp('unix')
    .min('now')
    .max('now')
});

Такое использование позволяет ограничивать временные границы относительно текущего момента.

Примеры логики:

  • min('now') — только будущие даты
  • max('now') — только прошедшие даты

Работа с API и внешними источниками данных

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

const schema = Joi.alternatives().try(
  Joi.date().timestamp('unix'),
  Joi.date().timestamp('javascript')
);

Это позволяет принимать оба формата без предварительного преобразования.

Ошибки при использовании timestamp

Наиболее распространённые ошибки:

Перепутанные единицы измерения

// ошибка: передан seconds вместо milliseconds
createdAt: 1715330000

В режиме 'javascript' это приведёт к дате в 1970 году.

Строки без конвертации

createdAt: "not-a-timestamp"

При отключённом convert значение будет отклонено.

Потеря информации о timezone

Timestamp всегда интерпретируется как UTC-время, поэтому любые локальные часовые пояса игнорируются на этапе валидации.

Комбинация с форматированными датами

Joi позволяет сочетать timestamp с ISO-строками:

const schema = Joi.object({
  createdAt: Joi.alternatives().try(
    Joi.date().timestamp('unix'),
    Joi.date().iso()
  )
});

Такой подход часто используется в API, где формат входных данных не стандартизирован.

Особенности сериализации

После валидации объект Date может быть сериализован в JSON:

JSON.stringify({ date: new Date() });

Результат:

  • автоматически преобразуется в ISO-строку
  • timestamp-значение не сохраняется напрямую

Для сохранения числового формата требуется явное преобразование:

date.getTime() // milliseconds
Math.floor(date.getTime() / 1000) // seconds

Практические аспекты выбора формата

Использование 'unix' предпочтительно в следующих случаях:

  • работа с SQL/NoSQL базами, где хранятся секунды
  • интеграция с системами на PHP, Python, Go

Использование 'javascript' оправдано:

  • внутри Node.js приложений
  • при работе с фронтендом
  • при использовании Date.now()

Несовпадение форматов без явного контроля приводит к ошибкам смещения времени на порядок величины (×1000), что является одной из наиболее критичных проблем при работе с временными метками в распределённых системах.