В Luxon отсутствует отдельный высокоуровневый механизм cron-подобных
расписаний. Повторяющиеся события формируются через комбинацию
DateTime, арифметики дат и, при необходимости,
Interval. Такой подход обеспечивает гибкость и
предсказуемость, поскольку каждое следующее значение вычисляется явно,
без скрытого состояния планировщика.
Основная идея построения повторяемости заключается в итеративном
смещении даты с использованием методов plus,
minus, а также нормализации через startOf и
endOf.
Повторяющаяся последовательность строится от стартовой точки:
import { DateTime } from "luxon";
const start = DateTime.local(2026, 1, 1);
Дальнейшая генерация выполняется путем последовательного добавления временных единиц:
const next = start.plus({ days: 1 });
Каждый вызов plus возвращает новый экземпляр
DateTime, что исключает мутации и упрощает построение
цепочек событий.
Ключевая особенность: все операции являются чистыми и детерминированными.
Наиболее распространенный способ построения повторяемости — цикл с накоплением значений:
const start = DateTime.local(2026, 1, 1);
const events = [];
let current = start;
for (let i = 0; i < 10; i++) {
events.push(current);
current = current.plus({ days: 1 });
}
Такая модель используется для ежедневных, еженедельных и произвольных периодов.
Ежедневные события формируются через инкремент
days: 1:
current = current.plus({ days: 1 });
Периодические события с произвольным шагом:
current = current.plus({ days: 3 });
или
current = current.plus({ hours: 6 });
Такая схема применяется для:
Interval применяется для определения границ временного
диапазона, внутри которого генерируются события:
import { Interval } from "luxon";
const start = DateTime.local(2026, 1, 1);
const end = DateTime.local(2026, 1, 10);
const interval = Interval.fromDateTimes(start, end);
Хотя Interval не генерирует повторяемость напрямую, он
используется как ограничитель при итерации:
const events = [];
let current = start;
while (interval.contains(current)) {
events.push(current);
current = current.plus({ days: 1 });
}
Для еженедельных событий используется шаг weeks:
let current = DateTime.local(2026, 1, 1);
const events = [];
for (let i = 0; i < 8; i++) {
events.push(current);
current = current.plus({ weeks: 1 });
}
Фиксация дня недели достигается через нормализацию:
const monday = current.set({ weekday: 1 });
Значение weekday позволяет принудительно установить день
недели в пределах текущей недели.
Распространённый сценарий — события каждый понедельник:
let current = DateTime.local(2026, 1, 1).set({ weekday: 1 });
const events = [];
for (let i = 0; i < 12; i++) {
events.push(current);
current = current.plus({ weeks: 1 });
}
Этот подход гарантирует стабильность расписания независимо от исходной даты.
События фиксируются по дню месяца:
let current = DateTime.local(2026, 1, 15);
const events = [];
for (let i = 0; i < 6; i++) {
events.push(current);
current = current.plus({ months: 1 });
}
При этом Luxon корректно обрабатывает переходы между месяцами различной длины.
Используется endOf:
const lastDay = DateTime.local(2026, 2, 1).endOf("month");
Генерация серии последних дней месяцев:
let current = DateTime.local(2026, 1, 1).endOf("month");
const events = [];
for (let i = 0; i < 6; i++) {
events.push(current);
current = current.plus({ months: 1 }).endOf("month");
}
Подобные шаблоны не реализованы напрямую и требуют вычисления вручную.
Пример: второй вторник месяца.
function nthWeekdayOfMonth(year, month, weekday, n) {
let date = DateTime.local(year, month, 1);
let count = 0;
while (date.month === month) {
if (date.weekday === weekday) {
count++;
if (count === n) return date;
}
date = date.plus({ days: 1 });
}
}
Фильтрация выходных реализуется через проверку
weekday:
function isWeekend(date) {
return date.weekday === 6 || date.weekday === 7;
}
Генерация последовательности рабочих дней:
const events = [];
let current = DateTime.local(2026, 1, 1);
while (events.length < 10) {
if (!isWeekend(current)) {
events.push(current);
}
current = current.plus({ days: 1 });
}
Часовые интервалы используются для задач планирования и логирования:
let current = DateTime.local(2026, 1, 1, 8, 0);
const events = [];
for (let i = 0; i < 12; i++) {
events.push(current);
current = current.plus({ hours: 2 });
}
Аналогично для минут:
current = current.plus({ minutes: 15 });
Повторяющиеся события должны учитывать фиксированную зону времени:
const base = DateTime.fromObject(
{ year: 2026, month: 1, day: 1, hour: 10 },
{ zone: "Europe/Berlin" }
);
При генерации важно сохранять зону:
const next = base.plus({ days: 1 });
Переключение зон без контроля может приводить к сдвигам при переходе на летнее время.
Использование startOf и endOf стабилизирует
границы событий:
const dayStart = DateTime.local().startOf("day");
const monthEnd = DateTime.local().endOf("month");
В повторяющихся сценариях это применяется для устранения дрейфа времени:
current = current.plus({ days: 1 }).startOf("day");
При генерации последовательностей всегда задаётся явное ограничение:
const events = [];
let current = DateTime.local(2026, 1, 1);
while (events.length < 20) {
events.push(current);
current = current.plus({ days: 1 });
}
Альтернативный подход — использование диапазона через сравнение:
while (current < end) {
current = current.plus({ days: 1 });
}
Комбинации условий формируют гибкие расписания:
Пример комбинированной логики:
let current = DateTime.local(2026, 1, 1);
const events = [];
while (events.length < 15) {
if (current.weekday <= 5) {
events.push(current);
}
current = current.plus({ days: 1 });
}
Каждое значение DateTime является неизменяемым объектом.
Это означает:
const a = DateTime.local();
const b = a.plus({ days: 1 });
a остаётся неизменным независимо от операций над
b.
Часто возникающие проблемы:
startOfПример дрейфа:
current = current.plus({ minutes: 1440 });
Корректнее:
current = current.plus({ days: 1 });