Округление до часов, дней

date-fns предоставляет набор функций для работы с датами, где операции округления реализуются не как единая универсальная функция, а через комбинацию базовых примитивов: усечение, добавление единиц времени и вычисление разницы между датами. Такой подход обеспечивает предсказуемость поведения и минимизирует скрытую сложность при работе с временными зонами и переходами на летнее/зимнее время.

Округление даты в контексте date-fns обычно сводится к трём стратегиям:

  • Округление вниз (floor) — приведение к началу интервала
  • Округление вверх (ceil) — приведение к концу или следующему интервалу
  • Округление к ближайшему значению (round) — выбор ближайшей границы интервала

В отличие от числовых типов, даты имеют сложную структуру: часы зависят от дней, дни от месяцев, а месяцы от календарных правил. Поэтому округление реализуется через специализированные функции.

Округление до часов

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

Начало и конец часа

Основные инструменты:

  • startOfHour
  • endOfHour

startOfHour приводит дату к началу текущего часа, обнуляя минуты, секунды и миллисекунды.

import { startOfHour } from 'date-fns';

startOfHour(new Date('2026-05-22T14:37:45'));
// 2026-05-22T14:00:00.000

endOfHour устанавливает дату на последний момент текущего часа:

import { endOfHour } from 'date-fns';

endOfHour(new Date('2026-05-22T14:37:45'));
// 2026-05-22T14:59:59.999

Эти функции фактически реализуют два крайних варианта округления: вниз и вверх относительно часа.

Ручное округление до ближайшего часа

В date-fns нет универсальной встроенной функции «round to nearest hour», поэтому применяется комбинация вычислений.

Алгоритм:

  1. Получение количества минут
  2. Сравнение с порогом 30 минут
  3. Смещение к началу часа или следующему часу
import { startOfHour, addHours } from 'date-fns';

function roundToNearestHour(date) {
  const minutes = date.getMinutes();
  const base = startOfHour(date);

  return minutes >= 30
    ? addHours(base, 1)
    : base;
}

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

Альтернативное округление через секунды

Иногда применяется более точная модель через миллисекунды:

function roundToNearestHour(date) {
  const msInHour = 3600000;
  return new Date(Math.round(date.getTime() / msInHour) * msInHour);
}

Такой подход быстрее, но может давать неожиданные эффекты при переходах DST.

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

Работа с днями проще концептуально, но сложнее из-за календарных особенностей.

Начало и конец дня

Основные функции:

  • startOfDay
  • endOfDay
import { startOfDay, endOfDay } from 'date-fns';

startOfDay(new Date('2026-05-22T14:37:45'));
// 2026-05-22T00:00:00.000

endOfDay(new Date('2026-05-22T14:37:45'));
// 2026-05-22T23:59:59.999

Эти операции часто используются как базовые элементы округления дат в бизнес-логике: отчёты, фильтры, агрегации.

Округление к ближайшему дню

Поскольку встроенного «roundToNearestDay» нет, используется сравнение с серединой суток.

import { startOfDay, addDays } from 'date-fns';

function roundToNearestDay(date) {
  const start = startOfDay(date);
  const diff = date - start;

  const halfDay = 12 * 60 * 60 * 1000;

  return diff >= halfDay
    ? addDays(start, 1)
    : start;
}

Логика опирается на середину суток как порог округления.

Округление через разницу в днях

Другой подход использует вычисление разницы и последующее добавление:

import { differenceInDays, addDays, startOfDay } from 'date-fns';

function roundByDays(base, target) {
  const start = startOfDay(base);
  const diff = differenceInDays(target, start);

  return addDays(start, diff);
}

Такой метод используется при нормализации дат относительно базовой точки.

Поведение при временных зонах и переходе на летнее время

Округление времени осложняется особенностями временных зон:

  • сутки могут содержать 23 или 25 часов
  • часовые интервалы не всегда равномерны
  • локальное время может «перескакивать»

Функции startOfHour и startOfDay учитывают эти особенности через встроенный механизм JavaScript Date, что снижает риск некорректных вычислений.

Однако ручное округление через getTime() требует осторожности:

  • возможны смещения на переходах DST
  • арифметика в миллисекундах не отражает календарную структуру

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

В date-fns округление часто комбинируется с фильтрацией и агрегацией данных.

Пример нормализации массива дат

import { startOfHour } from 'date-fns';

const normalized = dates.map(startOfHour);

Группировка по дням

import { startOfDay } from 'date-fns';

function groupByDay(dates) {
  return dates.reduce((acc, date) => {
    const key = startOfDay(date).toISOString();
    acc[key] = acc[key] || [];
    acc[key].push(date);
    return acc;
  }, {});
}

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

В реальных системах используются три устойчивые модели:

Усечение для хранения

Используется при сохранении логов и событий:

startOfHour(date)
startOfDay(date)

Округление для отображения

Используется в UI и отчетах:

  • ближайший час
  • ближайший день

Границы интервалов для фильтрации

startOfDay(date) → endOfDay(date)

Такой подход позволяет строить запросы диапазонов без потери точности.

Особенности точности и миллисекунд

Функции startOfHour и startOfDay всегда сбрасывают:

  • минуты
  • секунды
  • миллисекунды

Это важно при сравнении дат:

startOfDay(a).getTime() === startOfDay(b).getTime()

Такое сравнение часто используется вместо прямого Date-сравнения.

Выбор стратегии округления

Выбор метода зависит от задачи:

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

date-fns предоставляет базовые строительные блоки, а логика округления формируется на уровне приложения.