Округление длительностей

Работа с длительностями в Moment.js основана на объекте Duration, который представляет промежуток времени в миллисекундах, но предоставляет множество методов для преобразования и отображения этого интервала в разных единицах. Округление длительностей — один из ключевых аспектов, поскольку практически любые прикладные задачи (таймеры, отчёты, аналитика, UI-индикаторы времени) требуют не точных миллисекунд, а сглаженных, читаемых значений.


Базовое представление длительности и источник “неокруглённости”

Любая длительность в Moment.js создаётся через moment.duration():

const d = moment.duration(125000); // 125000 мс

Внутренне значение хранится точно, без округления. Проблема возникает при преобразовании:

  • 125000 мс = 2.08333 минуты
  • 2.08333 → требует решения: 2, 2.0, 2.1 или 3?

Moment.js не применяет автоматическое округление, оставляя это разработчику.


Методы преобразования и их поведение

milliseconds / seconds / minutes / hours

Методы извлечения времени возвращают дробные значения:

d.asSeconds(); // 125
d.asMinutes(); // 2.0833333333

Особенность: округления нет, всегда возвращается “сырой” float.

Для целых значений используются:

d.seconds(); // 5
d.minutes(); // 2

Но важно понимать:

  • minutes() — это “остаток минут”, а не общее количество
  • asMinutes() — полное значение с дробью

Округление через Math

Самый прямой способ — использовать стандартные функции Jav * aScript:

Округление вниз

Math.floor(d.asMinutes()); // 2

Используется в:

  • таймерах обратного отсчёта
  • логике прогресса
  • UI “оставшееся время”

Округление вверх

Math.ceil(d.asMinutes()); // 3

Используется в:

  • биллинге времени
  • расчётах минимальных интервалов
  • системах резервирования

Обычное округление

Math.round(d.asMinutes()); // 2

Используется:

  • в аналитике
  • при отображении “примерного времени”

Округление по единицам времени

Секунды

Math.floor(d.asSeconds());
Math.round(d.asSeconds());

Практика:

  • аудио/видео тайминги
  • логирование событий

Минуты

Math.floor(d.asMinutes());

Типичный кейс:

  • отображение “2 минуты назад”
  • UI уведомлений

Часы

Math.floor(d.asHours());

Используется:

  • длительные процессы
  • отчёты задач

Проблема накопления ошибок при цепочках

При последовательных преобразованиях легко получить дрейф:

const hours = Math.floor(d.asHours());
const minutes = Math.floor(d.asMinutes());

Ошибка: minutes содержит общее количество минут, а не остаток после часов.

Правильный подход:

const hours = Math.floor(d.asHours());
const minutes = Math.floor(d.asMinutes()) % 60;

Округление с использованием remainder-логики

Для точного представления длительности:

const d = moment.duration(3675, 'seconds');

const hours = Math.floor(d.asHours());
const minutes = Math.floor(d.asMinutes()) % 60;
const seconds = Math.floor(d.asSeconds()) % 60;

Это позволяет избежать накопления дробей и корректно разложить время.


Округление через преобразование в объект

Moment.js позволяет получить структуру:

d.hours();
d.minutes();
d.seconds();

Но важно:

  • это НЕ общее время
  • это разложение внутри суток (остатки)

Поэтому часто комбинируется с asX() и Math.


Формирование “человеческого” округления

Базовая логика

function formatDuration(d) {
  const minutes = Math.floor(d.asMinutes());

  if (minutes < 60) {
    return `${minutes} мин`;
  }

  const hours = Math.floor(d.asHours());
  const restMinutes = minutes % 60;

  return `${hours} ч ${restMinutes} мин`;
}

Округление до ближайшего значимого интервала

function smartRound(duration) {
  const minutes = duration.asMinutes();

  if (minutes < 1) return "меньше минуты";
  if (minutes < 60) return Math.round(minutes) + " мин";

  return Math.round(duration.asHours()) + " ч";
}

Особенности округления при отрицательных длительностях

Moment.js поддерживает отрицательные durations:

const d = moment.duration(-125000);

Поведение Math:

  • Math.floor(-2.1) = -3
  • Math.round(-2.1) = -2

Это часто приводит к логическим ошибкам.

Корректный подход:

const minutes = Math.abs(d.asMinutes());

или явная нормализация:

const sign = d.asMilliseconds() < 0 ? -1 : 1;
const minutes = Math.floor(Math.abs(d.asMinutes())) * sign;

Округление при отображении “humanize”

d.humanize();

Этот метод:

  • не даёт точного контроля
  • использует внутреннюю логику округления
  • зависит от локали

Пример:

  • 1.4 минуты → “a minute”
  • 1.6 минуты → “2 minutes”

Это автоматическое округление, не настраиваемое напрямую.


Проблемы точности при дробных секундах

При работе с миллисекундами:

const d = moment.duration(1999);
Math.floor(d.asSeconds()); // 1
Math.round(d.asSeconds()); // 2

Разница критична для:

  • измерения latency
  • performance метрик
  • аудио синхронизации

Рекомендации по выбору стратегии округления

Когда использовать floor

  • таймеры
  • оставшееся время
  • прогресс-бар

Когда использовать round

  • пользовательский интерфейс
  • отчёты
  • аналитика

Когда использовать ceil

  • биллинг
  • минимальные гарантированные интервалы
  • резервирование ресурсов

Типичные ошибки при округлении

Ошибка 1: смешивание asMinutes и minutes

d.minutes();     // остаток
d.asMinutes();   // общее значение

Ошибка 2: двойное округление

Math.round(Math.round(d.asMinutes()));

Ошибка 3: игнорирование миллисекунд

Math.floor(d.asSeconds());

без учёта дробных значений при расчётах UI может давать скачки.


Работа с кастомным округлением

Иногда требуется нестандартная логика:

Округление до 5 минут

function roundToFive(mins) {
  return Math.round(mins / 5) * 5;
}

Округление до 15 минут (кварталы часа)

function roundToQuarter(mins) {
  return Math.round(mins / 15) * 15;
}

Применение к duration

const rounded = roundToQuarter(d.asMinutes());

Итоговая модель мышления при работе с длительностями

При работе с длительностями в Moment.js важно разделять три уровня:

  • сырое значение (milliseconds) — точность
  • asX() значения — математическая основа
  • округлённые значения — представление для UI

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