Парсинг дат

Библиотека Superstruct предоставляет несколько способов валидации и преобразования дат. Основная сложность заключается в том, что в JavaScript дата может существовать в разных формах:

  • объект Date
  • строка ISO (2026-05-12T10:00:00Z)
  • UNIX timestamp
  • локализованная строка
  • nullable-значение
  • частично корректный объект Date

Superstruct позволяет:

  • проверять тип данных;
  • валидировать корректность даты;
  • преобразовывать строки в Date;
  • ограничивать диапазоны дат;
  • строить составные схемы;
  • реализовывать кастомные проверки.

Проверка объекта Date

Для проверки экземпляра Date используется встроенный struct date().

import { assert, date } from 'superstruct'

const UserSchema = date()

assert(new Date(), UserSchema)

Проверка проходит только для объектов класса Date.

assert('2026-05-12', date()) // Ошибка

Особенность Jav * aScript: некорректные даты

В JavaScript объект Date может быть формально создан, но содержать некорректное значение.

const invalid = new Date('wrong')

console.log(invalid instanceof Date) // true
console.log(isNaN(invalid.getTime())) // true

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

import { assert, date } from 'superstruct'

const invalid = new Date('wrong')

assert(invalid, date()) // Ошибки нет

Для полноценной проверки требуется дополнительная валидация.


Проверка корректности даты через refine

Функция refine() позволяет создавать дополнительное правило проверки.

import { date, refine } from 'superstruct'

const ValidDate = refine(date(), 'ValidDate', value => {
  return !isNaN(value.getTime())
})

Теперь схема отклоняет некорректные даты.

import { assert } from 'superstruct'

assert(new Date(), ValidDate)

assert(new Date('wrong'), ValidDate) // Ошибка

Проверка ISO-строк

Во многих API даты приходят строками.

{
  "createdAt": "2026-05-12T08:30:00Z"
}

Для проверки используется string() вместе с refine().

import { string, refine } from 'superstruct'

const ISODateString = refine(
  string(),
  'ISODateString',
  value => {
    return !isNaN(Date.parse(value))
  }
)

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

ISODateString.create('2026-05-12T08:30:00Z')

Строгая проверка формата ISO

Date.parse() принимает слишком много форматов. Для строгой проверки применяется регулярное выражение.

import { string, refine } from 'superstruct'

const StrictISODate = refine(
  string(),
  'StrictISODate',
  value => {
    return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(value)
  }
)

Проверка:

StrictISODate.create('2026-05-12T10:20:30.000Z')

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

Superstruct поддерживает трансформацию данных через coerce().

import { coerce, date, string } from 'superstruct'

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

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

const result = DateFromString.create(
  '2026-05-12T10:00:00Z'
)

console.log(result instanceof Date) // true

Комбинация coerce и refine

Обычно преобразование совмещают с проверкой корректности.

import {
  coerce,
  date,
  string,
  refine
} from 'superstruct'

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

Теперь схема:

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

Nullable-даты

Дата может отсутствовать.

{
  deletedAt: null
}

Для этого используется nullable().

import {
  nullable,
  date
} from 'superstruct'

const NullableDate = nullable(date())

Допустимые значения:

new Date()
null

Optional-поля с датами

Если поле может отсутствовать полностью:

import {
  optional,
  date
} from 'superstruct'

const OptionalDate = optional(date())

Пример:

{
  updatedAt: undefined
}

Объекты с датами

Чаще всего даты валидируются внутри объекта.

import {
  object,
  string,
  date
} from 'superstruct'

const User = object({
  id: string(),
  createdAt: date(),
  updatedAt: date()
})

Парсинг дат внутри объекта

import {
  object,
  string,
  date,
  coerce
} from 'superstruct'

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

const User = object({
  id: string(),
  createdAt: DateFromISO
})

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

const user = User.create({
  id: 'u1',
  createdAt: '2026-05-12T08:00:00Z'
})

console.log(user.createdAt instanceof Date)

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

Дата не раньше текущего момента

import {
  refine,
  date
} from 'superstruct'

const FutureDate = refine(
  date(),
  'FutureDate',
  value => value.getTime() > Date.now()
)

Дата только в прошлом

const PastDate = refine(
  date(),
  'PastDate',
  value => value.getTime() < Date.now()
)

Ограничение минимальной даты

const minDate = new Date('2025-01-01')

const DateAfter2025 = refine(
  date(),
  'DateAfter2025',
  value => value >= minDate
)

Ограничение диапазона

const start = new Date('2025-01-01')
const end = new Date('2026-01-01')

const DateInRange = refine(
  date(),
  'DateInRange',
  value => {
    return value >= start && value <= end
  }
)

Работа с UNIX timestamp

Иногда дата приходит числом.

{
  createdAt: 1747000000
}

Преобразование timestamp в Date:

import {
  coerce,
  date,
  number
} from 'superstruct'

const DateFromTimestamp = coerce(
  date(),
  number(),
  value => new Date(value * 1000)
)

Timestamp в миллисекундах

const DateFromMs = coerce(
  date(),
  number(),
  value => new Date(value)
)

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

Через union() можно принимать несколько типов.

import {
  union,
  string,
  number,
  date
} from 'superstruct'

const FlexibleDate = union([
  string(),
  number(),
  date()
])

Однако такая схема только проверяет типы. Для полноценного парсинга требуется coerce().


Универсальный парсер дат

import {
  union,
  string,
  number,
  date,
  coerce,
  refine
} from 'superstruct'

const RawDate = union([
  string(),
  number(),
  date()
])

const UniversalDate = refine(
  coerce(
    date(),
    RawDate,
    value => {
      if (value instanceof Date) {
        return value
      }

      if (typeof value === 'number') {
        return new Date(value)
      }

      return new Date(value)
    }
  ),
  'UniversalDate',
  value => !isNaN(value.getTime())
)

Поддерживаются:

'2026-05-12'
1747000000000
new Date()

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

Если дата отсутствует, можно подставлять текущее значение.

import {
  defaulted,
  date
} from 'superstruct'

const CreatedAt = defaulted(
  date(),
  () => new Date()
)

Автоматическое создание даты

import {
  object,
  string,
  defaulted,
  date
} from 'superstruct'

const Post = object({
  id: string(),
  createdAt: defaulted(
    date(),
    () => new Date()
  )
})

Проверка массива дат

import {
  array,
  date
} from 'superstruct'

const DateArray = array(date())

Массив ISO-строк

import {
  array,
  string,
  refine
} from 'superstruct'

const ISOString = refine(
  string(),
  'ISOString',
  value => !isNaN(Date.parse(value))
)

const ISODateArray = array(ISOString)

Парсинг массива строк в Date

import {
  array,
  string,
  coerce,
  date
} from 'superstruct'

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

const ParsedDateArray = array(DateFromISO)

Вложенные структуры с датами

import {
  object,
  array,
  string
} from 'superstruct'

const Comment = object({
  text: string(),
  createdAt: DateFromISO
})

const Article = object({
  title: string(),
  comments: array(Comment)
})

Кастомные сообщения об ошибках

import {
  refine,
  date
} from 'superstruct'

const ValidDate = refine(
  date(),
  'ValidDate',
  value => !isNaN(value.getTime())
)

Перехват ошибки:

import { assert } from 'superstruct'

try {
  assert(new Date('wrong'), ValidDate)
} catch (error) {
  console.log(error.message)
}

create вместо assert

assert

Проверяет данные и выбрасывает исключение.

assert(value, schema)

create

Проверяет и преобразует данные.

const result = schema.create(value)

Для дат create() особенно полезен при использовании coerce().


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

Метод validate() возвращает кортеж.

import { validate } from 'superstruct'

const [error, value] = validate(
  '2026-05-12',
  ParsedDate
)

Проверка:

if (error) {
  console.error(error)
} else {
  console.log(value)
}

Типизация дат в TypeScript

import { Infer } from 'superstruct'

type ParsedDateType = Infer<typeof ParsedDate>

Результат:

Date

Проблемы временных зон

JavaScript автоматически учитывает timezone.

new Date('2026-05-12')

Такой формат интерпретируется как UTC.

Локальное время:

new Date('2026-05-12T10:00:00')

UTC-время:

new Date('2026-05-12T10:00:00Z')

При валидации API рекомендуется использовать ISO-формат с Z.


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

import {
  string,
  refine
} from 'superstruct'

const DateOnly = refine(
  string(),
  'DateOnly',
  value => {
    return /^\d{4}-\d{2}-\d{2}$/.test(value)
  }
)

Проверка RFC 3339

const RFC3339 = refine(
  string(),
  'RFC3339',
  value => {
    return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(.\d+)?(Z|[+-]\d{2}:\d{2})$/.test(value)
  }
)

Комплексная схема даты для API

import {
  object,
  string,
  coerce,
  refine,
  date
} from 'superstruct'

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

const Event = object({
  id: string(),
  title: string(),
  startsAt: APIDate,
  endsAt: APIDate
})

Проверка логики дат

Superstruct позволяет проверять взаимосвязи между полями.

import {
  object,
  refine
} from 'superstruct'

const Event = refine(
  object({
    start: date(),
    end: date()
  }),
  'EventDates',
  value => {
    return value.start < value.end
  }
)

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

Иногда схема зависит от значения.

import {
  dynamic,
  string,
  date
} from 'superstruct'

const FlexibleSchema = dynamic(value => {
  if (typeof value === 'string') {
    return string()
  }

  return date()
})

Валидация nullable ISO-даты

import {
  nullable,
  refine,
  string
} from 'superstruct'

const NullableISODate = nullable(
  refine(
    string(),
    'ISODate',
    value => !isNaN(Date.parse(value))
  )
)

Комбинация optional и defaulted

import {
  optional,
  defaulted,
  date
} from 'superstruct'

const UpdatedAt = defaulted(
  optional(date()),
  () => new Date()
)

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

import {
  refine,
  date
} from 'superstruct'

const AdultBirthDate = refine(
  date(),
  'AdultBirthDate',
  value => {
    const age =
      new Date().getFullYear() -
      value.getFullYear()

    return age >= 18
  }
)

Проверка окончания срока действия

const ExpirationDate = refine(
  date(),
  'ExpirationDate',
  value => value > new Date()
)

Частые ошибки при работе с датами

Проверка только через date()

date()

Проверяется лишь экземпляр Date, но не валидность значения.


Использование Date.parse без строгого формата

Date.parse('12/05/2026')

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


Отсутствие timezone

2026-05-12T10:00:00

Такой формат может интерпретироваться по-разному.


Смешивание секунд и миллисекунд

new Date(1747000000)

Получится дата 1970 года.


Рекомендуемый подход

Для production-систем обычно используется комбинация:

refine(
  coerce(
    date(),
    string(),
    value => new Date(value)
  ),
  'ValidDate',
  value => !isNaN(value.getTime())
)

Такая схема:

  • принимает строку;
  • преобразует её в Date;
  • гарантирует корректность значения;
  • хорошо интегрируется с API;
  • совместима с TypeScript;
  • легко расширяется дополнительными правилами.