Особенности парсинга дат

Библиотека timeago.js не реализует собственный парсер дат. Вместо этого она делегирует преобразование строк нативному конструктору new Date(). Это решение упрощает библиотеку и уменьшает её размер, но переносит ответственность за правильность строк на вызывающий код.


Внутренний механизм преобразования

Внутри функции format входное значение приводится примерно так:

function toDate(input) {
  if (input instanceof Date) return input;
  return new Date(input); // для строк и чисел
}

Это означает: любой аргумент, который new Date() преобразует в валидную дату, будет работать. Если new Date(input) возвращает Invalid Date, результат форматирования будет непредсказуемым.


Проверка через new Date()

Перед передачей строки в format можно проверить её валидность:

const str = '2025-05-26T10:00:00Z';
const date = new Date(str);

console.log(isNaN(date.getTime())); // false — валидная дата

Если результат true — строка невалидна и не должна передаваться в format.


ISO 8601 парсится однозначно

Единственный формат, гарантированно стандартизированный в ECMAScript — ISO 8601:

YYYY-MM-DDTHH:mm:ss.sssZ

Примеры:

new Date('2025-05-26T10:00:00Z');        // ок
new Date('2025-05-26T10:00:00.000Z');    // ок
new Date('2025-05-26T10:00:00+03:00');   // ок

Все остальные строковые форматы оставлены на усмотрение реализации JavaScript-движка.


Неоднозначность строки “YYYY-MM-DD”

ECMAScript определяет, что строки формата YYYY-MM-DD без времени интерпретируются как UTC-полночь:

new Date('2025-05-26').toISOString();
// → "2025-05-26T00:00:00.000Z"

Если пользователь находится в часовом поясе UTC+3, то 2025-05-26 будет ощущаться как “вчера в 03:00 по местному времени” — не как “сегодня”.

Строки формата YYYY-MM-DDTHH:mm:ss без суффикса в большинстве современных браузеров интерпретируются как локальное время:

new Date('2025-05-26T00:00:00').getTime();
// разное значение в разных часовых поясах

Поведение в разных браузерах

Формат строки Chrome Firefox Safari Node.js
2025-05-26T10:00:00Z UTC UTC UTC UTC
2025-05-26T10:00:00 Local Local Может отличаться Local
2025-05-26 UTC UTC UTC UTC
2025/05/26 Local Local Ошибка в старых версиях Local
May 26 2025 Local Local Local Local

Safari исторически имеет проблемы с нестандартными форматами. Для кросс-браузерной совместимости следует использовать только ISO 8601 с явным UTC.


Парсинг дат в Node.js vs браузере

Node.js и браузеры используют разные движки JavaScript. Поведение парсинга нестандартных строк может отличаться:

// В браузере Chrome
new Date('2025.05.26'); // Valid Date

// В Node.js
new Date('2025.05.26'); // Invalid Date

Для кода, который выполняется и на сервере, и в браузере (SSR), это критически важно.


Проблема с часовыми поясами при парсинге

// UTC: 2025-05-26T10:00:00.000Z
// Если клиент в UTC+3, локальное время: 13:00:00

const utcDate = new Date('2025-05-26T10:00:00Z');
const diff = Date.now() - utcDate.getTime();

// Разница корректна — оба значения в UTC

Опасный вариант — передача строки без часового пояса и последующее несоответствие:

// Сервер сохранил "2025-05-26 10:00:00" как LOCAL время
// Клиент интерпретирует её как LOCAL time клиента
// Если у сервера и клиента разные часовые пояса — ошибка

Нормализация строк перед парсингом

Для строк с нестандартным форматом рекомендуется нормализация:

function normalizeDate(str) {
  // DD.MM.YYYY → YYYY-MM-DDTHH:mm:ssZ
  const match = str.match(/^(\d{2})\.(\d{2})\.(\d{4})$/);
  if (match) {
    return `${match[3]}-${match[2]}-${match[1]}T00:00:00Z`;
  }
  return str;
}

import { format } from 'timeago.js';

format(normalizeDate('26.05.2025'), 'ru');

Безопасный враппер с валидацией

import { format } from 'timeago.js';

function safeParse(input, locale = 'ru') {
  let date;

  if (input instanceof Date) {
    date = input;
  } else if (typeof input === 'number') {
    date = new Date(input);
  } else if (typeof input === 'string') {
    date = new Date(input);
  } else {
    return '';
  }

  if (isNaN(date.getTime())) {
    console.warn('[timeago] Invalid date:', input);
    return '';
  }

  return format(date, locale);
}

Парсинг временных зон без библиотек

Если строки всегда в UTC, но без суффикса Z:

const str = '2025-05-26T10:00:00'; // предположительно UTC

// Принудительно добавить Z
const normalized = str.endsWith('Z') ? str : str + 'Z';

format(normalized, 'ru');

Когда использовать внешний парсер

Если формат строк нестандартен или приходит из пользовательского ввода — рекомендуется использовать специализированные парсеры:

  • date-fns/parseISO — строгий парсинг ISO
  • date-fns/parse — парсинг по шаблону
  • dayjs(str) — гибкий парсинг
  • luxon.DateTime.fromFormat(str, fmt) — строгий шаблонный парсинг
import { parse } from 'date-fns';
import { format as timeagoFormat } from 'timeago.js';

const date = parse('26.05.2025', 'dd.MM.yyyy', new Date());

timeagoFormat(date, 'ru');

Такой подход разделяет ответственность: date-fns отвечает за парсинг, timeago.js — за форматирование.