Конец временного периода

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

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


Семантика конца периода

Конец периода — это момент времени, в который заканчивается выбранная единица измерения при календарной интерпретации.

Примеры:

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

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


Метод endOf и его поведение

Основной инструмент работы с концом периода — метод endOf.

moment().endOf('day')
moment().endOf('month')
moment().endOf('year')

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

Логика преобразования

При вызове:

moment('2026-05-10T12:34:56').endOf('day')

результат будет:

2026-05-10 23:59:59.999

При этом исходное время полностью перезаписывается в рамках той же календарной даты.


Иерархия временных единиц

Конец периода зависит от уровня агрегирования:

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

Каждый уровень включает в себя более мелкие единицы, которые при вызове endOf обнуляются или максимизируются.

Пример:

moment('2026-05-10 10:20:30').endOf('hour')

Результат:

2026-05-10 10:59:59.999

Календарные особенности

Месяцы разной длины

Конец месяца зависит от количества дней:

  • февраль: 28 или 29 дней
  • апрель: 30 дней
  • май: 31 день
moment('2026-02-15').endOf('month')

Результат:

2026-02-28 23:59:59.999

или в високосный год:

2026-02-29 23:59:59.999

Неделя как относительная единица

Неделя зависит от локали и настройки начала недели.

moment().endOf('week')

В одних конфигурациях конец недели может приходиться на субботу, в других — на воскресенье.


Влияние временных зон

При вычислении конца периода учитывается текущая временная зона окружения.

Особенности:

  • конец дня в UTC и локальной зоне различается
  • при переходах между зонами результат может изменяться

Пример:

moment.utc('2026-05-10').endOf('day')

Результат:

2026-05-10T23:59:59.999Z

Мутация объекта

Метод endOf изменяет исходный объект времени.

const m = moment('2026-05-10 12:00:00');
m.endOf('day');

После выполнения m уже содержит конечное значение дня.

Для сохранения исходного состояния используется клонирование:

const m1 = moment('2026-05-10 12:00:00');
const m2 = m1.clone().endOf('day');

Связь с началом периода

Конец периода всегда парно связан с началом:

  • startOf('day') → 00:00:00.000
  • endOf('day') → 23:59:59.999

Это используется для построения диапазонов:

const start = moment('2026-05-10').startOf('day');
const end = moment('2026-05-10').endOf('day');

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


Использование в сравнениях

Конец периода часто применяется в логике фильтрации:

const now = moment();

if (now.isBefore(moment().endOf('day'))) {
    // текущий момент находится внутри сегодняшнего дня
}

Или при проверке попадания в интервал:

const target = moment('2026-05-10 18:00:00');

const start = moment('2026-05-10').startOf('day');
const end = moment('2026-05-10').endOf('day');

target.isBetween(start, end, null, '[]');

Квадратные скобки указывают на включение границ.


Пограничные состояния времени

При переходах между единицами измерения возникают важные особенности:

Конец секунды и миллисекунды

moment('2026-05-10 12:00:00.123').endOf('second')

Результат:

2026-05-10 12:00:00.999

Конец часа

moment('2026-05-10 12:15:30').endOf('hour')

Результат:

2026-05-10 12:59:59.999

Кварталы и бизнес-логика

Кварталы вычисляются по фиксированному делению года:

  • Q1: январь–март
  • Q2: апрель–июнь
  • Q3: июль–сентябрь
  • Q4: октябрь–декабрь
moment('2026-05-10').endOf('quarter')

Результат:

2026-06-30 23:59:59.999

Дробление времени и нормализация

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

  • меньшие единицы максимизируются
  • большие остаются в рамках календаря
  • не происходит прибавления фиксированных интервалов

Пример:

moment('2026-01-15 10:10:10.500').endOf('day')

Результат:

2026-01-15 23:59:59.999

Сравнение с арифметическим добавлением

Временной конец периода не равен простому добавлению интервала:

moment().add(1, 'day')

и

moment().endOf('day')

имеют принципиально разные семантики:

  • add → смещение во времени
  • endOf → переход к границе календарного интервала

Граничные ошибки при работе с периодами

Ошибка включения/исключения границы

Частая проблема — неправильная интерпретация конца периода как исключающей границы.

Правильная логика:

  • endOf('day') включает весь день
  • сравнение должно учитывать миллисекунды

Ошибка хранения точности

Некоторые системы хранения времени обрезают миллисекунды, что приводит к:

  • потере точности
  • смещению границ диапазонов
  • некорректным фильтрам

Композиция периодов

Конец периода часто используется для построения сложных диапазонов:

const range = {
    start: moment('2026-01-01').startOf('year'),
    end: moment('2026-01-01').endOf('year')
};

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


Нормализация цепочек вызовов

Метод endOf может быть частью цепочки:

moment('2026-05-10')
    .add(2, 'days')
    .endOf('day')
    .format('YYYY-MM-DD HH:mm:ss')

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