Сравнение дат между собой

Работа с датами в валидационной библиотеке Yup строится вокруг объекта Yup.date(), который предоставляет набор методов для проверки, ограничения и сравнения значений типа Date. При разработке форм и бизнес-логики часто возникает необходимость сравнивать даты между собой: проверять хронологический порядок, ограничивать диапазоны, задавать зависимость одной даты от другой.


В Yup все операции сравнения дат опираются на внутреннее приведение значений к типу Date. Это означает, что строковые представления дат (например, ISO-формат) автоматически преобразуются перед проверкой.

Основные методы сравнения:

  • min(date, message?) — минимально допустимая дата
  • max(date, message?) — максимально допустимая дата
  • test(name, message, fn) — произвольная логика сравнения

Ограничение минимальной и максимальной даты

Наиболее простой способ сравнения дат — использование диапазонов.

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

import * as Yup from 'yup';

const schema = Yup.object({
  startDate: Yup.date()
    .min(new Date(2024, 0, 1), 'Дата не может быть раньше 01.01.2024')
});

Метод min проверяет, что значение не меньше указанной границы. Внутри происходит сравнение временных меток (getTime()).

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

const schema = Yup.object({
  endDate: Yup.date()
    .max(new Date(2026, 11, 31), 'Дата не может быть позже конца 2026 года')
});

Метод max работает симметрично min, ограничивая верхнюю границу диапазона.


Сравнение двух дат внутри одной схемы

Частая задача — проверка, что одна дата следует после другой. Например, дата окончания должна быть позже даты начала.

Использование ref

Yup позволяет ссылаться на другие поля схемы через Yup.ref.

const schema = Yup.object({
  startDate: Yup.date().required(),
  endDate: Yup.date()
    .min(Yup.ref('startDate'), 'Дата окончания должна быть позже начала')
});

Здесь происходит динамическое сравнение: значение endDate проверяется относительно значения startDate в момент валидации.


Строгое сравнение с исключением равенства

Иногда требуется, чтобы даты не только соблюдали порядок, но и не были равны.

const schema = Yup.object({
  startDate: Yup.date().required(),
  endDate: Yup.date()
    .min(Yup.ref('startDate'), 'Недопустимый диапазон')
    .test(
      'not-equal',
      'Даты не должны совпадать',
      function (value) {
        const { startDate } = this.parent;
        if (!value || !startDate) return true;
        return value.getTime() !== new Date(startDate).getTime();
      }
    )
});

Здесь используется test, так как встроенные методы min и max не различают строгое и нестрогое сравнение.


Проверка строгого “после” и “до”

Для более точного контроля используются пользовательские тесты.

Проверка “строго после”

endDate: Yup.date().test(
  'is-after',
  'Дата должна быть строго позже',
  function (value) {
    const { startDate } = this.parent;
    if (!value || !startDate) return true;
    return value.getTime() > new Date(startDate).getTime();
  }
)

Проверка “строго до”

startDate: Yup.date().test(
  'is-before',
  'Дата должна быть раньше',
  function (value) {
    const { endDate } = this.parent;
    if (!value || !endDate) return true;
    return value.getTime() < new Date(endDate).getTime();
  }
)

Диапазоны дат

Сравнение часто используется для задания интервалов. Yup позволяет комбинировать min и max.

const schema = Yup.object({
  eventDate: Yup.date()
    .min(new Date(2025, 0, 1))
    .max(new Date(2025, 11, 31))
});

Такой подход задаёт фиксированный временной промежуток.


Динамические диапазоны через ref

Гораздо более гибкий вариант — зависимость от других полей.

const schema = Yup.object({
  minDate: Yup.date().required(),
  maxDate: Yup.date()
    .min(Yup.ref('minDate'), 'Максимальная дата не может быть раньше минимальной')
});

Здесь maxDate динамически зависит от minDate, что позволяет строить каскадные ограничения.


Преобразование значений перед сравнением

Yup выполняет сравнение только после приведения типов. При необходимости можно явно управлять этим процессом через transform.

const schema = Yup.object({
  date: Yup.date().transform((value, originalValue) => {
    return originalValue === '' ? null : value;
  })
});

Это важно при сравнении, так как некорректное преобразование может привести к Invalid Date, что нарушает логику min и max.


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

Распространённый кейс — проверка относительно текущего времени.

const schema = Yup.object({
  birthDate: Yup.date()
    .max(new Date(), 'Дата не может быть в будущем')
});

Или наоборот:

const schema = Yup.object({
  eventDate: Yup.date()
    .min(new Date(), 'Дата не может быть в прошлом')
});

Точность сравнения: миллисекунды и нормализация

Сравнение в Yup основано на getTime(), что включает миллисекунды. Это может приводить к неожиданным результатам, если одна дата содержит время, а другая нет.

Пример проблемы:

  • 2026-01-01T00:00:00.000Z
  • 2026-01-01T12:00:00.000Z

Несмотря на одинаковый день, сравнение даст различие.

Для устранения таких ситуаций используется нормализация:

function normalize(date) {
  const d = new Date(date);
  d.setHours(0, 0, 0, 0);
  return d;
}

И применение в test:

test('same-day', 'Даты должны быть в один день', function (value) {
  const { otherDate } = this.parent;
  if (!value || !otherDate) return true;

  return normalize(value).getTime() === normalize(otherDate).getTime();
});

Комбинированные условия сравнения

Сложные схемы часто требуют объединения нескольких проверок:

const schema = Yup.object({
  startDate: Yup.date().required(),
  endDate: Yup.date()
    .required()
    .min(Yup.ref('startDate'))
    .test('max-range', 'Интервал слишком большой', function (value) {
      const { startDate } = this.parent;
      if (!value || !startDate) return true;

      const diff = value - new Date(startDate);
      const days = diff / (1000 * 60 * 60 * 24);

      return days <= 30;
    })
});

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

  • проверка порядка дат
  • ограничение максимальной длины интервала

Особенности поведения null и undefined

При сравнении дат Yup игнорирует проверку min и max, если значение отсутствует, если не указано required().

Yup.date().min(new Date()) // null пройдет проверку
Yup.date().required().min(new Date()) // null не пройдет

Это важно учитывать при построении зависимых схем.


Итоговая модель сравнений

Внутренняя логика сравнения дат в Yup опирается на три механизма:

  • декларативные ограничения (min, max)
  • ссылки на другие поля (Yup.ref)
  • пользовательские проверки (test)

Их комбинация позволяет строить как простые временные ограничения, так и сложные бизнес-правила, зависящие от нескольких дат одновременно.