Calcola quando verrà eseguito un cron job la prossima volta

Ho un cron “definizione temporale”

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) 

E ho un timestamp unix.

C’è un modo ovvio per trovare (calcolare) la prossima volta (dopo quella data data) il lavoro deve essere eseguito?

Sto usando PHP, ma il problema dovrebbe essere abbastanza indipendente dal linguaggio.

[Aggiornare]

La class ” PHP Cron Parser ” (suggerita da Ray) calcola l’ULTIMA volta in cui il lavoro CRON doveva essere eseguito, non la volta successiva.

Per semplificare: nel mio caso i parametri cron sono solo numeri assoluti, singoli o “*”. Non ci sono intervalli di tempo e nessun “* / 5” intervalli.

Questo è fondamentalmente facendo il contrario di controllare se l’ora corrente si adatta alle condizioni. quindi qualcosa come:

 //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; } 

Potrei averlo scritto un po ‘indietro. Inoltre può essere molto più breve se in ogni main se invece di fare il più grande di un semplice check incrementa il grado temporale corrente di uno e imposta il minor time time su 0 e poi continua; tuttavia, continuerai ad andare molto di più. Così:

 //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; } 

Ecco un progetto PHP basato sul codice psuedo di dlamblin.

Può calcolare la successiva data di esecuzione di un’espressione CRON, la precedente data di esecuzione di un’espressione CRON e determinare se un’espressione CRON corrisponde a una determinata ora. È ansible saltare Questo parser di espressioni CRON implementa completamente CRON:

  1. Incrementi di intervalli (ad es. * / 12, 3-59 / 15)
  2. Intervalli (es. 1-4, MON-FRI, JAN-MAR)
  3. Elenchi (ad es. 1,2,3 | JAN, MAR, DEC)
  4. L’ultimo giorno di un mese (ad es. L)
  5. Ultimo giorno della settimana di un mese (ad es. 5L)
  6. Nº giorno della settimana di un mese (ad es. 3 # 2, 1 # 1, MON # 4)
  7. Il giorno della settimana più vicino a un determinato giorno del mese (ad es. 15W, 1W, 30W)

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

Utilizzo (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(); 

Per chiunque sia interessato, ecco la mia implementazione PHP finale, che equivale praticamente allo pseudo codice 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) . '
';

Usa questa funzione:

 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 *')); 

Modifica Forse questo è più leggibile:

  &$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};"); } 

Uso:

  

Controlla questo :

Può calcolare la prossima volta che un lavoro pianificato deve essere eseguito in base alle definizioni cron fornite.

Created javascript API per calcolare il prossimo run time basato sull’idea @dlamblin. Supporta secondi e anni. Non sono ancora riuscito a testarlo completamente, quindi aspettati bug ma fammi sapere se ne trovi.

Link al repository: https://bitbucket.org/nevity/cronner

Grazie per aver pubblicato questo codice. Mi ha sicuramente aiutato, anche 6 anni dopo.

Cercando di implementare ho trovato un piccolo bug.

date('i G jn w', $time) restituisce un intero 0 riempito per i minuti.

Più avanti nel codice, fa un modulo su quel intero 0 riempito. PHP non sembra gestirlo come previsto.

 $ php  3 0 

Come puoi vedere, l’ 08 % 5 restituisce 0, mentre l’ 8 % 5 restituisce il valore atteso 3. Non sono riuscito a trovare un’opzione non riempita per il comando data. Ho provato a giocherellare con la riga {$time[$k]} % $1 === 0 (come cambiare {$time[$k]} in ({$time[$k]}+0) , ma non ho potuto ottenere per far cadere il padding 0 durante il modulo.

Così, ho finito solo cambiando il valore originale restituito dalla funzione data e rimosso lo 0 eseguendo $time[0] = $time[0] + 0; .

Ecco il mio 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"; } } } ?> 

La mia risposta non è unica. Solo una replica della risposta @BlaM scritta in java perché data e ora di PHP sono leggermente diverse da Java.

Questo programma presuppone che l’espressione CRON sia semplice. Può contenere solo cifre o *.

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

Codice:

 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(String[] args) { String cronExpression = "5 3 27 3 3 ls -la > a.txt"; CronPredict cronPredict = new CronPredict(); String[] parsed = cronPredict.parseCronExpression(cronExpression); System.out.println(cronPredict.getNextExecution(parsed).getTime().toString()); } //This method takes a cron string and separates entities like minutes, hours, etc. public String[] parseCronExpression(String cronExpression) { String[] parsedExpression = null; String 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.compile(cronPattern); Matcher matcher = cronRegex.matcher(cronExpression); if(matcher.matches()) { String minute = matcher.group(1); String hour = matcher.group(2); String day = matcher.group(3); String month = matcher.group(4); String weekday = matcher.group(5); String command = matcher.group(6); parsedExpression = new String[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(String[] 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; } }