Работа с периодическими событиями в JavaScript почти всегда требует сочетания двух механизмов: вычисления времени с помощью библиотеки работы с датами и планирования выполнения через таймеры среды выполнения. Day.js предоставляет компактный и предсказуемый инструментарий для расчёта дат, а логика повторяемости строится поверх этих вычислений.
Основная идея периодических событий заключается не в «запуске по расписанию» как таковом, а в постоянном пересчёте следующего момента выполнения на основе текущего времени и заданных правил повторения.
Любое периодическое событие можно выразить через три компонента:
Day.js используется для работы со стартовой точкой и интервалами.
import dayjs from "dayjs";
const start = dayjs();
const next = start.add(10, "minute");
Этот подход формирует основу для любых повторяющихся операций: каждое новое значение времени вычисляется относительно предыдущего.
Типичная реализация периодического события не полагается
исключительно на setInterval, так как он не учитывает дрейф
времени. Вместо этого Day.js используется для контроля фактических
временных меток.
import dayjs from "dayjs";
let nextRun = dayjs().add(5, "second");
setInterval(() => {
const now = dayjs();
if (now.isAfter(nextRun) || now.isSame(nextRun)) {
// выполнение задачи
console.log("Событие выполнено:", now.format());
// пересчёт следующего запуска
nextRun = nextRun.add(5, "second");
}
}, 200);
Такой подход обеспечивает устойчивость к задержкам и неточностям таймеров JavaScript.
const interval = 30; // секунд
let next = dayjs().add(interval, "second");
setInterval(() => {
const now = dayjs();
if (now.isAfter(next)) {
console.log("Триггер:", now.toISOString());
next = next.add(interval, "second");
}
}, 250);
Подобная модель подходит для:
const stepMinutes = 15;
let nextRun = dayjs().add(stepMinutes, "minute");
function tick() {
const now = dayjs();
if (now.isAfter(nextRun)) {
console.log("Запуск задачи");
nextRun = nextRun.add(stepMinutes, "minute");
}
}
setInterval(tick, 1000);
Суточные задачи требуют фиксации времени суток, а не только интервала.
import dayjs from "dayjs";
function getNextMorningRun() {
const now = dayjs();
let next = now.hour(9).minute(0).second(0).millisecond(0);
if (now.isAfter(next)) {
next = next.add(1, "day");
}
return next;
}
let nextRun = getNextMorningRun();
setInterval(() => {
const now = dayjs();
if (now.isAfter(nextRun)) {
console.log("Ежедневное событие");
nextRun = getNextMorningRun().add(1, "day");
}
}, 1000);
Ключевая особенность: время фиксируется внутри суток, а не просто прибавляется интервал.
Для недельных событий важны день недели и время.
function nextWeeklyRun(targetDay = 1, hour = 10) {
// 0 - воскресенье, 1 - понедельник ...
const now = dayjs();
let next = now.day(targetDay).hour(hour).minute(0).second(0).millisecond(0);
if (now.isAfter(next)) {
next = next.add(1, "week");
}
return next;
}
let nextRun = nextWeeklyRun(5, 18); // пятница 18:00
setInterval(() => {
const now = dayjs();
if (now.isAfter(nextRun)) {
console.log("Недельное событие");
nextRun = nextWeeklyRun(5, 18).add(1, "week");
}
}, 1000);
Месячные интервалы сложнее из-за разной длины месяцев.
function nextMonthlyRun(dayOfMonth = 10, hour = 12) {
const now = dayjs();
let next = now.date(dayOfMonth).hour(hour).minute(0).second(0).millisecond(0);
if (now.isAfter(next)) {
next = next.add(1, "month");
}
return next;
}
let nextRun = nextMonthlyRun(10, 12);
При установке даты, которой нет в следующем месяце, Day.js автоматически нормализует значение.
Например:
Это требует дополнительной защиты:
function safeMonthlyRun(day, hour) {
const now = dayjs();
const daysInMonth = now.daysInMonth();
const validDay = Math.min(day, daysInMonth);
let next = now.date(validDay).hour(hour).minute(0).second(0).millisecond(0);
if (now.isAfter(next)) {
const nextMonth = now.add(1, "month");
const daysNext = nextMonth.daysInMonth();
next = nextMonth.date(Math.min(day, daysNext))
.hour(hour)
.minute(0)
.second(0)
.millisecond(0);
}
return next;
}
При длительной работе системы важно избегать накопления ошибки времени.
const step = 10; // секунд
let next = dayjs();
function loop() {
const now = dayjs();
while (now.isAfter(next)) {
console.log("Срабатывание:", next.toISOString());
next = next.add(step, "second");
}
}
setInterval(loop, 500);
Использование while вместо if позволяет
компенсировать пропущенные циклы.
При работе с глобальными системами требуется учитывать смещение времени.
Подключение UTC-плагина:
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
import dayjs from "dayjs";
dayjs.extend(utc);
dayjs.extend(timezone);
const tz = "Europe/Moscow";
function nextRunTz(hour) {
const now = dayjs().tz(tz);
let next = now.hour(hour).minute(0).second(0).millisecond(0);
if (now.isAfter(next)) {
next = next.add(1, "day");
}
return next;
}
add(n, unit)Day.js не содержит встроенного cron-движка, но его можно эмулировать.
function matchSchedule(now, config) {
return (
now.minute() === config.minute &&
now.hour() === config.hour &&
now.day() === config.weekday
);
}
Использование:
setInterval(() => {
const now = dayjs();
if (matchSchedule(now, { minute: 0, hour: 12, weekday: 1 })) {
console.log("Понедельник 12:00");
}
}, 1000);
При перезапуске приложения необходимо восстанавливать следующую точку выполнения.
const stored = localStorage.getItem("nextRun");
let nextRun = stored ? dayjs(stored) : dayjs().add(1, "minute");
function persist() {
localStorage.setItem("nextRun", nextRun.toISOString());
}
Такой подход позволяет:
Реальные системы часто объединяют несколько типов расписаний:
function scheduler() {
const now = dayjs();
if (now.isAfter(nextRun)) {
executeTask();
nextRun = computeNext();
}
}
Функция computeNext становится центральным элементом
логики, определяя тип периодичности.