我有一个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时间戳。
是否有一种明显的方法可以在下一次(在给定的时间戳之后)找到(计算)作业将要执行的时间?
我正在使用PHP,但问题应该与语言无关。
[更新]
类“PHP Cron Parser”(Ray建议)计算CRON作业应该执行的最后时间,而不是下次。
为了更容易:在我的情况下,cron时间参数只是绝对的,单个数字或“*”。没有时间范围,也没有“* / 5”间隔。
答案 0 :(得分:31)
这是一个基于dlamblin的伪代码的PHP项目。
它可以计算CRON表达式的下一个运行日期,CRON表达式的上一个运行日期,并确定CRON表达式是否与给定时间匹配。您可以跳过此CRON表达式解析器完全实现CRON:
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();
答案 1 :(得分:23)
这基本上反过来检查当前时间是否符合条件。所以像:
//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;
}
我可能已经写了一点倒退。如果在每个主要部分中,如果不是大于检查而只是将当前时间等级增加1并将较小的时间等级设置为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;
}
答案 2 :(得分:8)
对于任何感兴趣的人,这是我的最终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('s i G j n 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('Y-m-d H:i:s') . '<hr>' . date('Y-m-d 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('Y-m-d H:i:s', $cron->timestamp) . '<hr>';
答案 3 :(得分:6)
使用此功能:
function parse_crontab($time, $crontab)
{$time=explode(' ', date('i G j n 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 j n 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
}
答案 4 :(得分:4)
检查this out:
它可以根据给定的cron定义计算下次计划作业的运行时间。
答案 5 :(得分:4)
根据@dlamblin的想法创建了用于计算下一次运行时间的JavaScript API。支持秒和年。还没有设法完全测试它所以期待错误,但如果发现任何问题,请告诉我。
答案 6 :(得分:2)
感谢您发布此代码。它确实帮助了我,即使是6年之后。
尝试实施我发现了一个小错误。
date('i G j n w', $time)
返回分钟的0填充整数。
稍后在代码中,它对该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填充。
所以,我最后只是更改了日期函数返回的原始值,并通过运行$time[0] = $time[0] + 0;
删除了0。
这是我的测试。
<?php
function parse_crontab($frequency='* * * * *', $time=false) {
$time = is_string($time) ? strtotime($time) : time();
$time = explode(' ', date('i G j n 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";
}
}
}
?>
答案 7 :(得分:2)
我的回答不是唯一的。只是用java编写的@BlaM答案的复制品,因为PHP的日期和时间与Java略有不同。
该程序假定CRON表达式很简单。它只能包含数字或*。
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.
//e.g. 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;
}
}