Парсинг с учетом локали

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

Примеры:

  • 04/05/2025

    • США: 5 апреля
    • Европа: 4 мая
  • 1 février 2025

    • корректно читается только при поддержке французской локали
  • понедельник, 7 октября 2024

    • требует понимания русских названий месяцев и дней недели

Библиотека date-fns предоставляет механизм локализованного парсинга через модуль локалей и функцию parse.


Подключение локалей

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

import { parse } from 'date-fns'
import { ru, enUS, fr, de } from 'date-fns/locale'

Каждая локаль содержит:

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

Функция parse

Сигнатура:

parse(dateString, formatString, referenceDate, options)

Аргументы:

Аргумент Назначение
dateString строка с датой
formatString шаблон разбора
referenceDate базовая дата
options дополнительные настройки

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


Парсинг русских дат

Разбор полного названия месяца

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

const result = parse(
  '15 января 2025',
  'd MMMM yyyy',
  new Date(),
  { locale: ru }
)

console.log(result)

Шаблон:

Токен Значение
d день месяца
MMMM полное название месяца
yyyy год

Парсинг сокращенных месяцев

const result = parse(
  '10 фев 2025',
  'd MMM yyyy',
  new Date(),
  { locale: ru }
)

Здесь:

  • MMM — сокращенное название месяца;
  • MMMM — полное название месяца.

Парсинг дней недели

const result = parse(
  'понедельник, 15 января 2025',
  'EEEE, d MMMM yyyy',
  new Date(),
  { locale: ru }
)

Токен EEEE соответствует полному названию дня недели.

Сокращенные варианты:

Токен Пример
E пн
EE пн
EEE пн
EEEE понедельник

Английская локаль

import { enUS } from 'date-fns/locale'

const result = parse(
  'January 15, 2025',
  'MMMM d, yyyy',
  new Date(),
  { locale: enUS }
)

Французская локаль

import { fr } from 'date-fns/locale'

const result = parse(
  '15 février 2025',
  'd MMMM yyyy',
  new Date(),
  { locale: fr }
)

Без передачи locale: fr парсинг завершится ошибкой.


Немецкая локаль

import { de } from 'date-fns/locale'

const result = parse(
  '15 März 2025',
  'd MMMM yyyy',
  new Date(),
  { locale: de }
)

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

parse требует базовую дату даже тогда, когда все части даты присутствуют в строке.

parse(
  '15 января 2025',
  'd MMMM yyyy',
  new Date(),
  { locale: ru }
)

Если часть данных отсутствует, значения берутся из referenceDate.

Пример:

const result = parse(
  '15 января',
  'd MMMM',
  new Date(2020, 5, 10),
  { locale: ru }
)

Результат:

2020-01-15

Год был взят из referenceDate.


Парсинг времени с локалью

const result = parse(
  '15 января 2025 18:30',
  'd MMMM yyyy HH:mm',
  new Date(),
  { locale: ru }
)

Парсинг AM/PM

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

const result = parse(
  'January 15, 2025 08:30 PM',
  'MMMM d, yyyy hh:mm a',
  new Date(),
  { locale: enUS }
)

Токены:

Токен Значение
hh часы 01–12
HH часы 00–23
a AM/PM

Локализованные шаблоны P и p

Date-fns поддерживает специальные локализованные токены.

Токен P

parse(
  '15.01.2025',
  'P',
  new Date(),
  { locale: ru }
)

Для разных локалей P означает разные шаблоны.

Например:

Локаль Интерпретация
ru dd.MM.yyyy
enUS MM/dd/yyyy
de dd.MM.yyyy

Токен PP

parse(
  '15 янв. 2025 г.',
  'PP',
  new Date(),
  { locale: ru }
)

Токен PPP

parse(
  '15 января 2025 г.',
  'PPP',
  new Date(),
  { locale: ru }
)

Токен PPPP

parse(
  'среда, 15 января 2025 г.',
  'PPPP',
  new Date(),
  { locale: ru }
)

Локализованное время

Токены времени:

Токен Назначение
p локализованное время
pp расширенное время
ppp время с дополнительными деталями

Пример:

parse(
  '15.01.2025, 18:45',
  'Pp',
  new Date(),
  { locale: ru }
)

Комбинирование локализованных токенов

parse(
  'Wednesday, January 15th, 2025 at 8:30 PM',
  'PPPP p',
  new Date(),
  { locale: enUS }
)

Отличие parse от Date.parse

Date.parse

Date.parse('15 января 2025')

Проблемы:

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

parse из date-fns

parse(
  '15 января 2025',
  'd MMMM yyyy',
  new Date(),
  { locale: ru }
)

Преимущества:

  • предсказуемый результат;
  • строгий шаблон;
  • поддержка локалей;
  • одинаковое поведение во всех средах.

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

После парсинга желательно проверять корректность даты.

import { isValid } from 'date-fns'

const result = parse(
  '35 января 2025',
  'd MMMM yyyy',
  new Date(),
  { locale: ru }
)

console.log(isValid(result))

Результат:

false

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

Неверный месяц

parse(
  '15 unknown 2025',
  'd MMMM yyyy',
  new Date(),
  { locale: enUS }
)

Результат:

Invalid Date

Несовпадение формата

parse(
  '2025-01-15',
  'd MMMM yyyy',
  new Date(),
  { locale: ru }
)

Шаблон не соответствует строке.


Строгий контроль формата

Date-fns не пытается «угадывать» формат.

Это принципиальное отличие от многих библиотек.

Строка:

15/01/2025

Не будет разобрана шаблоном:

MM/dd/yyyy

Парсинг ISO совместно с локалями

Локаль обычно не нужна для ISO-форматов.

parse(
  '2025-01-15',
  'yyyy-MM-dd',
  new Date()
)

Но локаль можно комбинировать при смешанных форматах.

parse(
  '15 janvier 2025 18:00',
  'd MMMM yyyy HH:mm',
  new Date(),
  { locale: fr }
)

Динамический выбор локали

Часто локаль выбирается на основе языка интерфейса.

import { ru, enUS, de } from 'date-fns/locale'

const locales = {
  ru,
  en: enUS,
  de
}

const currentLocale = locales[userLanguage]

const result = parse(
  input,
  format,
  new Date(),
  { locale: currentLocale }
)

Ленивая загрузка локалей

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

async function loadLocale(lang) {
  switch (lang) {
    case 'ru':
      return (await import('date-fns/locale/ru')).ru

    case 'fr':
      return (await import('date-fns/locale/fr')).fr

    default:
      return (await import('date-fns/locale/en-US')).enUS
  }
}

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

Типичный сценарий — ввод даты в форме.

function parseUserDate(value, locale) {
  return parse(
    value,
    'P',
    new Date(),
    { locale }
  )
}

Преимущество:

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

Поддержка интернационализации

Date-fns удобно интегрируется с i18n-системами:

  • i18next;
  • react-intl;
  • vue-i18n;
  • Angular i18n.

Пример:

const localeMap = {
  ru: ru,
  en: enUS,
  fr: fr
}

const locale = localeMap[i18n.language]

Частые ошибки при локализованном парсинге

Отсутствие locale

parse(
  '15 janvier 2025',
  'd MMMM yyyy',
  new Date()
)

Французский месяц не распознается.


Неправильный токен

'M'    // число месяца
'MMM'  // краткое название
'MMMM' // полное название

Использование YYYY вместо yyyy

Правильно:

yyyy

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

YYYY

В Date-fns эти токены имеют разный смысл.


Практический пример мультиязычного парсинга

import { parse, isValid } from 'date-fns'
import { ru, enUS, fr } from 'date-fns/locale'

const localeMap = {
  ru,
  en: enUS,
  fr
}

function parseLocalizedDate(value, lang) {
  const locale = localeMap[lang]

  const date = parse(
    value,
    'P',
    new Date(),
    { locale }
  )

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

  return date
}

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

parseLocalizedDate('15.01.2025', 'ru')
parseLocalizedDate('01/15/2025', 'en')
parseLocalizedDate('15/01/2025', 'fr')

Сравнение локализованных форматов

Локаль Пример
ru 15.01.2025
enUS 01/15/2025
de 15.01.2025
fr 15/01/2025

Поддерживаемые локали

Библиотека включает десятки локалей:

  • русский;
  • английский;
  • французский;
  • немецкий;
  • испанский;
  • итальянский;
  • японский;
  • китайский;
  • корейский;
  • украинский;
  • польский;
  • турецкий;
  • арабский и многие другие.

Каждая локаль импортируется отдельно:

import { ja } from 'date-fns/locale'
import { zhCN } from 'date-fns/locale'
import { uk } from 'date-fns/locale'

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

Особенности Date-fns:

  • отсутствие глобального состояния;
  • модульная архитектура;
  • tree-shaking;
  • отдельная загрузка локалей;
  • минимальное влияние на размер сборки.

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

import parse from 'date-fns/parse'

Совместимость с TypeScript

import { Locale } from 'date-fns'

function parseDate(
  value: string,
  locale: Locale
): Date {
  return parse(
    value,
    'P',
    new Date(),
    { locale }
  )
}

Создание универсального парсера

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

export function safeParse(
  value,
  format,
  locale
) {
  const result = parse(
    value,
    format,
    new Date(),
    { locale }
  )

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

  return result
}

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

safeParse(
  '15 января 2025',
  'd MMMM yyyy',
  ru
)