Валидация пользовательского ввода

При работе с пользовательским вводом нельзя полагаться на встроенный конструктор Date. Многие строки интерпретируются по-разному в разных браузерах, а часть значений автоматически «исправляется», создавая ложное ощущение корректности.

Библиотека date-fns предоставляет набор функций для строгой и предсказуемой валидации.

Установка библиотеки:

npm install date-fns

Проверка валидности объекта даты выполняется через isValid.

import { isValid } from "date-fns";

const date = new Date("2025-10-15");

console.log(isValid(date)); // true

Некорректная дата:

const invalidDate = new Date("invalid");

console.log(isValid(invalidDate)); // false

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


Строгий разбор пользовательских строк

Для безопасного преобразования строк применяется функция parse.

import { parse, isValid } from "date-fns";

const value = "25.12.2025";

const parsedDate = parse(
  value,
  "dd.MM.yyyy",
  new Date()
);

console.log(isValid(parsedDate)); // true

Аргументы функции:

  1. Исходная строка
  2. Формат
  3. Базовая дата

Почему parse безопаснее конструктора Date

Проблема стандартного Jav * aScript:

new Date("10/11/2025");

Невозможно однозначно определить:

  • 10 ноября
  • 11 октября

В разных окружениях результат может отличаться.

С date-fns формат задаётся явно:

parse("10/11/2025", "dd/MM/yyyy", new Date());

Проверка полного соответствия формату

Функция parse сама по себе не гарантирует абсолютную корректность строки. Некоторые значения могут быть автоматически преобразованы.

Пример:

parse("32.01.2025", "dd.MM.yyyy", new Date());

JavaScript попытается преобразовать дату в февраль.

Для строгой проверки применяется комбинация parse и format.

import {
  parse,
  format,
  isValid
} from "date-fns";

function isDateValid(value) {
  const parsed = parse(
    value,
    "dd.MM.yyyy",
    new Date()
  );

  if (!isValid(parsed)) {
    return false;
  }

  return format(parsed, "dd.MM.yyyy") === value;
}

console.log(isDateValid("25.12.2025")); // true
console.log(isDateValid("32.01.2025")); // false

Такая схема считается одной из самых надёжных.


Проверка обязательного ввода

Типичная задача — убедиться, что пользователь вообще ввёл дату.

function validateRequiredDate(value) {
  if (!value.trim()) {
    return "Дата обязательна";
  }

  return null;
}

Комбинированная проверка:

function validateDate(value) {
  if (!value.trim()) {
    return "Введите дату";
  }

  const parsed = parse(
    value,
    "dd.MM.yyyy",
    new Date()
  );

  if (!isValid(parsed)) {
    return "Некорректная дата";
  }

  if (format(parsed, "dd.MM.yyyy") !== value) {
    return "Неверный формат";
  }

  return null;
}

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

Часто необходимо ограничивать допустимый интервал.

Например:

  • дата рождения не может быть в будущем;
  • дата бронирования не может быть в прошлом;
  • отчётный период должен находиться внутри месяца.

Для проверки диапазона используются:

  • isBefore
  • isAfter
  • isWithinInterval

Проверка даты на будущее

import {
  parse,
  isAfter
} from "date-fns";

const date = parse(
  "25.12.2030",
  "dd.MM.yyyy",
  new Date()
);

const result = isAfter(date, new Date());

console.log(result); // true

Проверка даты на прошлое

import {
  parse,
  isBefore
} from "date-fns";

const date = parse(
  "01.01.2020",
  "dd.MM.yyyy",
  new Date()
);

console.log(
  isBefore(date, new Date())
);

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

import {
  parse,
  isWithinInterval
} from "date-fns";

const date = parse(
  "15.06.2025",
  "dd.MM.yyyy",
  new Date()
);

const result = isWithinInterval(date, {
  start: new Date(2025, 0, 1),
  end: new Date(2025, 11, 31)
});

console.log(result); // true

Проверка возраста пользователя

Распространённый сценарий — ограничение минимального возраста.

Функция differenceInYears вычисляет разницу между датами.

import {
  parse,
  differenceInYears
} from "date-fns";

function isAdult(value) {
  const birthDate = parse(
    value,
    "dd.MM.yyyy",
    new Date()
  );

  const age = differenceInYears(
    new Date(),
    birthDate
  );

  return age >= 18;
}

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

import {
  parse,
  isBefore,
  isAfter
} from "date-fns";

function validateDateRange(value) {
  const date = parse(
    value,
    "dd.MM.yyyy",
    new Date()
  );

  const minDate = new Date(2020, 0, 1);
  const maxDate = new Date(2030, 11, 31);

  if (isBefore(date, minDate)) {
    return "Дата слишком ранняя";
  }

  if (isAfter(date, maxDate)) {
    return "Дата слишком поздняя";
  }

  return null;
}

Проверка времени

Пользовательский ввод времени также требует строгой валидации.

import {
  parse,
  format,
  isValid
} from "date-fns";

function validateTime(value) {
  const parsed = parse(
    value,
    "HH:mm",
    new Date()
  );

  if (!isValid(parsed)) {
    return false;
  }

  return format(parsed, "HH:mm") === value;
}

console.log(validateTime("18:30")); // true
console.log(validateTime("25:90")); // false

Проверка даты и времени одновременно

import {
  parse,
  format,
  isValid
} from "date-fns";

function validateDateTime(value) {
  const parsed = parse(
    value,
    "dd.MM.yyyy HH:mm",
    new Date()
  );

  if (!isValid(parsed)) {
    return false;
  }

  return (
    format(
      parsed,
      "dd.MM.yyyy HH:mm"
    ) === value
  );
}

Валидация ISO-строк

API часто работают с ISO-форматом.

Пример:

2025-12-25T18:30:00Z

Для разбора используется parseISO.

import {
  parseISO,
  isValid
} from "date-fns";

const date = parseISO(
  "2025-12-25T18:30:00Z"
);

console.log(isValid(date));

Проверка високосного года

import { isLeapYear } from "date-fns";

console.log(
  isLeapYear(new Date(2024, 0, 1))
); // true

Практическое применение:

  • проверка 29 февраля;
  • генерация календарей;
  • расчёт количества дней.

Проверка существования даты

Иногда дата выглядит корректной, но фактически не существует.

Например:

31.02.2025

Строгая проверка:

function existsDate(value) {
  const parsed = parse(
    value,
    "dd.MM.yyyy",
    new Date()
  );

  return (
    isValid(parsed) &&
    format(parsed, "dd.MM.yyyy") === value
  );
}

Валидация периода

Часто требуется проверить две даты одновременно.

Например:

  • начало периода;
  • конец периода.
import {
  parse,
  isAfter
} from "date-fns";

function validatePeriod(start, end) {
  const startDate = parse(
    start,
    "dd.MM.yyyy",
    new Date()
  );

  const endDate = parse(
    end,
    "dd.MM.yyyy",
    new Date()
  );

  if (isAfter(startDate, endDate)) {
    return "Начальная дата больше конечной";
  }

  return null;
}

Работа с HTML-формами

Типичная интеграция с <input>:

<input id="date" />
<button id="send">
  Отправить
</button>
import {
  parse,
  format,
  isValid
} from "date-fns";

const input =
  document.getElementById("date");

const button =
  document.getElementById("send");

button.addEventListener("click", () => {
  const value = input.value;

  const parsed = parse(
    value,
    "dd.MM.yyyy",
    new Date()
  );

  const isCorrect =
    isValid(parsed) &&
    format(parsed, "dd.MM.yyyy") === value;

  if (!isCorrect) {
    alert("Ошибка даты");
    return;
  }

  console.log(parsed);
});

Валидация в React

Пример проверки поля формы:

import { useState } from "react";

import {
  parse,
  format,
  isValid
} from "date-fns";

export default function App() {
  const [value, setValue] =
    useState("");

  const [error, setError] =
    useState("");

  function validate(dateString) {
    const parsed = parse(
      dateString,
      "dd.MM.yyyy",
      new Date()
    );

    const correct =
      isValid(parsed) &&
      format(
        parsed,
        "dd.MM.yyyy"
      ) === dateString;

    if (!correct) {
      setError("Неверная дата");
      return;
    }

    setError("");
  }

  return (
    <div>
      <input
        value={value}
        onCha nge={(e) => {
          setValue(e.target.value);
          validate(e.target.value);
        }}
      />

      {error && <p>{error}</p>}
    </div>
  );
}

Проверка дат в API

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

Пример для Node.js:

import {
  parseISO,
  isValid
} from "date-fns";

function validatePayload(body) {
  const date = parseISO(body.createdAt);

  if (!isValid(date)) {
    throw new Error(
      "Некорректная дата"
    );
  }
}

Распространённые ошибки

Использование new Date(userInput)

new Date("01.02.2025");

Проблемы:

  • неоднозначность формата;
  • различия браузеров;
  • автоматические преобразования.

Отсутствие повторного форматирования

Неправильно:

isValid(parse(value, "dd.MM.yyyy", new Date()));

Правильно:

const parsed = parse(
  value,
  "dd.MM.yyyy",
  new Date()
);

const valid =
  isValid(parsed) &&
  format(parsed, "dd.MM.yyyy") === value;

Игнорирование часовых поясов

ISO-даты могут содержать timezone:

2025-10-15T12:00:00+05:00

При сравнении дат необходимо учитывать локальное время и UTC.


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

import {
  parse,
  format,
  isValid,
  isBefore,
  isAfter
} from "date-fns";

export function validateDate(
  value,
  options = {}
) {
  const {
    formatPattern = "dd.MM.yyyy",
    minDate,
    maxDate
  } = options;

  if (!value.trim()) {
    return {
      valid: false,
      error: "Пустое значение"
    };
  }

  const parsed = parse(
    value,
    formatPattern,
    new Date()
  );

  if (!isValid(parsed)) {
    return {
      valid: false,
      error: "Некорректная дата"
    };
  }

  if (
    format(parsed, formatPattern) !==
    value
  ) {
    return {
      valid: false,
      error: "Неверный формат"
    };
  }

  if (
    minDate &&
    isBefore(parsed, minDate)
  ) {
    return {
      valid: false,
      error: "Дата меньше допустимой"
    };
  }

  if (
    maxDate &&
    isAfter(parsed, maxDate)
  ) {
    return {
      valid: false,
      error: "Дата больше допустимой"
    };
  }

  return {
    valid: true,
    value: parsed
  };
}

Пример использования:

const result = validateDate(
  "25.12.2025",
  {
    minDate: new Date(2020, 0, 1),
    maxDate: new Date(2030, 11, 31)
  }
);

console.log(result);