计算下一次执行cron作业的时间
我有一个cron“时间定义”
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)
我有一个unix时间戳。
有没有一种明显的方法来find(计算)下一次(在给定的时间戳之后)作业是否将被执行?
我正在使用PHP,但问题应该是相当语言不可知的。
[更新]
“ PHP Cron Parser ”类(由Raybuild议)计算CRON作业应该执行的最后时间,而不是下一次。
为了更容易:在我的情况下,cron时间参数只是绝对的,单个数字或“*”。 没有时间范围,也没有“* / 5”间隔。
这基本上是检查当前时间是否符合条件。 所以像这样:
//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; }
我可能已经写了一些倒退。 如果在每一个主要的情况下,而不是做大于检查你只是递增当前时间等级一个,并设置较小的时间等级为0,然后继续; 然而,那么你会循环更多。 像这样:
//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; }
这是一个基于dlamblin的伪代码的PHP项目。
它可以计算CRONexpression式的下一个运行date,CRONexpression式的上一个运行date,并确定CRONexpression式是否与给定时间相匹配。 你可以跳过这个CRONexpression式parsing器完全实现了CRON:
- 范围的增量(例如* / 12,3-59 / 15)
- 间隔(例如1-4,MON-FRI,JAN-MAR)
- 列表(例如1,2,3 | JAN,MAR,DEC)
- 一个月的最后一天(如L)
- 最后一个月的工作日(如5L)
- 第N个给定的工作日(例如3#2,1#1,MON#4)
- 最近一个星期到当月的某一天(例如15W,1W,30W)
https://github.com/mtdowling/cron-expression
用法(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();
对于任何感兴趣的人来说,这里是我最后的PHP实现,这几乎等于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') . '<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>';
使用这个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 *'));
编辑也许这是更可读:
<?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};"); }
用法:
<?php if (parse_crontab('*/5 2 * * *')) { // should run cron } else { // should not run cron }
看看这个 :
它可以根据给定的cron定义计算下一次计划的作业应该运行。
基于@dlamblin的想法创build了JavaScript API来计算下一次运行时间。 支持秒和年。 还没有设法完全testing它,所以期待错误,但让我知道,如果find任何。
存储库链接: https : //bitbucket.org/nevity/cronner
感谢张贴这个代码。 这确实帮助我,甚至6年后。
试图实现我发现了一个小错误。
date('i G jn w', $time)
为分钟返回一个0填充的整数。
在后面的代码中,它在该填充的整数上做了一个模数。 PHP似乎并没有像预期的那样处理。
$ php <?php print 8 % 5 . "\n"; print 08 % 5 . "\n"; ?> 3 0
正如你所看到的, 08 % 5
返回0,而8 % 5
返回预期的3.我找不到date命令的非填充选项。 我试着摆弄{$time[$k]} % $1 === 0
行(如将{$time[$k]}
更改为({$time[$k]}+0)
,但无法获得它在模数下降0填充。
所以,我最终只改变了由date函数返回的原始值,并通过运行$time[0] = $time[0] + 0;
删除了$time[0] = $time[0] + 0;
。
这是我的testing。
<?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"; } } } ?>
我的答案不是唯一的。 只是用java编写的@BlaM答案的副本,因为PHP的date和时间与Java有点不同。
这个程序假定CRONexpression很简单。 它只能包含数字或*。
Minute = 0-60 Hour = 0-23 Day = 1-31 MONTH = 1-12 where 1 = January. WEEKDAY = 1-7 where 1 = Sunday.
码:
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; } }