Calculate when the cron job will run, and then next time

I have a 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) 

And I have a unix timestamp.

Is there an obvious way to find (calculate) the next time (after this given timestamp) the task must be completed?

I use PHP, but the problem should be pretty linguistic agnostic.

[Update]

The " PHP Cron Parser " class (proposed by Rey) calculates the LAST time when the CRON job was supposed to run, not the next time.

Make it easier: in my case, the cron time parameters are absolute, single, or "*". There are no time slots and no "* / 5" intervals.

+44
language-agnostic algorithm cron
Nov 26 '08 at 17:26
source share
8 answers

This basically does a reverse check to see if the current time matches the conditions. so something like:

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

I could write it a little back. It can also be much shorter if, in each main case, instead of doing more than checking, you simply increase the current time class by one and set lower time estimates by 0, then continue; however then you will go in cycles much more. For example:

 //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; } 
+23
Nov 26 '08 at 20:37
source share
β€” -

Here's a PHP project based on the psdo dlamblin code.

He can calculate the next execution date of the CRON expression, the previous execution date of the CRON expression, and determine whether the CRON expression matches the specified time. You may skip this CRON parser that fully implements CRON:

  • Range increase (e.g. * / 12, 3-59 / 15)
  • Intervals (e.g. 1-4, MON-FRI, JAN-MAR).
  • Lists (e.g. 1,2,3 | JAN, MAR, DEC)
  • Last day of the month (e.g. L)
  • Last set business day of the month (e.g. 5L)
  • Nth day of the week of the month (e.g. 3 # 2, 1 # 1, MON # 4)
  • The closest weekday to this day of the month (e.g. 15 W, 1 W, 30 W).

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

Usage (PHP 5.3 +):

 <?php // Works with predefined scheduling definitions $cron = Cron\CronExpression::factory('@daily'); $cron->isDue(); $cron->getNextRunDate(); $cron->getPreviousRunDate(); // Works with complex expressions $cron = Cron\CronExpression::factory('15 2,6-12 */15 1 2-5'); $cron->getNextRunDate(); 
+30
Aug 10 '10 at 10:10
source share

For anyone interested, here is my final PHP implementation, which pretty much equals the dlamblin pseudo-code:

 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( i 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') . '<hr>' . date('Ymd H:i:s', $cron->timestamp) . '<hr>'; $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) . '<hr>'; 
+8
Nov 27 '08 at 13:32
source share

Use this function:

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

Edit Perhaps this is more readable:

 <?php function parse_crontab($frequency='* * * * *', $time=false) { $time = is_string($time) ? strtotime($time) : time(); $time = explode(' ', date('i G jn w', $time)); $crontab = explode(' ', $frequency); foreach ($crontab as $k => &$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};"); } 

Using:

 <?php if (parse_crontab('*/5 2 * * *')) { // should run cron } else { // should not run cron } 
+6
Apr 20 '11 at 8:15
source share

Check this option :

It can calculate the next time a scheduled task should be executed based on given cron definitions.
+4
Nov 26 '08 at 17:28
source share

Created a javascript API to calculate the next runtime based on @dlamblin's idea Supports seconds and years. It was not possible to verify it completely, but so expect errors, but let me know if you find it.

Repository Link: https://bitbucket.org/nevity/cronner

+4
Jun 26 '14 at 9:25
source share

Thanks for posting this code. It definitely helped me, even after 6 years.

Trying to implement, I found a small error.

date('i G jn w', $time) returns 0 the completed integer for minutes.

Later in the code, it has a module on this 0 filled integer. PHP does not seem to handle this as expected.

 $ php <?php print 8 % 5 . "\n"; print 08 % 5 . "\n"; ?> 3 0 

As you can see, 08 % 5 returns 0, while 8 % 5 returns the expected value of 3. I could not find an optional option for the date command. I tried to work with the line {$time[$k]} % $1 === 0 (for example, changing {$time[$k]} to ({$time[$k]}+0) , but could not force reset it to zero during module.

So, I just changed the original value returned by the date function and deleted 0 by running $time[0] = $time[0] + 0; .

Here is my test.

 <?php function parse_crontab($frequency='* * * * *', $time=false) { $time = is_string($time) ? strtotime($time) : time(); $time = explode(' ', date('i G jn w', $time)); $time[0] = $time[0] + 0; $crontab = explode(' ', $frequency); foreach ($crontab as $k => &$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"; } } } ?> 
+2
Jan 30 '15 at 18:37
source share

My answer is not unique. Just a copy of @BlaM's answer is written in java, because the date and time of PHP is slightly different from Java.

This program assumes that the expression CRON is simple. It can only contain numbers or *.

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

the 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(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 month start 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; } } 
+1
May 14 '15 at 14:37
source share



All Articles