Calculer quand une tâche cron sera exécutée puis la prochaine fois

J’ai un cron “time definition”

1 * * * * (every hour at xx:01) 2 5 * * * (every day at 05:02) 0 4 3 * * (every third day of the month at 04:00) * 2 * * 5 (every minute between 02:00 and 02:59 on fridays) 

Et j’ai un horodatage unix.

Existe-t-il un moyen évident de trouver (calculer) la prochaine fois (après cet horodatage) que le travail doit être exécuté?

J’utilise PHP, mais le problème devrait être assez indépendant de la langue.

[Mettre à jour]

La classe ” PHP Cron Parser ” (suggérée par Ray) calcule la DERNIERE fois que le travail CRON était censé être exécuté, pas la prochaine fois.

Pour faciliter les choses: Dans mon cas, les parameters de cron time ne sont que des nombres absolus, simples ou “*”. Il n’y a pas de plage de temps ni d’intervalle “* / 5”.

Cela revient à vérifier si l’heure actuelle correspond aux conditions. alors quelque chose comme:

 //Totaly made up language next = getTimeNow(); next.addMinutes(1) //so that next is never now done = false; while (!done) { if (cron.minute != '*' && next.minute != cron.minute) { if (next.minute > cron.minute) { next.addHours(1); } next.minute = cron.minute; } if (cron.hour != '*' && next.hour != cron.hour) { if (next.hour > cron.hour) { next.hour = cron.hour; next.addDays(1); next.minute = 0; continue; } next.hour = cron.hour; next.minute = 0; continue; } if (cron.weekday != '*' && next.weekday != cron.weekday) { deltaDays = cron.weekday - next.weekday //assume weekday is 0=sun, 1 ... 6=sat if (deltaDays < 0) { deltaDays+=7; } next.addDays(deltaDays); next.hour = 0; next.minute = 0; continue; } if (cron.day != '*' && next.day != cron.day) { if (next.day > cron.day || !next.month.hasDay(cron.day)) { next.addMonths(1); next.day = 1; //assume days 1..31 next.hour = 0; next.minute = 0; continue; } next.day = cron.day next.hour = 0; next.minute = 0; continue; } if (cron.month != '*' && next.month != cron.month) { if (next.month > cron.month) { next.addMonths(12-next.month+cron.month) next.day = 1; //assume days 1..31 next.hour = 0; next.minute = 0; continue; } next.month = cron.month; next.day = 1; next.hour = 0; next.minute = 0; continue; } done = true; } 

J’aurais peut-être écrit ça un peu en arrière. En outre, il peut être beaucoup plus court si dans chaque main si au lieu de faire le plus grand que de vérifier, il vous suffit d’incrémenter le niveau de temps actuel de 1 et de définir les notes de temps inférieures à 0 puis de continuer; Cependant, vous serez en boucle beaucoup plus. Ainsi:

 //Shorter more loopy version next = getTimeNow().addMinutes(1); while (true) { if (cron.month != '*' && next.month != cron.month) { next.addMonths(1); next.day = 1; next.hour = 0; next.minute = 0; continue; } if (cron.day != '*' && next.day != cron.day) { next.addDays(1); next.hour = 0; next.minute = 0; continue; } if (cron.weekday != '*' && next.weekday != cron.weekday) { next.addDays(1); next.hour = 0; next.minute = 0; continue; } if (cron.hour != '*' && next.hour != cron.hour) { next.addHours(1); next.minute = 0; continue; } if (cron.minute != '*' && next.minute != cron.minute) { next.addMinutes(1); continue; } break; } 

Voici un projet PHP basé sur le code pseudo de dlamblin.

Il peut calculer la prochaine date d’exécution d’une expression CRON, la date d’exécution précédente d’une expression CRON, et déterminer si une expression CRON correspond à une heure donnée. Vous pouvez ignorer cet parsingur d’expression CRON qui implémente complètement CRON:

  1. Incréments de gammes (par exemple * / 12, 3-59 / 15)
  2. Intervalles (par exemple, 1-4, MON-FRI, JAN-MAR)
  3. Listes (par exemple 1,2,3 | JAN, MAR, DEC)
  4. Dernier jour d’un mois (par ex. L)
  5. Dernier jour de semaine du mois (par exemple 5L)
  6. Nième jour de semaine donné d’un mois (par ex. 3 # 2, 1 # 1, MON # 4)
  7. Jour de la semaine le plus proche d’un jour donné du mois (par exemple 15W, 1W, 30W)

https://github.com/mtdowling/cron-expression

Utilisation (PHP 5.3+):

 isDue(); $cron->getNextRunDate(); $cron->getPreviousRunDate(); // Works with complex expressions $cron = Cron\CronExpression::factory('15 2,6-12 */15 1 2-5'); $cron->getNextRunDate(); 

Pour toute personne intéressée, voici ma dernière implémentation PHP, qui correspond à peu près au pseudo-code dlamblin:

 class myMiniDate { var $myTimestamp; static private $dateComponent = array( 'second' => 's', 'minute' => 'i', 'hour' => 'G', 'day' => 'j', 'month' => 'n', 'year' => 'Y', 'dow' => 'w', 'timestamp' => 'U' ); static private $weekday = array( 1 => 'monday', 2 => 'tuesday', 3 => 'wednesday', 4 => 'thursday', 5 => 'friday', 6 => 'saturday', 0 => 'sunday' ); function __construct($ts = NULL) { $this->myTimestamp = is_null($ts)?time():$ts; } function __set($var, $value) { list($c['second'], $c['minute'], $c['hour'], $c['day'], $c['month'], $c['year'], $c['dow']) = explode(' ', date('si G jn Y w', $this->myTimestamp)); switch ($var) { case 'dow': $this->myTimestamp = strtotime(self::$weekday[$value], $this->myTimestamp); break; case 'timestamp': $this->myTimestamp = $value; break; default: $c[$var] = $value; $this->myTimestamp = mktime($c['hour'], $c['minute'], $c['second'], $c['month'], $c['day'], $c['year']); } } function __get($var) { return date(self::$dateComponent[$var], $this->myTimestamp); } function modify($how) { return $this->myTimestamp = strtotime($how, $this->myTimestamp); } } $cron = new myMiniDate(time() + 60); $cron->second = 0; $done = 0; echo date('Ymd H:i:s') . '
' . date('Ymd H:i:s', $cron->timestamp) . '
'; $Job = array( 'Minute' => 5, 'Hour' => 3, 'Day' => 13, 'Month' => null, 'DOW' => 5, ); while ($done < 100) { if (!is_null($Job['Minute']) && ($cron->minute != $Job['Minute'])) { if ($cron->minute > $Job['Minute']) { $cron->modify('+1 hour'); } $cron->minute = $Job['Minute']; } if (!is_null($Job['Hour']) && ($cron->hour != $Job['Hour'])) { if ($cron->hour > $Job['Hour']) { $cron->modify('+1 day'); } $cron->hour = $Job['Hour']; $cron->minute = 0; } if (!is_null($Job['DOW']) && ($cron->dow != $Job['DOW'])) { $cron->dow = $Job['DOW']; $cron->hour = 0; $cron->minute = 0; } if (!is_null($Job['Day']) && ($cron->day != $Job['Day'])) { if ($cron->day > $Job['Day']) { $cron->modify('+1 month'); } $cron->day = $Job['Day']; $cron->hour = 0; $cron->minute = 0; } if (!is_null($Job['Month']) && ($cron->month != $Job['Month'])) { if ($cron->month > $Job['Month']) { $cron->modify('+1 year'); } $cron->month = $Job['Month']; $cron->day = 1; $cron->hour = 0; $cron->minute = 0; } $done = (is_null($Job['Minute']) || $Job['Minute'] == $cron->minute) && (is_null($Job['Hour']) || $Job['Hour'] == $cron->hour) && (is_null($Job['Day']) || $Job['Day'] == $cron->day) && (is_null($Job['Month']) || $Job['Month'] == $cron->month) && (is_null($Job['DOW']) || $Job['DOW'] == $cron->dow)?100:($done+1); } echo date('Ymd H:i:s', $cron->timestamp) . '
';

Utilisez cette fonction:

 function parse_crontab($time, $crontab) {$time=explode(' ', date('i G jn w', strtotime($time))); $crontab=explode(' ', $crontab); foreach ($crontab as $k=>&$v) {$v=explode(',', $v); foreach ($v as &$v1) {$v1=preg_replace(array('/^\*$/', '/^\d+$/', '/^(\d+)\-(\d+)$/', '/^\*\/(\d+)$/'), array('true', '"'.$time[$k].'"==="\0"', '(\1<='.$time[$k].' and '.$time[$k].'<=\2)', $time[$k].'%\1===0'), $v1 ); } $v='('.implode(' or ', $v).')'; } $crontab=implode(' and ', $crontab); return eval('return '.$crontab.';'); } var_export(parse_crontab('2011-05-04 02:08:03', '*/2,3-5,9 2 3-5 */2 *')); var_export(parse_crontab('2011-05-04 02:08:03', '*/8 */2 */4 */5 *')); 

Modifier C'est peut-être plus lisible:

  &$v) { $v = explode(',', $v); $regexps = array( '/^\*$/', # every '/^\d+$/', # digit '/^(\d+)\-(\d+)$/', # range '/^\*\/(\d+)$/' # every digit ); $content = array( "true", # every "{$time[$k]} === 0", # digit "($1 <= {$time[$k]} && {$time[$k]} <= $2)", # range "{$time[$k]} % $1 === 0" # every digit ); foreach ($v as &$v1) $v1 = preg_replace($regexps, $content, $v1); $v = '('.implode(' || ', $v).')'; } $crontab = implode(' && ', $crontab); return eval("return {$crontab};"); } 

Usage:

  

Vérifiez ceci :

Il peut calculer la prochaine fois qu’un travail planifié est censé être exécuté en fonction des définitions cron données.

Création de l’API JavaScript pour calculer la prochaine exécution en fonction de l’idée @dlamblin. Prend en charge les secondes et les années. Je n’ai pas encore réussi à le tester complètement, alors attendez-vous à des bugs, mais laissez-moi savoir si vous en trouvez.

Lien vers le repository: https://bitbucket.org/nevity/cronner

Merci d’avoir posté ce code. Cela m’a définitivement aidé, même 6 ans plus tard.

Essayer d’implémenter j’ai trouvé un petit bogue.

date('i G jn w', $time) renvoie un entier 0 pour les minutes.

Plus tard dans le code, il fait un module sur cet entier 0 capitonné. PHP ne semble pas gérer cela comme prévu.

 $ php  3 0 

Comme vous pouvez le constater, 08 % 5 renvoie 0, tandis que 8 % 5 renvoie la valeur 3. Je n’ai pas trouvé d’option non remplie pour la commande de date. J’ai essayé de sortingpoter la ligne {$time[$k]} % $1 === 0 (comme changer {$time[$k]} en ({$time[$k]}+0) , mais je n’ai pas pu obtenir il laisse tomber le remplissage 0 pendant le module.

Donc, j’ai fini par changer la valeur d’origine renvoyée par la fonction de date et j’ai supprimé le 0 en exécutant $time[0] = $time[0] + 0; .

Voici mon test

  &$v) { $v = explode(',', $v); $regexps = array( '/^\*$/', # every '/^\d+$/', # digit '/^(\d+)\-(\d+)$/', # range '/^\*\/(\d+)$/' # every digit ); $content = array( "true", # every "{$time[$k]} === $0", # digit "($1 <= {$time[$k]} && {$time[$k]} <= $2)", # range "{$time[$k]} % $1 === 0" # every digit ); foreach ($v as &$v1) $v1 = preg_replace($regexps, $content, $v1); $v = '('.implode(' || ', $v).')'; } $crontab = implode(' && ', $crontab); return eval("return {$crontab};"); } for($i=0; $i<24; $i++) { for($j=0; $j<60; $j++) { $date=sprintf("%d:%02d",$i,$j); if (parse_crontab('*/5 * * * *',$date)) { print "$date yes\n"; } else { print "$date no\n"; } } } ?> 

Ma réponse n’est pas unique. Juste une réplique de la réponse @BlaM écrite en Java car la date et l’heure de PHP sont un peu différentes de celles de Java.

Ce programme suppose que l’expression CRON est simple. Il ne peut contenir que des chiffres ou *.

 Minute = 0-60 Hour = 0-23 Day = 1-31 MONTH = 1-12 where 1 = January. WEEKDAY = 1-7 where 1 = Sunday. 

Code:

 package main; import java.util.Calendar; import java.util.Date; import java.util.regex.Matcher; import java.util.regex.Pattern; public class CronPredict { public static void main(Ssortingng[] args) { Ssortingng cronExpression = "5 3 27 3 3 ls -la > a.txt"; CronPredict cronPredict = new CronPredict(); Ssortingng[] parsed = cronPredict.parseCronExpression(cronExpression); System.out.println(cronPredict.getNextExecution(parsed).getTime().toSsortingng()); } //This method takes a cron ssortingng and separates entities like minutes, hours, etc. public Ssortingng[] parseCronExpression(Ssortingng cronExpression) { Ssortingng[] parsedExpression = null; Ssortingng cronPattern = "^([0-9]|[1-5][0-9]|\\*)\\s([0-9]|1[0-9]|2[0-3]|\\*)\\s" + "([1-9]|[1-2][0-9]|3[0-1]|\\*)\\s([1-9]|1[0-2]|\\*)\\s" + "([1-7]|\\*)\\s(.*)$"; Pattern cronRegex = Pattern.comstack(cronPattern); Matcher matcher = cronRegex.matcher(cronExpression); if(matcher.matches()) { Ssortingng minute = matcher.group(1); Ssortingng hour = matcher.group(2); Ssortingng day = matcher.group(3); Ssortingng month = matcher.group(4); Ssortingng weekday = matcher.group(5); Ssortingng command = matcher.group(6); parsedExpression = new Ssortingng[6]; parsedExpression[0] = minute; parsedExpression[1] = hour; parsedExpression[2] = day; //since java's month start's from 0 as opposed to PHP which starts from 1. parsedExpression[3] = month.equals("*") ? month : (Integer.parseInt(month) - 1) + ""; parsedExpression[4] = weekday; parsedExpression[5] = command; } return parsedExpression; } public Calendar getNextExecution(Ssortingng[] job) { Calendar cron = Calendar.getInstance(); cron.add(Calendar.MINUTE, 1); cron.set(Calendar.MILLISECOND, 0); cron.set(Calendar.SECOND, 0); int done = 0; //Loop because some dates are not valid. //eg March 29 which is a Friday may never come for atleast next 1000 years. //We do not want to keep looping. Also it protects against invalid dates such as feb 30. while(done < 100) { if(!job[0].equals("*") && cron.get(Calendar.MINUTE) != Integer.parseInt(job[0])) { if(cron.get(Calendar.MINUTE) > Integer.parseInt(job[0])) { cron.add(Calendar.HOUR_OF_DAY, 1); } cron.set(Calendar.MINUTE, Integer.parseInt(job[0])); } if(!job[1].equals("*") && cron.get(Calendar.HOUR_OF_DAY) != Integer.parseInt(job[1])) { if(cron.get(Calendar.HOUR_OF_DAY) > Integer.parseInt(job[1])) { cron.add(Calendar.DAY_OF_MONTH, 1); } cron.set(Calendar.HOUR_OF_DAY, Integer.parseInt(job[1])); cron.set(Calendar.MINUTE, 0); } if(!job[4].equals("*") && cron.get(Calendar.DAY_OF_WEEK) != Integer.parseInt(job[4])) { Date previousDate = cron.getTime(); cron.set(Calendar.DAY_OF_WEEK, Integer.parseInt(job[4])); Date newDate = cron.getTime(); if(newDate.before(previousDate)) { cron.add(Calendar.WEEK_OF_MONTH, 1); } cron.set(Calendar.HOUR_OF_DAY, 0); cron.set(Calendar.MINUTE, 0); } if(!job[2].equals("*") && cron.get(Calendar.DAY_OF_MONTH) != Integer.parseInt(job[2])) { if(cron.get(Calendar.DAY_OF_MONTH) > Integer.parseInt(job[2])) { cron.add(Calendar.MONTH, 1); } cron.set(Calendar.DAY_OF_MONTH, Integer.parseInt(job[2])); cron.set(Calendar.HOUR_OF_DAY, 0); cron.set(Calendar.MINUTE, 0); } if(!job[3].equals("*") && cron.get(Calendar.MONTH) != Integer.parseInt(job[3])) { if(cron.get(Calendar.MONTH) > Integer.parseInt(job[3])) { cron.add(Calendar.YEAR, 1); } cron.set(Calendar.MONTH, Integer.parseInt(job[3])); cron.set(Calendar.DAY_OF_MONTH, 1); cron.set(Calendar.HOUR_OF_DAY, 0); cron.set(Calendar.MINUTE, 0); } done = (job[0].equals("*") || cron.get(Calendar.MINUTE) == Integer.parseInt(job[0])) && (job[1].equals("*") || cron.get(Calendar.HOUR_OF_DAY) == Integer.parseInt(job[1])) && (job[2].equals("*") || cron.get(Calendar.DAY_OF_MONTH) == Integer.parseInt(job[2])) && (job[3].equals("*") || cron.get(Calendar.MONTH) == Integer.parseInt(job[3])) && (job[4].equals("*") || cron.get(Calendar.DAY_OF_WEEK) == Integer.parseInt(job[4])) ? 100 : (done + 1); } return cron; } }