2024-01-02 03:50:31 -08:00
|
|
|
import moment from 'moment-timezone';
|
2024-07-16 11:42:48 -07:00
|
|
|
import { type CronExpression, randomInt } from 'n8n-workflow';
|
|
|
|
import type { IRecurrenceRule, ScheduleInterval } from './SchedulerInterface';
|
2023-02-01 13:53:05 -08:00
|
|
|
|
2024-07-16 11:42:48 -07:00
|
|
|
export function recurrenceCheck(
|
|
|
|
recurrence: IRecurrenceRule,
|
|
|
|
recurrenceRules: number[],
|
2023-02-01 13:53:05 -08:00
|
|
|
timezone: string,
|
|
|
|
): boolean {
|
2024-07-16 11:42:48 -07:00
|
|
|
if (!recurrence.activated) return true;
|
2023-02-01 13:53:05 -08:00
|
|
|
|
2024-07-16 11:42:48 -07:00
|
|
|
const intervalSize = recurrence.intervalSize;
|
|
|
|
if (!intervalSize) return false;
|
2023-02-01 13:53:05 -08:00
|
|
|
|
2024-07-16 11:42:48 -07:00
|
|
|
const index = recurrence.index;
|
|
|
|
const typeInterval = recurrence.typeInterval;
|
|
|
|
const lastExecution = recurrenceRules[index];
|
|
|
|
|
|
|
|
const momentTz = moment.tz(timezone);
|
|
|
|
if (typeInterval === 'hours') {
|
|
|
|
const hour = momentTz.hour();
|
|
|
|
if (lastExecution === undefined || hour === (intervalSize + lastExecution) % 24) {
|
|
|
|
recurrenceRules[index] = hour;
|
2023-02-01 13:53:05 -08:00
|
|
|
return true;
|
|
|
|
}
|
2024-07-16 11:42:48 -07:00
|
|
|
} else if (typeInterval === 'days') {
|
|
|
|
const dayOfYear = momentTz.dayOfYear();
|
|
|
|
if (lastExecution === undefined || dayOfYear === (intervalSize + lastExecution) % 365) {
|
|
|
|
recurrenceRules[index] = dayOfYear;
|
2023-02-01 13:53:05 -08:00
|
|
|
return true;
|
|
|
|
}
|
2024-07-16 11:42:48 -07:00
|
|
|
} else if (typeInterval === 'weeks') {
|
|
|
|
const week = momentTz.week();
|
2023-02-01 13:53:05 -08:00
|
|
|
if (
|
2024-07-16 11:42:48 -07:00
|
|
|
lastExecution === undefined || // First time executing this rule
|
|
|
|
week === (intervalSize + lastExecution) % 52 || // not first time, but minimum interval has passed
|
|
|
|
week === lastExecution // Trigger on multiple days in the same week
|
2023-02-01 13:53:05 -08:00
|
|
|
) {
|
2024-07-16 11:42:48 -07:00
|
|
|
recurrenceRules[index] = week;
|
2023-02-01 13:53:05 -08:00
|
|
|
return true;
|
|
|
|
}
|
2024-07-16 11:42:48 -07:00
|
|
|
} else if (typeInterval === 'months') {
|
|
|
|
const month = momentTz.month();
|
|
|
|
if (lastExecution === undefined || month === (intervalSize + lastExecution) % 12) {
|
|
|
|
recurrenceRules[index] = month;
|
2023-02-01 13:53:05 -08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2023-06-13 09:57:17 -07:00
|
|
|
|
2024-07-16 11:42:48 -07:00
|
|
|
export const toCronExpression = (interval: ScheduleInterval): CronExpression => {
|
|
|
|
if (interval.field === 'cronExpression') return interval.expression;
|
|
|
|
if (interval.field === 'seconds') return `*/${interval.secondsInterval} * * * * *`;
|
|
|
|
|
|
|
|
const randomSecond = randomInt(0, 60);
|
|
|
|
if (interval.field === 'minutes') return `${randomSecond} */${interval.minutesInterval} * * * *`;
|
|
|
|
|
|
|
|
const minute = interval.triggerAtMinute ?? randomInt(0, 60);
|
|
|
|
if (interval.field === 'hours')
|
|
|
|
return `${randomSecond} ${minute} */${interval.hoursInterval} * * *`;
|
|
|
|
|
|
|
|
// Since Cron does not support `*/` for days or weeks, all following expressions trigger more often, but are then filtered by `recurrenceCheck`
|
|
|
|
const hour = interval.triggerAtHour ?? randomInt(0, 24);
|
|
|
|
if (interval.field === 'days') return `${randomSecond} ${minute} ${hour} * * *`;
|
|
|
|
if (interval.field === 'weeks') {
|
|
|
|
const days = interval.triggerAtDay;
|
|
|
|
const daysOfWeek = days.length === 0 ? '*' : days.join(',');
|
|
|
|
return `${randomSecond} ${minute} ${hour} * * ${daysOfWeek}` as CronExpression;
|
|
|
|
}
|
|
|
|
|
|
|
|
const dayOfMonth = interval.triggerAtDayOfMonth ?? randomInt(0, 31);
|
|
|
|
return `${randomSecond} ${minute} ${hour} ${dayOfMonth} */${interval.monthsInterval} *`;
|
|
|
|
};
|
|
|
|
|
|
|
|
export function intervalToRecurrence(interval: ScheduleInterval, index: number) {
|
|
|
|
let recurrence: IRecurrenceRule = { activated: false };
|
|
|
|
|
|
|
|
if (interval.field === 'hours') {
|
|
|
|
const { hoursInterval } = interval;
|
|
|
|
if (hoursInterval !== 1) {
|
|
|
|
recurrence = {
|
|
|
|
activated: true,
|
|
|
|
index,
|
|
|
|
intervalSize: hoursInterval,
|
|
|
|
typeInterval: 'hours',
|
|
|
|
};
|
2023-06-13 09:57:17 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-16 11:42:48 -07:00
|
|
|
if (interval.field === 'days') {
|
|
|
|
const { daysInterval } = interval;
|
|
|
|
if (daysInterval !== 1) {
|
|
|
|
recurrence = {
|
|
|
|
activated: true,
|
|
|
|
index,
|
|
|
|
intervalSize: daysInterval,
|
|
|
|
typeInterval: 'days',
|
|
|
|
};
|
|
|
|
}
|
2023-06-13 09:57:17 -07:00
|
|
|
}
|
2024-04-18 02:57:36 -07:00
|
|
|
|
2024-07-16 11:42:48 -07:00
|
|
|
if (interval.field === 'weeks') {
|
|
|
|
const { weeksInterval } = interval;
|
|
|
|
if (weeksInterval !== 1) {
|
|
|
|
recurrence = {
|
|
|
|
activated: true,
|
|
|
|
index,
|
|
|
|
intervalSize: weeksInterval,
|
|
|
|
typeInterval: 'weeks',
|
|
|
|
};
|
|
|
|
}
|
2024-04-18 02:57:36 -07:00
|
|
|
}
|
2024-07-16 11:42:48 -07:00
|
|
|
|
|
|
|
if (interval.field === 'months') {
|
|
|
|
const { monthsInterval } = interval;
|
|
|
|
if (monthsInterval !== 1) {
|
|
|
|
recurrence = {
|
|
|
|
activated: true,
|
|
|
|
index,
|
|
|
|
intervalSize: monthsInterval,
|
|
|
|
typeInterval: 'months',
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return recurrence;
|
|
|
|
}
|