Даты: date()

Работа с датами в Joi строится вокруг метода Joi.date(), который определяет поле как значение типа Date и предоставляет набор инструментов для строгой и гибкой валидации временных данных. Валидация дат в прикладных задачах часто связана с проверкой диапазонов, форматов, временных ограничений и преобразований входных значений, поступающих из HTTP-запросов или других внешних источников.

Базовое определение даты выглядит следующим образом:

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

Любое значение, которое может быть интерпретировано как дата JavaScript, будет преобразовано и проверено на корректность. Входные строки типа ISO, timestamp или Date-объекты проходят нормализацию.


Преобразование значений в дату

Joi.date() по умолчанию выполняет преобразование входных значений. Это означает, что строки и числа могут автоматически интерпретироваться как даты.

Поддерживаются следующие типы входных данных:

  • ISO 8601 строки
  • Unix timestamp (в миллисекундах)
  • Date объекты JavaScript

Пример:

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

schema.validate({
  createdAt: '2024-01-01T10:00:00.000Z'
});

Также допустимы числовые значения:

schema.validate({
  createdAt: 1704103200000
});

Если требуется запрет автоматического преобразования, используется строгий режим:

Joi.date().strict()

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


Проверка минимальной и максимальной даты

Ограничение диапазона времени реализуется через методы min() и max().

Минимальная дата

const schema = Joi.object({
  createdAt: Joi.date().min('2020-01-01')
});

Значение должно быть не ранее указанной даты.

Максимальная дата

const schema = Joi.object({
  createdAt: Joi.date().max('2025-12-31')
});

Допускается также использование Date объектов:

const minDate = new Date('2020-01-01');

Joi.date().min(minDate);

Относительные ограничения дат

Joi поддерживает относительные ограничения на основе текущего времени:

Прошедшее время

Joi.date().less('now')

Будущее время

Joi.date().greater('now')

Эти проверки часто применяются для событий:

  • дата рождения (должна быть в прошлом)
  • дата бронирования (может быть только в будущем)
  • срок действия (ограниченный диапазон)

ISO и строгие форматы

Для контроля формата используется iso():

Joi.date().iso()

Это ограничивает вход только ISO 8601 строками.

Пример допустимого значения:

2024-05-10T12:30:00.000Z

Любые нестандартные строки будут отклонены.


Работа с timestamp

Иногда требуется принимать только числовой Unix timestamp:

Joi.date().timestamp()

Можно указать единицы измерения:

Joi.date().timestamp('unix') // секунды
Joi.date().timestamp('javascript') // миллисекунды

Это важно при интеграции с API, где формат времени фиксирован.


Значение по умолчанию

Для автоматического заполнения даты применяется default():

Joi.date().default(Date.now)

Важно, что функция передаётся без вызова, чтобы значение вычислялось при каждой валидации.

Также возможно использование фиксированной даты:

Joi.date().default('2020-01-01')

Обязательность и допустимость null

Как и для других типов, применяются:

Joi.date().required()
Joi.date().optional()
Joi.date().allow(null)

Пример комбинированной схемы:

Joi.date().optional().allow(null)

Это позволяет явно контролировать отсутствие даты.


Запрет недопустимых значений

Метод invalid() используется для исключения конкретных дат:

Joi.date().invalid('2024-01-01')

Можно комбинировать несколько значений:

Joi.date().invalid('2024-01-01', '2024-12-31')

Форматирование ошибок

Для дат часто требуется кастомизация сообщений:

Joi.date().messages({
  'date.base': 'Значение должно быть датой',
  'date.min': 'Дата слишком ранняя',
  'date.max': 'Дата превышает допустимый предел'
});

Каждый тип ошибки связан с конкретным правилом валидации.


Комбинирование ограничений

Joi позволяет строить сложные правила:

const schema = Joi.object({
  startDate: Joi.date().required().min('2020-01-01'),
  endDate: Joi.date().greater(Joi.ref('startDate'))
});

Здесь реализуется зависимость между полями объекта: конечная дата должна быть позже начальной.


Использование ссылок между полями

Joi.ref() позволяет сравнивать даты внутри объекта:

Joi.date().greater(Joi.ref('createdAt'))

Это особенно важно для:

  • диапазонов событий
  • периода подписки
  • логики бронирования

Работа с временем в API

При интеграции с HTTP API даты часто приходят в строковом виде. Типичная схема запроса:

const schema = Joi.object({
  userId: Joi.number().required(),
  createdAt: Joi.date().iso().required(),
  updatedAt: Joi.date().optional()
});

Такой подход обеспечивает единообразие входных данных и предотвращает ошибки парсинга.


Преобразование и нормализация

Joi приводит даты к стандартному виду Date объекта. Это упрощает дальнейшую обработку:

const result = schema.validate({
  createdAt: '2024-01-01T00:00:00Z'
});

result.value.createdAt instanceof Date; // true

Таким образом, после валидации данные уже готовы к использованию в логике приложения без дополнительного парсинга.


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

Частая задача — проверка интервала:

const schema = Joi.object({
  start: Joi.date().required(),
  end: Joi.date().required().greater(Joi.ref('start'))
});

При необходимости допускается равенство:

Joi.date().min(Joi.ref('start'))

Ограничения на будущее время

Для событий с ограничением на ближайший период:

Joi.date().max('now').min('2023-01-01')

Это ограничивает дату диапазоном между прошлым и текущим моментом.


Таймзоны и особенности хранения

Joi не выполняет сложную обработку таймзон, так как использует стандартный объект Date. Входные ISO строки с таймзоной корректно приводятся к UTC:

Joi.date().iso()

Пример:

2024-05-10T12:00:00+03:00

будет нормализован в UTC-эквивалент.


Применение в сложных схемах

В больших объектах даты часто комбинируются с другими типами:

const schema = Joi.object({
  id: Joi.string().uuid().required(),
  createdAt: Joi.date().default(Date.now),
  updatedAt: Joi.date().optional(),
  deletedAt: Joi.date().allow(null)
});

Такая структура типична для сущностей с жизненным циклом.


Валидация массивов дат

Joi позволяет проверять коллекции:

Joi.array().items(Joi.date().iso())

Это используется для:

  • списка событий
  • календарных данных
  • логов активности

Поведение при ошибках преобразования

Если входное значение невозможно интерпретировать как дату, возвращается ошибка типа date.base. Это означает, что значение не соответствует допустимому формату даты и не может быть преобразовано даже в рамках гибкого режима.


Итоговая структура правил для дат

Использование Joi.date() обычно включает комбинацию:

  • преобразование входных значений
  • ограничения диапазона (min, max)
  • проверка относительных дат (greater, less)
  • контроль формата (iso, timestamp)
  • зависимость от других полей (Joi.ref)
  • настройка обязательности и значений по умолчанию