Skip to content

Commit

Permalink
feat: Ajouter le support des secondes.
Browse files Browse the repository at this point in the history
  • Loading branch information
regseb committed Jun 30, 2023
1 parent 444958d commit c966de3
Show file tree
Hide file tree
Showing 12 changed files with 764 additions and 398 deletions.
2 changes: 2 additions & 0 deletions .stryker.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
* @type {import("@stryker-mutator/api/core").PartialStrykerOptions}
*/
export default {
disableTypeChecks: false,
incremental: true,
incrementalFile: ".stryker/incremental.json",
ignoreStatic: true,
mochaOptions: { config: "test/mocharc.json" },
reporters: ["dots", "clear-text"],
tempDirName: ".stryker/tmp/",
testRunner: "mocha",
Expand Down
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,14 +217,15 @@ Annule la planification.

## Expression _cron_

Les expressions _cron_ sont des chaines de caractères composées de cinq éléments
séparés par une espace. Les éléments représentent :

1. les minutes : `0` à `59` ;
2. les heures : `0` à `23` ;
3. le jour du mois : `1` à `31` ;
4. le mois : `1` ou `jan`, `2` ou `feb`, …, `12` ou `dec` ;
5. le jour de la semaine : `0`, `7` ou `sun`, `1` ou `mon`, …, `6` ou `sat`.
Les expressions _cron_ sont des chaines de caractères composées de cinq ou six
éléments séparés par une espace. Les éléments représentent :

1. les secondes (optionnel ; `0` par défaut) : `0` à `59` ;
2. les minutes : `0` à `59` ;
3. les heures : `0` à `23` ;
4. le jour du mois : `1` à `31` ;
5. le mois : `1` ou `jan`, `2` ou `feb`, …, `12` ou `dec` ;
6. le jour de la semaine : `0`, `7` ou `sun`, `1` ou `mon`, …, `6` ou `sat`.

Pour chaque élément, des compositions sont possibles :

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"lint:fix": "metalint --fix",
"lint:types": "tsc --project .tsconfig_lint.json",
"test": "npm run test:coverage",
"test:unit": "mocha",
"test:unit": "mocha --config test/mocharc.json",
"test:coverage": "stryker run .stryker.config.js",
"jsdocs": "typedoc --tsconfig .tsconfig_jsdocs.json",
"prepare": "tsc --project .tsconfig_types.json",
Expand Down
49 changes: 44 additions & 5 deletions src/cronexp.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ import parse from "./parse.js";
* @class
*/
export default class CronExp {
/**
* Les valeurs possibles pour les secondes.
*
* @type {Field}
*/
#seconds;

/**
* Les valeurs possibles pour les minutes.
*
Expand Down Expand Up @@ -45,7 +52,7 @@ export default class CronExp {

/**
* Les valeurs possibles pour le jour de la semaine (en utilisant toujours
* <code>0</code> pour le dimanche afin d'utiliser la même numéroration que
* <code>0</code> pour le dimanche afin d'utiliser la même numérotation que
* <code>Date.prototype.getDay()</code>).
*
* @type {Field}
Expand All @@ -66,6 +73,7 @@ export default class CronExp {
*/
constructor(pattern) {
const fields = parse(pattern);
this.#seconds = fields.seconds;
this.#minutes = fields.minutes;
this.#hours = fields.hours;
this.#date = fields.date;
Expand All @@ -82,9 +90,10 @@ export default class CronExp {
* sinon <code>false</code>.
*/
test(date = new Date()) {
// Vérifier que les minutes, les heures et le mois respectent les
// conditions.
// 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())
Expand All @@ -107,6 +116,30 @@ export default class CronExp {
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.getTime());
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.
Expand All @@ -120,6 +153,7 @@ export default class CronExp {
}

const date = new Date(start.getTime());
date.setSeconds(this.#seconds.min);
const next = this.#minutes.next(date.getMinutes());
if (undefined === next) {
date.setHours(date.getHours() + 1);
Expand All @@ -144,6 +178,7 @@ export default class CronExp {

const date = new Date(start.getTime());
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);
Expand All @@ -170,6 +205,7 @@ export default class CronExp {
const date = new Date(start.getTime());
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 ||
Expand Down Expand Up @@ -201,6 +237,7 @@ export default class CronExp {
const date = new Date(start.getTime());
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;
Expand Down Expand Up @@ -248,6 +285,7 @@ export default class CronExp {
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);
Expand All @@ -267,9 +305,10 @@ export default class CronExp {
*/
next(start = new Date()) {
let date = new Date(start.getTime());
date.setSeconds(0, 0);
date.setMinutes(date.getMinutes() + 1);
date.setMilliseconds(0);
date.setSeconds(date.getSeconds() + 1);

date = this.#nextSeconds(date);
date = this.#nextMinutes(date);
date = this.#nextHours(date);
date = this.#nextDateDay(date);
Expand Down
90 changes: 50 additions & 40 deletions src/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,25 @@ import Field from "./field.js";
* @type {Object<string, string>}
*/
const NICKNAMES = {
"@yearly": "0 0 1 1 *",
"@annually": "0 0 1 1 *",
"@monthly": "0 0 1 * *",
"@weekly": "0 0 * * 0",
"@daily": "0 0 * * *",
"@midnight": "0 0 * * *",
"@hourly": "0 * * * *",
"@yearly": "0 0 0 1 1 *",
"@annually": "0 0 0 1 1 *",
"@monthly": "0 0 0 1 * *",
"@weekly": "0 0 0 * * 0",
"@daily": "0 0 0 * * *",
"@midnight": "0 0 0 * * *",
"@hourly": "0 0 * * * *",
};

/**
* Les formes littérales des mois et des jours de la semaine avec leur
* équivalent numérique. Les autres champs (minutes, heures et jour du mois)
* n'en ont pas.
* équivalent numérique. Les autres champs (secondes, minutes, heures et jour du
* mois) n'en ont pas.
*
* @type {Object<string, number>[]}
*/
const BASE_NAMES = [
// Secondes.
{},
// Minutes.
{},
// Heures.
Expand Down Expand Up @@ -76,9 +78,9 @@ const NAMES = {
* @type {Object<string, number[]>}
*/
const LIMITS = {
// Minutes, heures, jour du mois, mois, jour de la semaine.
MIN: [0, 0, 1, 1, 0],
MAX: [59, 23, 31, 12, 7],
// Secondes, Minutes, heures, jour du mois, mois, jour de la semaine.
MIN: [0, 0, 0, 1, 1, 0],
MAX: [59, 59, 23, 31, 12, 7],
};

/**
Expand Down Expand Up @@ -133,8 +135,8 @@ const FORMATS = [
/** @type {string} */ (format)
.replaceAll("*", "\\*")
.replaceAll("{step}", "(?<step>\\d+)")
.replaceAll("{min}", "(?<min>\\d+|[a-z]+|\\?)")
.replaceAll("{max}", "(?<max>\\d+|[a-z]+|\\?)") +
.replaceAll("{min}", "(?<min>[\\da-z?]+)")
.replaceAll("{max}", "(?<max>[\\da-z?]+)") +
"$",
"iu",
),
Expand All @@ -154,21 +156,23 @@ const FORMATS = [
const getIndexValue = (date, index, max = false) => {
switch (index) {
case 0:
return date.getMinutes();
return date.getSeconds();
case 1:
return date.getHours();
return date.getMinutes();
case 2:
return date.getDate();
return date.getHours();
case 3:
return date.getDate();
case 4:
// Incrémenter d'un pour faire commencer les mois à un.
return date.getMonth() + 1;
case 4:
case 5:
// Utiliser le nombre sept pour le dimanche quand il est placé dans
// la borne supérieure.
return max && 0 === date.getDay() ? 7 : date.getDay();
// Stryker disable next-line all: Désactiver Stryker pour le défaut car
// la fonction getIndexValue() est toujours appelée avec un index entre
// 0 et 4. Il est donc impossible de tester cette condition.
// 0 et 5. Il est donc impossible de tester cette condition.
default:
// Stryker disable next-line all
throw new TypeError(`Invalid index ${index}`);
Expand Down Expand Up @@ -266,36 +270,41 @@ const parseField = (parts, index, now, pattern) => {
*/
export default function parse(pattern) {
// Remplacer l'éventuelle chaine spéciale par son équivalent et séparer
// les cinq champs (minutes, heures, jour du mois, mois et jour de la
// semaine).
// les cinq ou six champs (secondes, minutes, heures, jour du mois, mois et
// jour de la semaine).
const fields = (NICKNAMES[pattern.toLowerCase()] ?? pattern).split(/\s+/u);
if (5 !== fields.length) {
if (5 === fields.length) {
// Ajouter la valeur "0" pour les secondes (car elles ne sont pas
// renseignées).
fields.unshift("0");
} else if (6 !== fields.length) {
throw new Error(ERROR + pattern);
}

// Figer la date courante pour remplacer tous les éventuelles "?" par les
// mêmes valeurs.
const now = new Date();

// Parcourir les cinq champs.
const [minutes, hours, date, month, day] = fields.map((field, index) => {
return Field.flat(
// Parcourir les sous-champs.
field.split(",").map((subfield) => {
for (const [format, complements] of FORMATS) {
const result = format.exec(subfield);
if (null !== result) {
const parts = {
...result.groups,
...complements,
};
return parseField(parts, index, now, pattern);
// Parcourir les six champs.
const [seconds, minutes, hours, date, month, day] = fields.map(
(field, index) =>
Field.flat(
// Parcourir les sous-champs.
field.split(",").map((subfield) => {
for (const [format, complements] of FORMATS) {
const result = format.exec(subfield);
if (null !== result) {
const parts = {
...result.groups,
...complements,
};
return parseField(parts, index, now, pattern);
}
}
}
throw new Error(ERROR + pattern);
}),
);
});
throw new Error(ERROR + pattern);
}),
),
);

// Récupérer le nombre maximum de jours du mois le plus long parmi tous
// les mois autorisés.
Expand All @@ -308,6 +317,7 @@ export default function parse(pattern) {
}

return {
seconds,
minutes,
hours,
date,
Expand Down
Loading

0 comments on commit c966de3

Please sign in to comment.