Сравнение с текущей датой

Поведение строкового значения "now" в правилах date

В Joi для описания временных ограничений используется специализированный синтаксис, позволяющий ссылаться на момент выполнения валидации. Ключевое значение — строка "now", интерпретируемая как текущий момент времени в системе.

import Joi from 'joi';

const schema = Joi.object({
  publishedAt: Joi.date().less('now')
});

В этом случае значение publishedAt должно быть строго меньше текущей даты и времени в момент вызова validate. Проверка выполняется динамически, а не при создании схемы.

Важно учитывать, что "now" не является фиксированным значением. Оно вычисляется каждый раз при валидации, что делает поведение схемы чувствительным ко времени выполнения.


Сравнение даты с текущим моментом: less, greater, min, max

Joi предоставляет набор методов для сравнения дат относительно текущего времени.

less('now')

Ограничивает дату значением строго меньше текущего времени.

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

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

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

greater('now')

Ограничивает дату значением строго больше текущего времени.

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

Применяется для:

  • будущих событий;
  • дедлайнов;
  • планирования.

max('now') и min('now')

Эти методы работают аналогично, но включают границу.

Joi.date().max('now')
Joi.date().min('now')
  • max('now') — допускает значение вплоть до текущего момента включительно;
  • min('now') — допускает текущий момент и всё, что позже.

Внутренний механизм интерпретации "now"

При передаче строки "now" библиотека интерпретирует её как вызов Date.now() во время выполнения валидации. Это означает:

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

Пример:

const schema = Joi.date().less('now');

schema.validate(new Date(Date.now() - 1000)); // валидно
schema.validate(new Date(Date.now() + 1000)); // ошибка

Проблемы непредсказуемости при тестировании

Использование "now" создаёт эффект недетерминированности. В тестах это может приводить к нестабильным результатам.

Проблема проявляется при сравнении с границей времени:

const schema = Joi.date().less('now');

const result = schema.validate(new Date());

В зависимости от момента выполнения теста значение может оказаться валидным или нет.


Альтернатива: явная фиксация текущего времени

Для устранения неопределённости часто используют явное вычисление текущего времени:

const now = new Date();

const schema = Joi.object({
  expiresAt: Joi.date().greater(now)
});

Здесь момент времени фиксируется один раз и становится стабильной точкой отсчёта.


Использование Joi.ref и динамических значений

Хотя Joi.ref чаще применяется для ссылок внутри объекта, его можно использовать для передачи значений, связанных с текущим временем, через внешний слой.

const now = Date.now();

const schema = Joi.object({
  createdAt: Joi.date().max(new Date(now))
});

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


Кастомная проверка относительно текущего времени

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

const schema = Joi.date().custom((value, helpers) => {
  const now = Date.now();

  if (value.getTime() > now) {
    return helpers.error('date.future');
  }

  return value;
});

Преимущества такого подхода:

  • возможность добавлять сложную бизнес-логику;
  • контроль над сообщениями ошибок;
  • использование любых временных библиотек.

Работа с библиотеками дат внутри Joi-валидации

При необходимости точного контроля времени часто подключаются внешние библиотеки:

  • dayjs
  • moment (устаревает, но встречается в старых проектах)
  • date-fns

Пример с dayjs:

import dayjs from 'dayjs';

const schema = Joi.date().custom((value, helpers) => {
  if (dayjs(value).isAfter(dayjs())) {
    return helpers.error('date.future');
  }
  return value;
});

Такой подход полезен при работе с часовыми поясами и сложными временными интервалами.


Особенности сравнения ISO-строк и объектов Date

Joi автоматически нормализует входные значения:

  • строки ISO ("2026-05-10T12:00:00Z")
  • числовые timestamp
  • объекты Date

Все они приводятся к внутреннему представлению Date.

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

будет корректно работать со всеми этими форматами.

Однако стоит учитывать, что:

  • строка без временной зоны интерпретируется в локальном времени;
  • timestamp всегда считается в UTC;
  • Date объект зависит от окружения исполнения.

Границы времени и погрешности сравнения

При сравнении с текущим временем возможны микрорасхождения:

  • между созданием объекта Date и его проверкой;
  • при асинхронной валидации;
  • при нагрузочном тестировании.

Пример:

const value = new Date();

setTimeout(() => {
  schema.validate(value);
}, 10);

Даже небольшая задержка может изменить результат при строгих сравнениях less('now') или greater('now').


Использование времени в схемах с условной логикой

Комбинация с when позволяет строить зависимые правила:

const schema = Joi.object({
  startDate: Joi.date(),
  endDate: Joi.date().when('startDate', {
    is: Joi.date().less('now'),
    then: Joi.date().greater(Joi.ref('startDate')),
    otherwise: Joi.date().greater('now')
  })
});

Здесь логика зависит одновременно от:

  • текущего времени;
  • значения другого поля.

Поведение при повторной валидации одного и того же объекта

Каждый вызов validate пересчитывает "now" заново:

const schema = Joi.date().greater('now');

const value = new Date(Date.now() + 1000);

schema.validate(value); // может быть валидно
setTimeout(() => schema.validate(value), 2000); // может стать невалидно

Это критично при работе с кэшированными данными или повторной проверкой объектов.


Особенности серверного и клиентского времени

При использовании в распределённых системах важно учитывать:

  • серверное время может отличаться от клиентского;
  • часовые пояса могут не совпадать;
  • NTP-синхронизация не гарантирует абсолютной точности.

Валидация "now" всегда опирается на локальное время среды выполнения, что делает поведение зависимым от инфраструктуры.


Стратегии контроля времени в больших приложениях

Для стабильных систем применяются следующие подходы:

  • передача now через контекст (DI);
  • использование фиксированного времени в тестах;
  • централизованный сервис времени;
  • запрет прямого использования "now" в критичных доменах.

Пример централизованного времени:

const getNow = () => new Date();

const schema = Joi.date().less(getNow());

Такой подход упрощает тестирование и повышает предсказуемость поведения схем.