Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 15x 15x 30x 30x 30x 30x 23x 23x 30x 30x 30x 1x 1x 1x 1x 1x 1x 1x 1x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 30x 3000x 3000x 3000x 30x 30x 30x 30x 105x 105x 30x 30x 30x 30x 30x 30x 30x 1x 1x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 272x 272x 22x 22x 22x 22x 22x 22x 22x 22x | import { Logger } from '@nestjs/common';
import { CronJob } from 'cron';
import { noop } from 'lodash-es';
import { dayjs } from '@amalia/ext/dayjs';
import { assert, toError } from '@amalia/ext/typescript';
import { type Company } from '@amalia/tenants/companies/types';
// Limit for the algorithm.
const MAX_CRON_OCCURRENCES_PER_DAY = 100;
const getMinMaxDates = (cron: string, currentTime: Date) => {
// Parse the cron to get start and end hours.
const hours = (() => {
try {
const splitCron = cron.split(' ')[1].split(',');
assert(splitCron.every((hour) => !Number.isNaN(+hour)));
return splitCron;
} catch (err) {
Logger.warn(`Invalid cron expression (${cron}): ${toError(err).message}`);
throw new Error(
`Invalid cron expression: ${cron}. It should be in the format: "something 23,0,1,2 * * *" (specify hours manually).`,
);
}
})();
const startHour = hours[0];
const endHour = hours.at(-1)!;
// Offset the current time by 1 hour in the past to get the start of the script.
let startOfScriptDate = dayjs(currentTime).utc().startOf('hour');
while (startOfScriptDate.hour() !== +startHour) {
startOfScriptDate = startOfScriptDate.subtract(1, 'hour');
}
// Offset the current time by 1 hour in the future to get the end of the script.
let endOfScriptDate = dayjs(currentTime).utc().endOf('hour');
while (endOfScriptDate.hour() !== +endHour) {
endOfScriptDate = endOfScriptDate.add(1, 'hour');
}
return [startOfScriptDate, endOfScriptDate];
};
/**
* Given an UTC cron and the current UTC date, give us the position of the current date in the cron.
*
* @param cron
* @param currentTime
*/
export const getPositionInCron = (cron: string, currentTime: Date) => {
const [startOfScriptDate, endOfScriptDate] = getMinMaxDates(cron, currentTime);
// We have to offset everything by 1 day because the lib cannot work on a passed date.
const nextStartUtc = startOfScriptDate.add(1, 'day');
const nextEndUtc = endOfScriptDate.add(1, 'day');
const currentTimeOneDayOffset = dayjs(currentTime).utc().add(1, 'day');
const job = new CronJob(cron, noop, null, false, 'UTC');
// Check the time of the occurrences for tomorrow.
const perDayOccurrences = job.nextDates(MAX_CRON_OCCURRENCES_PER_DAY).filter((occurrence) => {
const time = occurrence.toUnixInteger();
return time >= nextStartUtc.unix() && time < nextEndUtc.unix();
});
// Check on which occurrence we are (all checks are made with a day offset, but we don't care).
const currentOccurrenceIndex = perDayOccurrences.findIndex(
(occurrence, index) =>
occurrence.toUnixInteger() < currentTimeOneDayOffset.unix() &&
(!perDayOccurrences[index + 1] || currentTimeOneDayOffset.unix() < perDayOccurrences[index + 1].toUnixInteger()),
);
return {
position: currentOccurrenceIndex,
count: perDayOccurrences.length,
};
};
export const getCurrentBatchCompanies = (
cron: string,
currentTime: Date,
companies: Company[],
): { position: number; count: number; companies: Company[] } => {
const { position, count } = getPositionInCron(cron, currentTime);
// Build an array of placeholders for the batches.
// We cannot put the companies here or else we would have:
// - 0, 3
// - 1, 4
// - 2
// And we want:
// - 0, 1
// - 2, 3
// - 4
const batchesOfPlaceholders = Array.from({ length: count }, (_) => [] as string[]);
companies.forEach((_, index) => {
batchesOfPlaceholders[index % count].push('placeholder');
});
// And now invert the array to get the actual batches.
let companyArrayIndex = 0;
const batches = batchesOfPlaceholders.map((batch) => batch.map((_) => companies[companyArrayIndex++]));
// Finally, return the batch of the current position.
return { position, count, companies: batches.at(position) ?? [] };
};
|