Даты и время

Валидация дат в JavaScript опирается на встроенный объект Date, который имеет ряд особенностей: нестрогий парсинг строк, влияние локальной временной зоны и различия между форматами ISO и «человеческими» форматами. В связке с YupResolver это становится критичным, поскольку именно схема Yup определяет, какие значения попадут в форму как валидные.

Базовый тип для работы с датами в Yup:

import * as yup from 'yup';

const schema = yup.object({
  birthDate: yup.date()
});

yup.date() принимает любые значения, которые можно преобразовать в корректную дату через new Date(value). Это означает, что строка "2024-01-01" будет интерпретирована как валидная дата, но строка "01/01/2024" может вести себя по-разному в разных окружениях.


Неявное приведение типов и его последствия

Yup выполняет автоматическое приведение типов (coercion). При работе с датами это приводит к тому, что входные значения преобразуются в Date до выполнения проверок.

Пример поведения:

yup.date().isValidSync("2024-01-01"); // true
yup.date().isValidSync("invalid-date"); // false

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

  • строковые форматы, не соответствующие ISO 8601, могут интерпретироваться по-разному
  • пустые строки часто преобразуются в Invalid Date
  • числовые значения трактуются как timestamp

Для строгого контроля часто добавляется кастомная проверка:

birthDate: yup
  .date()
  .typeError('Некорректная дата')

Валидация диапазонов дат

Одна из ключевых задач — ограничение допустимого диапазона дат. Yup предоставляет методы min и max.

const schema = yup.object({
  startDate: yup
    .date()
    .required()
    .min(new Date(2000, 0, 1))
    .max(new Date())
});

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

  • min и max сравнивают значения через timestamp
  • границы включительны
  • при отсутствии времени сравнение идёт с 00:00:00 локального времени

Типичный кейс — возрастные ограничения:

const today = new Date();
const minBirthDate = new Date(
  today.getFullYear() - 18,
  today.getMonth(),
  today.getDate()
);

const schema = yup.object({
  birthDate: yup
    .date()
    .max(minBirthDate, 'Возраст должен быть не менее 18 лет')
});

Форматы ввода и нормализация данных

Формы часто передают даты в виде строк. Для корректной работы с YupResolver важно нормализовать входные данные.

Yup позволяет использовать трансформации:

birthDate: yup
  .date()
  .transform((value, originalValue) => {
    return originalValue === '' ? null : new Date(originalValue);
  })
  .nullable()
  .required()

Такая схема решает несколько задач:

  • пустая строка не превращается в Invalid Date
  • явно допускается null
  • обеспечивается предсказуемость преобразования

Часовые пояса и проблемы интерпретации

Date в JavaScript всегда хранится в виде количества миллисекунд с 1 января 1970 UTC, но отображение зависит от локальной зоны.

При работе с датами в формах это приводит к ошибкам:

  • "2024-01-01" интерпретируется как UTC или локальное время в зависимости от окружения
  • сравнение дат может давать неожиданные результаты при пересечении суток

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

new Date("2024-01-01") < new Date("2023-12-31T23:00:00Z");

Решения:

  • использовать ISO 8601 с явным Z
  • нормализовать дату до начала дня
const normalizeDate = (d) => {
  const date = new Date(d);
  date.setHours(0, 0, 0, 0);
  return date;
};

И интегрировать в Yup:

birthDate: yup
  .date()
  .transform((value, originalValue) =>
    originalValue ? normalizeDate(originalValue) : null
  )

Использование с YupResolver в React Hook Form

YupResolver выступает связующим звеном между схемой Yup и системой валидации форм.

import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';

const schema = yup.object({
  eventDate: yup.date().required()
});

const form = useForm({
  resolver: yupResolver(schema)
});

Механизм работы:

  1. пользователь вводит значение
  2. React Hook Form передаёт данные в resolver
  3. Yup выполняет преобразование и валидацию
  4. возвращается объект ошибок или валидированные данные

Поведение ошибок при некорректных датах

Yup возвращает структурированные ошибки, которые попадают в formState.errors.

Пример:

{
  eventDate: {
    type: "typeError",
    message: "eventDate must be a `date` type, but the final value was: Invalid Date"
  }
}

Часто полезно унифицировать сообщения:

eventDate: yup
  .date()
  .typeError('Введите корректную дату')
  .required('Дата обязательна')

Работа с временем (DateTime)

Хотя Yup ориентирован на Date, он не различает отдельно дату и время. Это создаёт ограничения при работе с временными интервалами.

Пример схемы:

const schema = yup.object({
  startTime: yup.date().required(),
  endTime: yup
    .date()
    .min(yup.ref('startTime'), 'Конец должен быть позже начала')
});

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

  • сравнение происходит по timestamp
  • одинаковые даты с разным временем считаются разными значениями
  • отсутствие нормализации приводит к логическим ошибкам

Сравнение дат через зависимости полей

Yup поддерживает ссылки yup.ref, что позволяет строить зависимости между полями формы:

const schema = yup.object({
  from: yup.date().required(),
  to: yup
    .date()
    .required()
    .min(yup.ref('from'), 'Неверный диапазон')
});

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

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

Обработка nullable и optional дат

Различие между nullable и optional критично:

  • optional() — поле может отсутствовать
  • nullable() — поле может быть null

Комбинированное использование:

yup
  .date()
  .nullable()
  .notRequired()

Это позволяет корректно обрабатывать пустые значения из форм без генерации Invalid Date.


Типичные ошибки при работе с датами

Основные проблемы возникают не в YupResolver, а в несогласованности входных данных:

  • передача пустой строки вместо null
  • использование локальных форматов дат (DD.MM.YYYY)
  • сравнение дат без нормализации времени
  • смешивание UTC и локального времени
  • отсутствие typeError, из-за чего ошибки становятся неочевидными

Корректная схема обычно включает:

  • явное преобразование (transform)
  • строгую типизацию (typeError)
  • ограничения (min, max)
  • обработку пустых значений (nullable, notRequired)