Округление дат: startOfDay, endOfDay

В работе с датами в JavaScript часто возникает необходимость привести момент времени к границам суток: началу или концу дня. Это требуется при фильтрации записей по дате, построении диапазонов, агрегации событий, работе с календарями и аналитикой.

В библиотеке date-fns для этих задач предусмотрены функции startOfDay и endOfDay, обеспечивающие предсказуемое и неизменяемое преобразование объектов Date.


startOfDay: приведение даты к началу суток

Функция startOfDay возвращает новый объект Date, установленный на 00:00:00.000 указанного дня.

Сигнатура

startOfDay(date: Date | number): Date
  • date — исходная дата (объект Date или timestamp)
  • возвращает новый объект Date

Поведение

startOfDay обнуляет все временные компоненты:

  • часы → 0
  • минуты → 0
  • секунды → 0
  • миллисекунды → 0

При этом календарная дата сохраняется без изменений.

Пример использования

import { startOfDay } from 'date-fns';

const date = new Date('2026-05-22T15:45:30.500');

const result = startOfDay(date);

console.log(result.toISOString());
// 2026-05-22T00:00:00.000Z (в UTC-отображении)

Особенности работы

  • функция не мутирует исходный объект
  • всегда возвращается новый экземпляр Date
  • учитывается локальная временная зона окружения выполнения

endOfDay: приведение даты к концу суток

Функция endOfDay устанавливает время на 23:59:59.999, что соответствует последнему возможному моменту дня в пределах миллисекундной точности JavaScript.

Сигнатура

endOfDay(date: Date | number): Date
  • date — исходная дата
  • возвращает новый объект Date

Поведение

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

  • часы → 23
  • минуты → 59
  • секунды → 59
  • миллисекунды → 999

Пример использования

import { endOfDay } from 'date-fns';

const date = new Date('2026-05-22T08:10:00');

const result = endOfDay(date);

console.log(result.toISOString());
// 2026-05-22T23:59:59.999Z (в UTC-отображении)

Использование startOfDay и endOfDay в диапазонах

Наиболее частый сценарий — формирование диапазона суток для фильтрации данных.

Пример: фильтрация событий за день

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

const targetDate = new Date('2026-05-22T12:00:00');

const start = startOfDay(targetDate);
const end = endOfDay(targetDate);

const events = [
  { name: 'A', date: new Date('2026-05-22T01:00:00') },
  { name: 'B', date: new Date('2026-05-22T23:00:00') },
  { name: 'C', date: new Date('2026-05-23T00:00:00') }
];

const filtered = events.filter(e =>
  e.date >= start && e.date <= end
);

В результате останутся только события внутри одного календарного дня.


Важные особенности работы с временными зонами

JavaScript Date всегда хранит время в UTC, но отображение зависит от локальной временной зоны.

Что это означает на практике

  • startOfDay устанавливает 00:00 в локальной зоне
  • при выводе через toISOString() время может смещаться в UTC
  • визуально «начало дня» может отличаться от UTC-нулевой отметки

Пример смещения

Если локальная зона UTC+3:

const d = new Date('2026-05-22T00:00:00');

startOfDay(d).toISOString();

Результат может выглядеть как:

2026-05-21T21:00:00.000Z

Это не ошибка, а следствие преобразования локального времени в UTC.


Поведение с числовыми timestamp

Обе функции поддерживают передачу времени в виде числового значения:

import { startOfDay } from 'date-fns';

const timestamp = 1789833600000;

const result = startOfDay(timestamp);

console.log(result);

Это упрощает работу с данными из API и баз данных, где даты часто представлены Unix-временем.


Иммутабельность и безопасность преобразований

startOfDay и endOfDay:

  • не изменяют входной объект
  • создают новый экземпляр Date
  • безопасны для повторного использования входных данных

Пример проверки иммутабельности

const original = new Date('2026-05-22T10:00:00');

const result = startOfDay(original);

console.log(original.toISOString());
console.log(result.toISOString());

Оригинальная дата остаётся неизменной.


Типичные сценарии применения

1. Группировка данных по дням

const dayKey = startOfDay(item.date).getTime();

2. Проверка попадания в сутки

const isSameDay = (a, b) =>
  startOfDay(a).getTime() === startOfDay(b).getTime();

3. Построение диапазонов запросов

const range = {
  from: startOfDay(new Date()),
  to: endOfDay(new Date())
};

Особенности при переходе через границы суток

При работе с временными данными важно учитывать:

  • переходы через полночь
  • различия локальных зон
  • возможные DST-сдвиги (летнее/зимнее время)

endOfDay всегда вычисляется как максимально возможное локальное время суток, а не фиксированное UTC-значение.


Сочетание с другими функциями date-fns

startOfDay и endOfDay часто используются совместно с:

  • isWithinInterval
  • format
  • addDays
  • subDays

Пример:

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

const date = new Date('2026-05-22T14:00:00');

const result = isWithinInterval(date, {
  start: startOfDay(date),
  end: endOfDay(date)
});

Производительность и предсказуемость

Обе функции:

  • имеют O(1) сложность
  • не зависят от внешних данных
  • выполняют только локальные вычисления полей даты
  • подходят для массовой обработки (фильтрации больших массивов дат)

Отличие от ручной установки через setHours

Аналог без библиотеки:

date.setHours(0, 0, 0, 0);

Недостатки ручного подхода:

  • мутирует исходный объект
  • менее читаем в цепочках обработки
  • повышает риск побочных эффектов

startOfDay и endOfDay устраняют эти проблемы за счёт неизменяемого подхода и явного намерения кода.