Date

В библиотеке Superstruct работа с датами основана на строгой типизации значений и их проверке через встроенные и расширяемые структуры. Основная цель — обеспечить предсказуемую валидацию объектов Date, строковых представлений дат и пользовательских форматов, сохраняя контроль над преобразованием и ошибками.

Функция date() создаёт структурный валидатор, который проверяет, является ли значение экземпляром Date и содержит ли оно корректную временную метку.

import { date } from 'superstruct';

const DateStruct = date();

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

Проверка экземпляра Date

Основной принцип работы заключается в проверке через instanceof Date и дополнительной проверке на валидность временной метки.

DateStruct.assert(new Date()); // проходит проверку
DateStruct.assert('2024-01-01'); // ошибка
DateStruct.assert(1700000000000); // ошибка

Важный момент — даже валидный объект Date, содержащий Invalid Date, считается некорректным.

const d = new Date('invalid');

DateStruct.assert(d); // ошибка

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

Проверка включает контроль метода getTime(). Если результат — NaN, структура считается нарушенной.

const isValidDate = (value) =>
  value instanceof Date && !isNaN(value.getTime());

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

Строки и преобразование (coercion)

Часто данные приходят в виде строк ISO-формата. Для этого применяется механизм преобразования через coerce.

import { coerce, date } from 'superstruct';

const CoercedDate = coerce(date(), string(), (value) => new Date(value));

Теперь строка автоматически преобразуется в Date перед проверкой.

CoercedDate.assert('2024-05-01T10:00:00Z'); // проходит

Если строка не может быть распознана, результатом станет Invalid Date, что приведёт к ошибке валидации.

Разделение этапов: преобразование и проверка

Архитектура Superstruct разделяет два процесса:

  • coerce — приведение значения к нужному типу
  • validate — проверка структуры

Это позволяет явно контролировать поток данных:

const structure = coerce(date(), string(), (v) => new Date(v));

const [error, value] = structure.validate('2025-01-01');

Если требуется строгий режим без преобразований, используется только date().

Композиция с refinement

Для более строгих условий используется refine, позволяющий добавлять пользовательские ограничения.

import { refine, date } from 'superstruct';

const FutureDate = refine(date(), 'FutureDate', (value) => {
  return value.getTime() > Date.now();
});

Теперь структура допускает только даты в будущем.

FutureDate.assert(new Date('2030-01-01')); // проходит
FutureDate.assert(new Date('2000-01-01')); // ошибка

Работа с диапазонами дат

Частый сценарий — проверка диапазонов времени.

const DateRange = refine(date(), 'DateRange', (value) => {
  const min = new Date('2020-01-01');
  const max = new Date('2030-01-01');

  return value >= min && value <= max;
});

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

Интеграция с объектами

Дата часто используется как часть сложных структур:

import { object, string } from 'superstruct';

const Event = object({
  title: string(),
  createdAt: date(),
});
Event.assert({
  title: 'Meeting',
  createdAt: new Date(),
});

При использовании coerce можно автоматически принимать строки:

const Event = object({
  title: string(),
  createdAt: CoercedDate,
});

Обработка ошибок

Ошибки валидации содержат информацию о пути, типе и причине нарушения.

try {
  DateStruct.assert('not-a-date');
} catch (e) {
  console.log(e.failures());
}

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

Особенности работы с временными зонами

Superstruct не интерпретирует временные зоны самостоятельно — это задача JavaScript Date. Однако важно учитывать, что:

  • строки ISO интерпретируются как UTC
  • локальные строки могут давать разные результаты
  • сравнение дат происходит по timestamp
new Date('2024-01-01T00:00:00Z');

Кастомные парсеры дат

В реальных приложениях часто требуется поддержка нестандартных форматов.

const CustomDate = coerce(date(), string(), (value) => {
  const [day, month, year] = value.split('.');
  return new Date(`${year}-${month}-${day}`);
});

Теперь строка 31.12.2024 корректно преобразуется в объект Date.

Использование валидации без преобразования

В случаях, когда входные данные уже нормализованы, используется чистая проверка:

const StrictDate = date();

Это исключает любые неявные преобразования и повышает предсказуемость.

Поведение при сериализации

При работе с API важно помнить, что Date при сериализации превращается в строку:

JSON.stringify({ date: new Date() });

Superstruct в этом случае должен использовать coerce, иначе валидация не пройдёт.

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

Хотя библиотека не предоставляет встроенных операторов сравнения, они легко реализуются через refine:

const NotPast = refine(date(), 'NotPast', (value) => {
  return value.getTime() >= Date.now();
});

Производительность валидации

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

  • instanceof проверка
  • вызов getTime()
  • опциональная функция refine

Основная нагрузка появляется только при кастомных преобразованиях.

Комбинация с union и optional

Дата может участвовать в гибких структурах:

import { union, optional } from 'superstruct';

const MaybeDate = optional(date());

const Flexible = union([date(), string()]);

В последнем случае часто добавляется coerce для нормализации.

Использование в формах и API

При обработке пользовательского ввода дата чаще всего поступает как строка. Типичный паттерн:

const FormSchema = object({
  birthday: coerce(date(), string(), (v) => new Date(v)),
});

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

Ограничения модели Date

Встроенный Date в JavaScript имеет особенности:

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

Superstruct компенсирует это через строгую валидацию и явные преобразования.

Пользовательские стратегии валидации

Гибкость достигается через комбинацию базовых элементов:

const SafeDate = refine(
  coerce(date(), string(), (v) => new Date(v)),
  'SafeDate',
  (value) => !isNaN(value.getTime())
);

Такая структура объединяет преобразование и проверку в одном типе.

Составные правила для временных данных

При работе с событиями часто требуется комплексная логика:

const EventDate = refine(date(), 'EventDate', (value) => {
  const now = Date.now();
  const maxFuture = now + 1000 * 60 * 60 * 24 * 365;

  return value.getTime() >= now && value.getTime() <= maxFuture;
});

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