Обработка ошибок парсинга

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

Наиболее распространённые проблемы:

  • невалидный формат даты;
  • несоответствие шаблона строке;
  • отсутствие обязательных компонентов;
  • передача undefined или null;
  • некорректные значения дней и месяцев;
  • ошибки временных зон;
  • использование неверной локали.

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

После парсинга дата должна проходить обязательную проверку. В date-fns для этого используется функция isValid.

import { parse, isValid } from 'date-fns'

const result = parse('31.02.2024', 'dd.MM.yyyy', new Date())

console.log(isValid(result)) // false

Функция parse не выбрасывает исключение при ошибке. Вместо этого создаётся объект Invalid Date.

Это важная особенность библиотеки:

const invalid = new Date('wrong')

console.log(invalid)

Результат:

Invalid Date

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

if (!isValid(result)) {
  console.error('Некорректная дата')
}

Ошибки несовпадения шаблона

Функция parse требует точного соответствия между строкой и шаблоном.

Пример ошибки:

parse('2024/10/15', 'dd.MM.yyyy', new Date())

Строка содержит:

2024/10/15

А шаблон ожидает:

15.10.2024

Результат:

Invalid Date

Правильный вариант:

parse('2024/10/15', 'yyyy/MM/dd', new Date())

Ошибки токенов форматирования

В date-fns используются собственные токены форматирования, отличающиеся от других библиотек.

Частая ошибка — использование:

YYYY

вместо:

yyyy

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

parse('2024-05-10', 'YYYY-MM-dd', new Date())

Правильно:

parse('2024-05-10', 'yyyy-MM-dd', new Date())

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

Ошибка Правильный токен Описание
YYYY yyyy Год
DD dd День месяца
YY yy Короткий год
A a AM/PM

Ошибки при передаче исходной даты

Третий аргумент parse — базовая дата (referenceDate).

parse(dateString, formatString, referenceDate)

Ошибка:

parse('15:30', 'HH:mm')

Будет выброшено исключение:

3 arguments required

Правильно:

parse('15:30', 'HH:mm', new Date())

Обработка исключений

Некоторые функции date-fns действительно выбрасывают ошибки. Например, при неправильном количестве аргументов.

import { format } from 'date-fns'

format()

Ошибка:

TypeError: 2 arguments required

Безопасная обработка:

try {
  const result = format()
} catch (error) {
  console.error(error.message)
}

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

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

Небезопасный код:

const result = parse(userInput, 'dd.MM.yyyy', new Date())

Без проверки возможно появление:

  • Invalid Date;
  • логических ошибок;
  • некорректных вычислений;
  • ошибок сериализации.

Безопасный вариант:

import { parse, isValid } from 'date-fns'

function parseUserDate(input) {
  const parsed = parse(input, 'dd.MM.yyyy', new Date())

  if (!isValid(parsed)) {
    return null
  }

  return parsed
}

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

const date = parseUserDate('99.99.9999')

if (!date) {
  console.log('Дата введена неверно')
}

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

Иногда дата формально валидна, но не подходит бизнес-логике.

Пример:

parse('01.01.1800', 'dd.MM.yyyy', new Date())

Дата существует, но может быть недопустимой для приложения.

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

import { isBefore, isAfter } from 'date-fns'

function isAllowedDate(date) {
  const min = new Date(2000, 0, 1)
  const max = new Date(2100, 11, 31)

  return !isBefore(date, min) && !isAfter(date, max)
}

Ошибки при ISO-парсинге

Для ISO-строк используется parseISO.

import { parseISO } from 'date-fns'

const date = parseISO('2024-05-20')

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

parseISO('20-05-2024')

Результат:

Invalid Date

Проверка:

const parsed = parseISO(input)

if (!isValid(parsed)) {
  throw new Error('Некорректный ISO формат')
}

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

Дата может быть валидной, но отображаться неверно из-за часового пояса.

Пример:

parseISO('2024-05-10T00:00:00Z')

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

Проверка:

console.log(date.toISOString())
console.log(date.toString())

Типичная ошибка:

const date = parseISO('2024-05-10')

Строка без времени интерпретируется как локальное время, а не UTC.


Ошибки при работе с локалями

Некоторые функции используют локализацию.

Пример:

import { parse } from 'date-fns'
import { ru } from 'date-fns/locale'

parse('10 мая 2024', 'dd MMMM yyyy', new Date(), {
  locale: ru
})

Без локали:

parse('10 мая 2024', 'dd MMMM yyyy', new Date())

Результат:

Invalid Date

Ошибки несуществующих дат

JavaScript автоматически корректирует некоторые значения.

Пример:

new Date(2024, 1, 31)

Результат:

Sat Mar 02 2024

Это опасное поведение.

date-fns позволяет обнаружить подобные проблемы через isValid.

const parsed = parse('31.02.2024', 'dd.MM.yyyy', new Date())

console.log(isValid(parsed))

Безопасный универсальный парсер

Практика крупных приложений — создание собственного слоя валидации.

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

import { parse, isValid } from 'date-fns'

function safeParseDate(input, formatString) {
  if (typeof input !== 'string') {
    return {
      success: false,
      error: 'Дата должна быть строкой'
    }
  }

  const parsed = parse(input, formatString, new Date())

  if (!isValid(parsed)) {
    return {
      success: false,
      error: 'Некорректный формат даты'
    }
  }

  return {
    success: true,
    value: parsed
  }
}

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

const result = safeParseDate(
  '15.10.2024',
  'dd.MM.yyyy'
)

if (!result.success) {
  console.error(result.error)
} else {
  console.log(result.value)
}

Валидация нескольких форматов

Иногда дата приходит в разных форматах.

Пример:

[
  'dd.MM.yyyy',
  'yyyy-MM-dd',
  'MM/dd/yyyy'
]

Реализация:

import { parse, isValid } from 'date-fns'

function parseMultipleFormats(input, formats) {
  for (const formatString of formats) {
    const parsed = parse(
      input,
      formatString,
      new Date()
    )

    if (isValid(parsed)) {
      return parsed
    }
  }

  return null
}

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

const result = parseMultipleFormats(
  '2024-10-15',
  [
    'dd.MM.yyyy',
    'yyyy-MM-dd',
    'MM/dd/yyyy'
  ]
)

console.log(result)

Типичные ошибки при работе с timestamp

Unix timestamp часто путают с миллисекундами.

Ошибка:

new Date(1715000000)

JavaScript ожидает миллисекунды.

Правильно:

new Date(1715000000 * 1000)

В date-fns используется fromUnixTime.

import { fromUnixTime } from 'date-fns'

const date = fromUnixTime(1715000000)

Ошибки сериализации

Некорректная дата ломает JSON-сериализацию.

Пример:

const invalid = new Date('wrong')

JSON.stringify({
  date: invalid
})

Результат:

{
  "date": null
}

Без проверки возможна потеря данных.

Безопасный вариант:

if (!isValid(date)) {
  throw new Error('Дата повреждена')
}

Ошибки сравнений

Сравнение невалидных дат приводит к непредсказуемому поведению.

Ошибка:

const date = new Date('wrong')

console.log(date > new Date())

Результат:

false

Без явной ошибки.

Безопасное сравнение:

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

Стратегии обработки ошибок

Раннее обнаружение

Проверка сразу после парсинга:

const parsed = parse(input, format, new Date())

if (!isValid(parsed)) {
  return
}

Централизованная валидация

Создание единой функции:

function validateDate(date) {
  return isValid(date)
}

Явные сообщения об ошибках

Плохой вариант:

throw new Error('Ошибка')

Хороший вариант:

throw new Error(
  'Дата должна соответствовать формату dd.MM.yyyy'
)

Разделение ошибок формата и бизнес-логики

Неверно:

if (!date) {
  throw new Error('Ошибка даты')
}

Лучше:

if (!isValid(date)) {
  throw new Error('Некорректный формат')
}

if (isBefore(date, minDate)) {
  throw new Error('Дата слишком ранняя')
}

Проверка строгого соответствия строки

Иногда parse способен интерпретировать строку неожиданным образом.

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

import {
  parse,
  format,
  isValid
} from 'date-fns'

function strictParse(input, formatString) {
  const parsed = parse(
    input,
    formatString,
    new Date()
  )

  if (!isValid(parsed)) {
    return null
  }

  const formatted = format(parsed, formatString)

  if (formatted !== input) {
    return null
  }

  return parsed
}

Проверка:

strictParse(
  '1.1.2024',
  'dd.MM.yyyy'
)

Результат:

null

Потому что ожидается:

01.01.2024

Логирование ошибок

Для диагностики полезно сохранять:

  • исходную строку;
  • формат;
  • часовой пояс;
  • источник данных;
  • сообщение ошибки.

Пример:

function logDateError(input, format, error) {
  console.error({
    input,
    format,
    error: error.message,
    timestamp: new Date().toISOString()
  })
}

Защита API от невалидных дат

При обработке HTTP-запросов дата должна проверяться до бизнес-логики.

Пример:

app.post('/events', (req, res) => {
  const parsed = parse(
    req.body.date,
    'yyyy-MM-dd',
    new Date()
  )

  if (!isValid(parsed)) {
    return res.status(400).json({
      error: 'Некорректная дата'
    })
  }

  res.sendStatus(200)
})

Проверка пустых значений

Частая ошибка:

parse('', 'dd.MM.yyyy', new Date())

Результат:

Invalid Date

Безопасная проверка:

if (!input?.trim()) {
  throw new Error('Дата не указана')
}

Обработка null и undefined

Ошибка:

parse(undefined, 'dd.MM.yyyy', new Date())

Безопасный вариант:

function ensureString(value) {
  if (typeof value !== 'string') {
    throw new TypeError(
      'Ожидается строка'
    )
  }

  return value
}

Использование TypeScript для предотвращения ошибок

Типизация уменьшает количество проблем.

function parseDate(
  input: string
): Date | null {
  const parsed = parse(
    input,
    'dd.MM.yyyy',
    new Date()
  )

  return isValid(parsed)
    ? parsed
    : null
}

Строгие типы позволяют обнаруживать ошибки ещё до выполнения приложения.


Практика defensive programming

Надёжная работа с датами строится на нескольких принципах:

  • никогда не доверять входящим данным;
  • проверять результат каждого парсинга;
  • использовать строгие форматы;
  • валидировать диапазоны;
  • отделять ошибки формата от логических ошибок;
  • централизовать обработку дат;
  • избегать неявного поведения Date;
  • логировать проблемные случаи;
  • использовать ISO-форматы при обмене данными;
  • применять строгую типизацию.