cronnor
Version:
Bibliothèque JavaScript implémentant un programme cron.
321 lines (292 loc) • 10.3 kB
JavaScript
/**
* @module
* @license MIT
* @author Sébastien Règne
*/
import parse from "./parse.js";
/**
* @import Field from "./field.js"
*/
/**
* La classe d'une expression _cron_.
*
* @class
*/
export default class CronExp {
/**
* Les valeurs possibles pour les secondes.
*
* @type {Field}
*/
#seconds;
/**
* Les valeurs possibles pour les minutes.
*
* @type {Field}
*/
#minutes;
/**
* Les valeurs possibles pour les heures.
*
* @type {Field}
*/
#hours;
/**
* Les valeurs possibles pour le jour du mois.
*
* @type {Field}
*/
#date;
/**
* Les valeurs possibles pour le mois (dont les numéros commencent à zéro
* afin d'utiliser la même numérotation que `Date.prototype.getMonth()`).
*
* @type {Field}
*/
#month;
/**
* Les valeurs possibles pour le jour de la semaine (en utilisant toujours
* `0` pour le dimanche afin d'utiliser la même numérotation que
* `Date.prototype.getDay()`).
*
* @type {Field}
*/
#day;
/**
* Crée une expression _cron_.
*
* @param {string} pattern Le motif de l'expression _cron_.
* @throws {Error} Si la syntaxe du motif est incorrecte.
* @throws {RangeError} Si un intervalle est invalide (hors limite ou quand
* la borne supérieure est plus petite que la borne
* inférieure).
* @throws {TypeError} Si le constructeur est appelé sans le mot clé `new`
* ou si le motif n'est pas une chaine de caractères.
*/
constructor(pattern) {
const fields = parse(pattern);
this.#seconds = fields.seconds;
this.#minutes = fields.minutes;
this.#hours = fields.hours;
this.#date = fields.date;
this.#month = fields.month;
this.#day = fields.day;
}
/**
* Teste si une date respecte l'expression.
*
* @param {Date} [date] La date qui sera testée (ou l'instant présent par
* défaut).
* @returns {boolean} `true` si l'expression est respectée ; sinon `false`.
*/
test(date = new Date()) {
// Vérifier que les secondes, minutes, les heures et le mois respectent
// les conditions.
if (
!(
this.#seconds.test(date.getSeconds()) &&
this.#minutes.test(date.getMinutes()) &&
this.#hours.test(date.getHours()) &&
this.#month.test(date.getMonth())
)
) {
return false;
}
// Quand le jour du mois et le jour de la semaine sont renseignés
// (différent de l'astérisque), vérifier si au moins une des deux
// conditions est respectée.
if (this.#date.restricted && this.#day.restricted) {
return (
this.#date.test(date.getDate()) || this.#day.test(date.getDay())
);
}
// Sinon : soit le jour du mois, soit le jour de la semaine ou aucun des
// deux ne sont renseignés. Dans ce cas, vérifier classiquement les deux
// conditions.
return this.#date.test(date.getDate()) && this.#day.test(date.getDay());
}
/**
* Calcule la prochaine date (ou garde la date de début si les secondes
* respectent la condition) en vérifiant seulement la condition des
* secondes.
*
* @param {Date} start La date de début.
* @returns {Date} La prochaine date vérifiant la condition des secondes.
*/
#nextSeconds(start) {
if (this.#seconds.test(start.getSeconds())) {
return start;
}
const date = new Date(start);
const next = this.#seconds.next(date.getSeconds());
if (undefined === next) {
date.setMinutes(date.getMinutes() + 1);
date.setSeconds(this.#seconds.min);
} else {
date.setSeconds(next);
}
return date;
}
/**
* Calcule la prochaine date (ou garde la date de début si les minutes
* respectent la condition) en vérifiant seulement la condition des minutes.
*
* @param {Date} start La date de début.
* @returns {Date} La prochaine date vérifiant la condition des minutes.
*/
#nextMinutes(start) {
if (this.#minutes.test(start.getMinutes())) {
return start;
}
const date = new Date(start);
date.setSeconds(this.#seconds.min);
const next = this.#minutes.next(date.getMinutes());
if (undefined === next) {
date.setHours(date.getHours() + 1);
date.setMinutes(this.#minutes.min);
} else {
date.setMinutes(next);
}
return date;
}
/**
* Calcule la prochaine date (ou garde la date de début si les heures
* respectent la condition) en vérifiant seulement la condition des heures.
*
* @param {Date} start La date de début.
* @returns {Date} La prochaine date vérifiant la condition des heures.
*/
#nextHours(start) {
if (this.#hours.test(start.getHours())) {
return start;
}
const date = new Date(start);
date.setMinutes(this.#minutes.min);
date.setSeconds(this.#seconds.min);
const next = this.#hours.next(date.getHours());
if (undefined === next) {
date.setDate(date.getDate() + 1);
date.setHours(this.#hours.min);
} else {
date.setHours(next);
}
return date;
}
/**
* Calcule la prochaine date (ou garde la date de début si le jour du mois
* respecte la condition) en vérifiant seulement la condition du jour du
* mois.
*
* @param {Date} start La date de début.
* @returns {Date} La prochaine date vérifiant la condition du jour du mois.
*/
#nextDate(start) {
if (this.#date.test(start.getDate())) {
return start;
}
const date = new Date(start);
date.setHours(this.#hours.min);
date.setMinutes(this.#minutes.min);
date.setSeconds(this.#seconds.min);
const next = this.#date.next(date.getDate());
if (
undefined === next ||
next >
new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate()
) {
date.setMonth(date.getMonth() + 1);
date.setDate(this.#date.min);
} else {
date.setDate(next);
}
return date;
}
/**
* Calcule la prochaine date (ou garde la date de début si le jour de la
* semaine respecte la condition) en vérifiant seulement la condition du
* jour de la semaine.
*
* @param {Date} start La date de début.
* @returns {Date} La prochaine date vérifiant la condition du jour de la
* semaine.
*/
#nextDay(start) {
if (this.#day.test(start.getDay())) {
return start;
}
const date = new Date(start);
date.setHours(this.#hours.min);
date.setMinutes(this.#minutes.min);
date.setSeconds(this.#seconds.min);
const next = this.#day.next(date.getDay()) ?? this.#day.min;
date.setDate(date.getDate() + ((next + (7 - date.getDay())) % 7));
return date;
}
/**
* Calcule la prochaine date (ou garde la date de début si le jour du mois
* ou de la semaine respecte la condition) en vérifiant seulement la
* condition du jour du mois ou de la semaine.
*
* @param {Date} start La date de début.
* @returns {Date} La prochaine date vérifiant la condition du jour du mois
* ou de la semaine.
*/
#nextDateDay(start) {
const nextDate = this.#nextDate(start);
const nextDay = this.#nextDay(start);
// Si le jour du mois et de la semaine sont renseignés (différent de
// l'astérisque), prendre la prochaine date la plus proche (qui est
// l'équivalent de prendre la prochaine date vérifiant au moins une des
// deux conditions).
// Sinon, prendre la prochaine date la plus éloignée (qui est
// l'équivalent de prendre la prochaine date vérifiant les deux
// conditions dont au moins une n'a aucune contrainte).
return this.#date.restricted && this.#day.restricted
? new Date(Math.min(nextDate.getTime(), nextDay.getTime()))
: new Date(Math.max(nextDate.getTime(), nextDay.getTime()));
}
/**
* Calcule la prochaine date (ou garde la date de début si le mois respecte
* la condition) en vérifiant seulement la condition du mois.
*
* @param {Date} start La date de début.
* @returns {Date} La prochaine date vérifiant la condition du mois.
*/
#nextMonth(start) {
if (this.#month.test(start.getMonth())) {
return start;
}
const date = new Date(start);
// Mettre temporairement le premier jour du mois et calculer le bon jour
// après avoir trouvé le bon mois.
date.setDate(1);
date.setHours(this.#hours.min);
date.setMinutes(this.#minutes.min);
date.setSeconds(this.#seconds.min);
const next = this.#month.next(date.getMonth());
if (undefined === next) {
date.setFullYear(date.getFullYear() + 1);
date.setMonth(this.#month.min);
} else {
date.setMonth(next);
}
return this.#nextDateDay(date);
}
/**
* Calcule la prochaine date respectant l'expression.
*
* @param {Date} [start] La date de début (ou l'instant présent par défaut).
* @returns {Date} La prochaine date respectant l'expression.
*/
next(start = new Date()) {
let date = new Date(start);
date.setMilliseconds(0);
date.setSeconds(date.getSeconds() + 1);
date = this.#nextSeconds(date);
date = this.#nextMinutes(date);
date = this.#nextHours(date);
date = this.#nextDateDay(date);
date = this.#nextMonth(date);
return date;
}
}