Если вы уже используете новый Intl.DurationFormat в совсем проекте, то вам будет полезен мой кейс и поможет вам сэкономить пару часов на дебагинг.
Продукт над которым я работаю - это платежная форма. Это стабильное давно работающее приложение и вдруг пользователи стали сообщать об «Unknown error» при попытке провести транзакцию. При этом проблема была только для одного вида транзакций — «Счет».
Сначала проверили бэкенд, там все запросы проходят корректно, я стала проверять фронт и к моему удивлению никаких особенностей для этого типа транзакций добавлено не было и в кодовой базе она ничем не отличалась от обработки других транзакций.
Если коротко, платежная форма состоит из инпутов, таймера обратного отсчета и кнопок. Соответственно, подозрения пали на работу таймера в первую очередь. После сравнения данных транзакции для успешных и выдающих ошибку, паттерн обнаружился в данных бэкенда:
{
"transaction": {
"id": 111111111,
"status": "Pending",
"createdAt": "2026-05-11T08:39:15+00:00",
"expiresAt": "2026-07-01T08:39:15+00:00"
}
}
expiresAt — июль, а сейчас май. Так, срок жизни транзакции типа «Счет» ~55 дней, в то время как все другие транзакции живут не больше 15 минут. Дело в том, что банковские переводы ждут подтверждения от банка и могут висеть несколько дней. Именно поэтому баг проявлялся только для этого типа — остальные работали корректно.
У нас в проекте для таймера обратного отсчёта используется компонент TextTimer с полифиллом formatjs/intl-durationformat@ 0.7.4. Полифилл подключён в корне приложения глобально, поэтому нативная реализация браузера не задействована.
Код до фикса:
const formatter = new Intl.DurationFormat(i18n.locale, {
hoursDisplay: "auto",
secondsDisplay: "always",
style: formatStyle,
});
function getDiff(unit: dayjs.OpUnitType) {
let diff = expiresAtDayjs.diff(now, unit);
if (unit === "m" || unit === "s") {
diff %= 60;
}
return Math.max(0, diff);
}
formatter.format({
hours: getDiff("h"), // = 1320
minutes: getDiff("m"),
seconds: getDiff("s"),
}); // 💥 RangeError
Так в чем же проблема?
Дело в том, что если в таймер приходит длительность больше суток, а в моем случае это было 55 дней, getDiff(“h”) возвращает 1320 — потому что для часов не было % 24, и days не передавался в .format().
Полифилл видел 1320 часов, Intl.DurationFormat не понимал как обработать такой период и выбрасывал RangeError. Ошибка всплывала наверх без ErrorBoundary на компоненте, отсюда «Unknown error» вместо формы оплаты.
Решение
Два изменения:
Добавить % 24 для часов в getDiff
Передать days в .format()
const formatter = new Intl.DurationFormat(i18n.locale, {
hoursDisplay: "auto",
secondsDisplay: "always",
style: formatStyle,
});
function getDiff(unit: dayjs.OpUnitType) {
let diff = expiresAtDayjs.diff(now, unit);
if (unit === "h") {
diff %= 24; // ← добавили
} else if (unit === "m" || unit === "s") {
diff %= 60;
}
return Math.max(0, diff);
}
formatter.format({
days: getDiff("d"), // ← добавили
hours: getDiff("h"), // теперь 1320 % 24 = 8 ✓
minutes: getDiff("m"),
seconds: getDiff("s"),
});
Теперь пользователь видит 55 дн. 10:05:00 (RU) или 55d 10:05:00 (EN) вместо ошибки.
А что если бы был ErrorBoundary?
Поскольку таймер — вспомогательный элемент, он показывает сколько транзакция будет открыта, но не блокирует оплату. Если обернуть его в предохранитель, падение таймера не будет блокировать работу платежной формы.
class TimerErrorBoundary extends React.Component {
componentDidCatch(error: Error) {
logger.error('TextTimer crashed', { error }); // обязательно!
}
render() {
if (this.state.hasError) return null;
return this.props.children;
}
}
Главное — внутри ErrorBoundary всегда нужно логировать ошибку, иначе баг останется невидимым на проде. Для логирования подойдёт любой мониторинг ошибок, например, Sentry, Datadog, или собственный logger если он есть в проекте.
Также стоит понимать, что обернуть в ErrorBoundary для того, чтобы ошибка не блокировала весь компонент можно только второстепенные компоненты. То, что отвечает за вывод суммы, реквизиты и кнопка оплаты должны падать громко. ErrorBoundary подходит только для элементов, без которых оплата всё равно возможна.
Выводы
Полифилл formatjs/intl-durationformat@0.7.4 отдает RangeError, если передать в негоhours ≥ 24 без days. Нет предупреждений — сразу исключение.
ErrorBoundary на уровне компонента, не страницы. Оборачивайте некритичные элементы - и всегда с логированием в мониторинг.



















