Форматы даты и времени

Обработка дат и времени в схемах валидации требует строгого разделения между входными форматами данных и внутренним представлением. В Zod для этого используются базовые типы, преобразования и уточняющие проверки, позволяющие контролировать как структуру, так и семантику временных значений.


Базовый тип z.date()

Тип z.date() предназначен для работы с объектами Date JavaScript. Он проверяет, что входное значение является валидным экземпляром Date и не содержит некорректного состояния.

import { z } from "zod";

const schema = z.date();

Ключевые особенности:

  • принимает только Date-объекты
  • отклоняет строки, числа и Invalid Date
  • не выполняет автоматического парсинга

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

schema.parse(new Date()); // OK
schema.parse("2025-01-01"); // ошибка
schema.parse(Date.now()); // ошибка

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

Даже объект Date может быть невалидным. Поэтому часто добавляется уточнение:

const schema = z.date().refine((val) => !isNaN(val.getTime()));

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


Автоматическое преобразование через z.coerce.date()

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

const schema = z.coerce.date();

Поддерживаемые входные форматы:

  • строки ISO 8601
  • timestamp в миллисекундах
  • Date-объекты

Примеры:

schema.parse("2025-01-01T10:00:00Z");
schema.parse(1735689600000);
schema.parse(new Date());

Механизм основан на вызове new Date(value), что накладывает ограничения на нестандартные форматы.


Строки в формате ISO 8601

Для строгой проверки строкового формата используется z.string().datetime().

const schema = z.string().datetime();

Поддерживается стандарт ISO 8601:

  • 2025-01-01T10:00:00Z
  • 2025-01-01T10:00:00+03:00

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

const schema = z.string().datetime({
  offset: true,
  precision: 3
});
  • offset — разрешение временных смещений
  • precision — контроль количества миллисекунд

Преобразование строки в Date через preprocess

Для нестандартных форматов используется z.preprocess, позволяющий преобразовать вход перед валидацией.

Формат DD.MM.YYYY

const schema = z.preprocess((val) => {
  if (typeof val !== "string") return val;

  const [day, month, year] = val.split(".");
  return new Date(`${year}-${month}-${day}`);
}, z.date());

Такой подход разделяет:

  • этап парсинга
  • этап валидации результата

Разбор времени без даты

Для значений вида HH:mm применяется комбинированная схема:

const timeSchema = z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/);

Детали:

  • часы ограничены диапазоном 00–23
  • минуты 00–59
  • строгая проверка формата

Преобразование времени в структуру

Для дальнейшей работы часто выполняется трансформация:

const timeSchema = z.string()
  .regex(/^([01]\d|2[0-3]):([0-5]\d)$/)
  .transform((val) => {
    const [hours, minutes] = val.split(":").map(Number);
    return { hours, minutes };
  });

Результат:

{ hours: 14, minutes: 30 }

Работа с Unix timestamp

Числовые временные метки обрабатываются через z.number() с последующим преобразованием.

const schema = z.number().int().transform((val) => new Date(val));

Дополнительная проверка:

const schema = z.number()
  .int()
  .positive()
  .transform((val) => new Date(val));

Используемые единицы:

  • миллисекунды с 1970-01-01
  • стандарт JavaScript Date

Комбинирование форматов входных данных

Часто требуется поддержка нескольких форматов одновременно:

const schema = z.union([
  z.string().datetime(),
  z.coerce.date(),
  z.number().int()
]).transform((val) => new Date(val));

Такой подход позволяет принимать:

  • ISO-строки
  • timestamp
  • Date-объекты

Нормализация даты

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

const schema = z.coerce.date().transform((date) => ({
  iso: date.toISOString(),
  timestamp: date.getTime()
}));

Результат фиксирует единое представление вне зависимости от входного формата.


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

Ограничение временных интервалов реализуется через refine:

const schema = z.coerce.date().refine((date) => {
  const min = new Date("2020-01-01");
  const max = new Date("2030-01-01");
  return date >= min && date <= max;
});

Применение:

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

Работа с часовыми поясами

JavaScript Date хранит время в UTC, но отображение зависит от локальной зоны. В Zod отсутствует встроенная поддержка полноценной работы с часовыми поясами, поэтому используются внешние библиотеки или строковые форматы с offset.

Пример валидации строки с временной зоной:

const schema = z.string().datetime({ offset: true });

Форматы:

  • 2025-01-01T10:00:00+05:00
  • 2025-01-01T05:00:00Z

Обработка некорректных значений

Типичные ошибки:

  • передача строки в z.date()
  • невалидный ISO формат
  • Invalid Date после преобразования
  • числовые значения вне диапазона Number.MAX_SAFE_INTEGER

Защитные схемы:

const safeDate = z.coerce.date().refine((d) => !isNaN(d.getTime()));

Композиция схем для сложных моделей

В структурах данных даты часто комбинируются с другими полями:

const eventSchema = z.object({
  title: z.string(),
  start: z.coerce.date(),
  end: z.coerce.date()
}).refine((data) => data.end > data.start);

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


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

Zod позволяет формировать выходные строки:

const schema = z.coerce.date().transform((date) =>
  date.toLocaleDateString("ru-RU")
);

Результат:

  • 01.01.2025

Дополнительно возможно формирование структурированных представлений:

.transform((date) => ({
  year: date.getFullYear(),
  month: date.getMonth() + 1,
  day: date.getDate()
}));

Ограничения подхода

Работа с датами в Zod опирается на встроенный объект Date, что накладывает ограничения:

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

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