Валидация входных данных

Работа с датами в JavaScript неизбежно связана с неопределённостью входных значений: строки приходят из API, числовые метки времени могут быть некорректными, пользовательский ввод часто содержит частично валидные или вовсе ошибочные данные. Библиотека date-fns предоставляет набор инструментов, позволяющих выстраивать предсказуемую и строгую проверку входных данных перед выполнением операций над датами.

Поведение JavaScript Date и источники ошибок

Конструктор Date в JavaScript допускает широкий спектр входных форматов, включая строки, числа и даже частично некорректные значения. Это приводит к ситуации, когда объект Date создаётся, но содержит невалидное значение времени.

const d1 = new Date('invalid-date');
console.log(d1); // Invalid Date
console.log(d1 instanceof Date); // true

Ключевая особенность заключается в том, что Invalid Date остаётся объектом Date, но его числовое представление возвращает NaN:

console.log(Number(d1)); // NaN

Следовательно, проверка только через instanceof Date не обеспечивает корректной валидации.


Функция isValid как базовый механизм проверки

В date-fns центральным инструментом проверки является функция isValid. Она принимает дату, timestamp или строку, преобразует входное значение в дату и проверяет её корректность.

import { isValid } from 'date-fns';

isValid(new Date()); // true
isValid(new Date('invalid')); // false
isValid(1699999999999); // true или false в зависимости от значения

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

  • валидная дата → число (timestamp)
  • невалидная дата → NaN

Разница между isValid и проверкой instanceof Date

Типичная ошибка заключается в использовании только проверки типа:

const value = new Date('bad input');

if (value instanceof Date) {
  // всегда true даже для Invalid Date
}

Корректный подход:

import { isValid } from 'date-fns';

if (isValid(value)) {
  // безопасное использование
}

Проверка входных значений до создания Date

Валидация становится более строгой, если проверка выполняется до создания объекта Date.

Проверка числового timestamp

function isValidTimestamp(value) {
  return typeof value === 'number' && Number.isFinite(value);
}

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

if (isValidTimestamp(input)) {
  const date = new Date(input);
}

Проверка строкового формата

Строки требуют дополнительной осторожности, так как Date.parse допускает неоднозначные форматы:

const timestamp = Date.parse('2024-13-40'); // NaN

parse и риск неявной интерпретации

Функция parse из date-fns позволяет разбирать строки по заданному шаблону:

import { parse } from 'date-fns';

const result = parse('31-12-2024', 'dd-MM-yyyy', new Date());

Однако даже при использовании шаблона возможны ситуации, когда результат формально создаётся, но не является валидной датой:

const date = parse('31-02-2024', 'dd-MM-yyyy', new Date());

Такой результат требует дополнительной проверки:

import { isValid } from 'date-fns';

const date = parse('31-02-2024', 'dd-MM-yyyy', new Date());

if (!isValid(date)) {
  // обработка ошибки
}

parseISO как более строгий вариант

Функция parseISO используется для работы с ISO 8601 строками. Она обеспечивает более предсказуемое поведение, чем Date.parse.

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

const date = parseISO('2024-12-31T10:15:30Z');

if (isValid(date)) {
  // корректная дата
}

ISO-формат минимизирует неоднозначность, однако не гарантирует валидность календарных значений:

parseISO('2024-02-30T00:00:00Z'); // Invalid Date

Защита от null, undefined и неожиданных типов

Одной из частых проблем является передача в функции значений, не относящихся к датам:

const input = null;

Без проверки это приводит к:

new Date(null); // 1970-01-01T00:00:00.000Z

Такое поведение может быть логически некорректным, но формально валидным.

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

function isValidDateInput(value) {
  if (value == null) return false;
  if (typeof value === 'boolean') return false;
  return true;
}

Комбинированный подход:

import { isValid } from 'date-fns';

function safeToDate(value) {
  const date = new Date(value);
  return isValid(date) ? date : null;
}

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

Валидация часто включает контроль допустимого диапазона времени.

const MIN_DATE = new Date('2000-01-01');
const MAX_DATE = new Date('2100-01-01');

function isInRange(date) {
  return date >= MIN_DATE && date <= MAX_DATE;
}

Комбинация с isValid:

import { isValid } from 'date-fns';

function validate(date) {
  return isValid(date) && isInRange(date);
}

Приведение значений через toDate

Функция toDate используется для унификации входных типов:

import { toDate } from 'date-fns';

toDate(1699999999999);
toDate(new Date());

Однако toDate не выполняет строгую валидацию, поэтому требуется дополнительная проверка:

import { toDate, isValid } from 'date-fns';

const date = toDate(input);

if (!isValid(date)) {
  // обработка ошибки
}

Частые ошибки при валидации

1. Проверка только на truthy

if (date) {
  // неверно: Invalid Date является truthy
}

2. Игнорирование NaN через Number

const time = Number(new Date('bad'));

3. Отсутствие проверки после parse

const date = parse('31-02-2024', 'dd-MM-yyyy', new Date());
// использование без isValid

Стратегии построения строгой валидации

На практике применяется многоуровневый подход:

  1. Проверка типа входного значения
  2. Приведение к Date через parse, parseISO или toDate
  3. Проверка через isValid
  4. Дополнительные бизнес-ограничения (диапазоны, логика домена)

Пример универсального валидатора:

import { isValid, toDate } from 'date-fns';

function validateDate(value) {
  if (value == null) return false;

  const date = toDate(value);

  if (!isValid(date)) return false;

  if (date.getFullYear() < 1970) return false;

  return true;
}

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

При работе с коллекциями требуется проверка каждого элемента:

import { isValid, toDate } from 'date-fns';

function validateDates(array) {
  if (!Array.isArray(array)) return false;

  return array.every((item) => isValid(toDate(item)));
}

Ленивая валидация и ранний выход

Для оптимизации вычислений применяется стратегия раннего выхода:

function hasInvalidDate(array) {
  for (const item of array) {
    const date = new Date(item);
    if (!isValid(date)) return true;
  }
  return false;
}

Итоговые принципы строгой проверки

  • объект Date сам по себе не гарантирует валидность
  • проверка instanceof Date недостаточна
  • isValid является базовым критерием корректности
  • parse, parseISO, toDate требуют последующей проверки
  • бизнес-валидация дополняет техническую
  • обработка null, undefined и нечисловых значений обязательна
  • диапазон времени часто является частью логики корректности