Методы startOf и endOf

Методы startOf и endOf в Day.js используются для приведения даты к началу или концу выбранного временного интервала. Они решают задачу нормализации времени при работе с календарными операциями, фильтрацией диапазонов, построением отчётов и сравнением дат на уровне дней, недель, месяцев и более крупных единиц.

Любая дата в JavaScript содержит не только календарный день, но и точное время вплоть до миллисекунд. При аналитических или прикладных вычислениях часто требуется игнорировать «лишнюю точность» и работать с границами интервалов:

  • начало дня: 00:00:00.000
  • конец дня: 23:59:59.999
  • начало месяца: первый день месяца, 00:00:00.000
  • конец месяца: последний день месяца, 23:59:59.999

Методы startOf и endOf обеспечивают стандартизированное приведение даты к таким границам.

startOf: приведение к началу интервала

Метод startOf(unit) возвращает новый объект даты, округлённый вниз до начала указанной единицы времени.

Сигнатура:

dayjs().startOf(unit)

Где unit может принимать значения:

  • year
  • month
  • week
  • day
  • hour
  • minute
  • second
  • millisecond

Поведение метода

startOf обнуляет все младшие компоненты времени относительно выбранной единицы:

  • при month сбрасываются день, час, минута, секунда и миллисекунды
  • при day сбрасывается только время суток
  • при hour сбрасываются минуты, секунды и миллисекунды

Пример:

const d = dayjs('2026-05-22T15:34:48.123');

d.startOf('day');
// 2026-05-22 00:00:00.000

d.startOf('month');
// 2026-05-01 00:00:00.000

d.startOf('year');
// 2026-01-01 00:00:00.000

endOf: приведение к концу интервала

Метод endOf(unit) возвращает дату, установленную на последний момент указанной единицы времени.

Сигнатура:

dayjs().endOf(unit)

Поведение метода

endOf выполняет противоположную операцию по отношению к startOf, устанавливая максимальные значения для выбранного уровня:

  • для day23:59:59.999
  • для month — последний день месяца в 23:59:59.999
  • для year — 31 декабря в 23:59:59.999

Пример:

const d = dayjs('2026-05-22T15:34:48.123');

d.endOf('day');
// 2026-05-22 23:59:59.999

d.endOf('month');
// 2026-05-31 23:59:59.999

d.endOf('year');
// 2026-12-31 23:59:59.999

Единицы измерения и иерархия округления

Оба метода работают в рамках иерархии временных единиц. Каждая более крупная единица включает в себя более мелкие:

  • millisecond ⟶ second ⟶ minute ⟶ hour ⟶ day ⟶ month ⟶ year

При вызове:

  • startOf('hour') обнуляются минуты, секунды и миллисекунды
  • startOf('month') обнуляется всё ниже уровня дня

Для week поведение зависит от локали и настройки начала недели (понедельник или воскресенье), что может влиять на результат:

dayjs('2026-05-22').startOf('week');

Результат зависит от конфигурации локализации.

Внутренний принцип округления

Обе функции не изменяют исходный объект, а возвращают новый экземпляр даты. Day.js придерживается неизменяемого подхода:

const a = dayjs('2026-05-22T10:00:00');

const b = a.startOf('day');

a.isSame(b); // false

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

Отличие startOf и endOf

Ключевое различие заключается в направлении округления:

  • startOf — всегда в меньшую сторону (минимальное значение интервала)
  • endOf — всегда в большую сторону (максимальное значение интервала)

Пример сравнения:

const d = dayjs('2026-05-22T10:15:00');

d.startOf('day'); // 00:00:00.000
d.endOf('day');   // 23:59:59.999

Эти методы часто используются вместе для создания диапазонов.

Формирование временных диапазонов

Один из наиболее распространённых сценариев — построение диапазона для фильтрации данных:

const start = dayjs().startOf('month');
const end = dayjs().endOf('month');

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

Пример логики фильтрации:

records.filter(record => {
  const date = dayjs(record.createdAt);
  return date.isAfter(start) && date.isBefore(end);
});

Работа с неделями

При использовании week важно учитывать локаль:

dayjs.locale('en'); // неделя начинается с воскресенья
dayjs().startOf('week');

dayjs.locale('ru'); // неделя начинается с понедельника
dayjs().startOf('week');

Это влияет на смещение границ недели и может менять результат на несколько дней.

Особенности endOf и точность времени

Метод endOf устанавливает максимальное значение с точностью до миллисекунд (.999). Это важно при сравнении дат:

const end = dayjs().endOf('day');

end.millisecond(); // 999

При работе с базами данных иногда требуется учитывать, что некоторые системы используют секунды, а не миллисекунды, что может приводить к пограничным ошибкам.

Иммутабельность и цепочки вызовов

Оба метода корректно работают в цепочках:

dayjs('2026-05-22')
  .add(1, 'month')
  .startOf('month')
  .add(2, 'day');

Каждый шаг возвращает новый объект, что позволяет строить предсказуемые последовательности преобразований дат.

Влияние плагинов

При подключении расширений Day.js поведение временных границ может дополняться:

  • utc — приводит вычисления к UTC-времени
  • timezone — учитывает смещения часовых поясов

Пример с UTC:

dayjs.utc('2026-05-22').startOf('day');

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

Частые ошибки при использовании

Одной из распространённых проблем является неверное понимание границ включительности:

  • startOf('day') — всегда включается
  • endOf('day') — часто используется как верхняя граница, но при сравнении с < может исключаться

Пример ошибки:

date.isBefore(dayjs().endOf('day'));

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

Другая ошибка связана с ожиданием мутации исходного объекта, что противоречит архитектуре Day.js:

const d = dayjs();
d.startOf('month'); // результат не изменяет d

Практическое применение в системах

Методы активно используются в:

  • агрегации статистики по дням и месяцам
  • построении календарных интерфейсов
  • формировании отчётных периодов
  • обработке логов и событий
  • реализации пагинации по времени

Типичный пример агрегации:

const todayStart = dayjs().startOf('day');
const todayEnd = dayjs().endOf('day');

events.filter(e =>
  dayjs(e.timestamp).isBetween(todayStart, todayEnd, null, '[]')
);

Такие конструкции позволяют стандартизировать временную логику независимо от источника данных.