Некорректная обработка дат — наиболее частая причина неверного вывода timeago.js. Проблемы возникают при парсинге строк, временных зонах, граничных значениях и типах входных данных.
import { format } from 'timeago.js';
format('2025-06-01T12:00:00', 'ru');
Строка без Z или +HH:MM парсится браузером
как локальное время. На машине с UTC+3 это
означает:
'2025-06-01T12:00:00' → 2025-06-01T09:00:00Z
Если сервер отдаёт UTC, разница составит 3 часа. Пользователь видит неверное “относительное время”.
Решение:
// Вариант 1 — нормализация на клиенте
function normalizeDate(str) {
if (str && str.includes('T') && !str.endsWith('Z') && !/[+\-]\d{2}:\d{2}$/.test(str)) {
return str + 'Z';
}
return str;
}
format(normalizeDate(post.createdAt), 'ru');
// Вариант 2 — отдавать ISO с Z на сервере
// "2025-06-01T12:00:00.000Z"
format('01.06.2025', 'ru'); // Неверно — формат ДД.ММ.ГГГГ
format('June 1, 2025', 'ru'); // Зависит от движка
format('2025/06/01', 'ru'); // Работает, но нестандартно
Надёжный формат — ISO 8601:
format('2025-06-01', 'ru'); // Только дата (время = 00:00:00 UTC или локально)
format('2025-06-01T12:00:00Z', 'ru'); // Дата и время в UTC
format('2025-06-01T12:00:00+03:00', 'ru'); // С явным offset
Большинство бэкендов на Python/PHP возвращают timestamp в секундах:
const unixSeconds = 1748779200; // June 1, 2025 в секундах
format(unixSeconds, 'ru'); // Ошибка! Дата будет в 1970 году
Решение:
function fromUnixSeconds(ts) {
return new Date(ts * 1000);
}
format(fromUnixSeconds(post.createdAt), 'ru');
// Если часы сервера и браузера расхождаться более чем на несколько секунд
const serverTime = '2025-06-01T12:00:05Z'; // Сервер
const clientNow = '2025-06-01T12:00:00Z'; // Клиент — отстаёт на 5 секунд
// format покажет "через 5 секунд" — правильно
// Но при рассинхронизации часов разница может быть неожиданной
Решение с relativeDate:
// Если сервер отдаёт текущее время — использовать его как точку отсчёта
const serverNow = response.headers['x-server-time'];
format(post.createdAt, 'ru', {
relativeDate: new Date(serverNow),
});
const date = new Date('not a date');
format(date, 'ru'); // Не бросает исключение — возвращает "NaN years ago" или похожее
Проверка перед вызовом:
function safeFormat(date, locale = 'ru') {
const d = new Date(date);
return isNaN(d.getTime()) ? '' : format(d, locale);
}
const timestamp = apiResponse.date; // Может быть null, если дата не задана
format(timestamp, 'ru'); // TypeError или неверный результат
format(timestamp ?? new Date(), 'ru'); // Fallback на текущее время
// Или
if (timestamp) format(timestamp, 'ru');
format(new Date('1970-01-01'), 'ru'); // "55 лет назад"
Для очень старых дат возвращается корректный результат, но число может быть неожиданно большим. Если нужно ограничить:
function boundedFormat(date, locale = 'ru', maxYears = 10) {
const d = new Date(date);
const ageMs = Date.now() - d.getTime();
const ageYrs = ageMs / (365.25 * 86400 * 1000);
if (ageYrs > maxYears) {
return d.toLocaleDateString('ru-RU');
}
return format(d, locale);
}
При использовании iframe или Shadow DOM Date из другого
контекста не является экземпляром текущего Date:
const iframeDate = iframe.contentWindow.eval('new Date()');
iframeDate instanceof Date; // false в некоторых браузерах
Решение:
// Преобразовать через timestamp
format(iframeDate.getTime(), 'ru');
function diagnoseDate(input) {
const d = new Date(input);
return {
input,
isValid: !isNaN(d.getTime()),
utcString: isNaN(d.getTime()) ? null : d.toISOString(),
localString: isNaN(d.getTime()) ? null : d.toLocaleString('ru-RU'),
timestamp: isNaN(d.getTime()) ? null : d.getTime(),
type: typeof input,
isDate: input instanceof Date,
};
}
console.log(diagnoseDate('01.06.2025'));
console.log(diagnoseDate('2025-06-01T12:00:00'));
console.log(diagnoseDate('2025-06-01T12:00:00Z'));
Z или явный offset?null перед вызовом
format?isNaN(new Date(value).getTime())?