Создание даты "Следующее время выполнения" из синтаксиса cronjob-like

В приложении, которое я создаю, пользователю предоставляется возможность планировать повторяющуюся задачу. Просто значения для генерации интервальной схемы:

Minute: [0-59, 90 (each minute)]
Hour: [0-23, 90 (each hour)]
Day of month: [1-31, 90 (each day of month), 91 (last day of month)]
Month: [1-12, 90 (each month)]

Итак, например, у меня этот формат: 10 - 2 - 90 - 90, что соответствует 2015-07-16 2:10. Метод, определяющий следующую дату выполнения, может представить мне эту дату. Но я ищу эффективный способ проверить, прошла ли уже следующая дата выполнения (простая часть), но затем снова сгенерирует первую следующую дату выполнения. В этом случае это будет 2015-07-17 2:10.

Я узнал, что наш мозг легко справляется с задачей, но я не уверен, какие логические шаги следует предпринять, чтобы определить это наиболее эффективным способом (не выписывая всех возможных возможностей).

Любые предложения?

Ответы

Ответ 1

Честно говоря, я был не вполне доволен принятым и вознагражденным ответом, потому что я хотел сам понять это, не закончив с внешней библиотекой и переписывая свой код.

Учитывая мой синтаксис cron как описано в вопросе, я создал функцию для определения следующего времени выполнения. Я хотел бы поделиться им с намерением помочь другим в будущем, если вы столкнулись с аналогичной проблемой. Возможно, это указывает на то, что вам нужно.

Некоторые демонстрационные примеры

$cases = [
    // Every minute
    ['i' => 90, 'H' => 90, 'd' => 90, 'm' => 90],
    // Every 5th minute
    ['i' => 5, 'H' => 90, 'd' => 90, 'm' => 90],
    // Every day at 02:15
    ['i' => 15, 'H' => 2, 'd' => 90, 'm' => 90],
    // Every last day of the month at 15:20
    ['i' => 20, 'H' => 15, 'd' => 91, 'm' => 90],
    // June 4th at 12:37
    ['i' => 37, 'H' => 12, 'd' => 4, 'm' => 6],
    // Every minute between 04:00 and 05:00 at every day in October
    ['i' => 90, 'H' => 4, 'd' => 90, 'm' => 10],
];

Функция:

function getNextRunTime($config) {
    $minute = $config['i'];
    $hour   = $config['H'];
    $day    = $config['d'];
    $month  = $config['m'];

    // Get minute
    switch($minute) {
        case 90 :
            $nextMinute = date('i', strtotime('now + 1 minute'));
            break;
        default :
            $nextMinute = $minute;
    }

    // Get hour
    switch($hour) {
        case 90 :
            if($minute == 90 || $nextMinute > date('i')) {
                $nextHour = date('H');
            } else {
                $nextHour = date('H', strtotime('now + 1 hour'));
            }
            break;
        default :
            $nextHour = $hour;
    }

    // Get day
    switch($day) {
        case 90 :
            if($hour == 90 && $nextHour > date('H')) {
                $nextDay = date('d');
            } elseif($hour <> 90 && $nextHour <= date('H')) {
                $nextDay = date('d', strtotime('now + 1 day'));
            } else {
                if($nextHour > date('H')) {
                    $nextDay = date('d');
                } else {
                    if ($nextMinute > date('i')) {
                        $nextDay = date('d');
                    } else {
                        $nextDay = date('d', strtotime('now + 1 day'));
                    }
                }
            }
            break;
        case 91 :
            if(date('t') == date('d')) {
                if($nextHour > date('H')) {
                    $nextDay = date('d');
                } elseif($nextHour == date('H') && $nextMinute > date('i')) {
                    $nextDay = date('d');
                } else {
                    $nextDay = date('t', strtotime('now + 1 month'));
                }
            } else {
                $nextDay = date('t');
            }
            break;
        default :
            $nextDay = $day;
    }

    // Get month
    switch($month) {
        case 90 :
            if($day == 90 || $nextDay > date('d')) {
                $nextMonth = date('m');
            } elseif($nextDay == date('d')) {
                if($hour == 90 || $nextHour > date('H')) {
                    $nextMonth = date('m');
                } elseif($nextHour == date('H')) {
                    if($minute == 90 || $nextMinute > date('i')) {
                        $nextMonth = date('m');
                    } else {
                        $nextMonth = date('m', strtotime('now + 1 month'));
                    }
                } else {
                    $nextMonth = date('m', strtotime('now + 1 month'));
                }
            } else {
                $nextMonth = date('m', strtotime('now + 1 month'));
            }
            break;
        default :
            $nextMonth = $month;
    }

    // Get year
    if($month == 90 || $nextMonth > date('m')) {
        $nextYear = date('Y');
    } elseif($nextMonth == date('m')) {
        if($day == 90 || $nextDay > date('d')) {
            $nextYear = date('Y');
        } elseif($nextDay == date('m')) {
            if($hour == 90 || $nextHour > date('H')) {
                $nextYear = date('Y');
            } elseif($nextHour == date('H')) {
                if($minute == 90 || $nextMinute > date('i')) {
                    $nextYear = date('Y');
                } else {
                    $nextYear = date('Y') + 1;
                }
            } else {
                $nextYear = date('Y') + 1;
            }
        } else {
            $nextYear = date('Y') + 1;
        }
    } else {
        $nextYear = date('Y') + 1;
    }

    // Create the timestamp for the 'Next Run Time'
    $nextRunTime = mktime($nextHour, $nextMinute, 0, $nextMonth, $nextDay, $nextYear);

    // Check if the job has to run every minute, maybe a reset to d-m-Y h:00 is possible
    if($nextRunTime > time() && $minute == 90) {
        $tempNextRunTime = mktime($nextHour, 0, 0, $nextMonth, $nextDay, $nextYear);

        if($tempNextRunTime > time()) {
            $nextMinute  = 0;
            $nextRunTime = $tempNextRunTime;
        }
    }

    // Check if the job has to run every hour, maybe a reset to d-m-Y 00:i is possible
    if($nextRunTime > time() && $hour == 90) {
        $tempNextRunTime = mktime(0, $nextMinute, 0, $nextMonth, $nextDay, $nextYear);

        if($tempNextRunTime > time()) {
            $nextHour    = 0;
            $nextRunTime = $tempNextRunTime;
        }
    }

    // Check if the job has to run every day, maybe a reset to 1-m-Y H:i is possible
    if($nextRunTime > time() && $day == 90) {
        $tempNextRunTime = mktime($nextHour, $nextMinute, 0, $nextMonth, 1, $nextYear);

        if($tempNextRunTime > time()) {
            $nextRunTime = $tempNextRunTime;
        }
    }

    // Return the Next Run Time timestamp
    return $nextRunTime;
}

Вывод (время выполнения 23-09-2015, 11:12):

string '23-09-2015, 11:13' (length=17)
string '23-09-2015, 12:05' (length=17)
string '24-09-2015, 02:15' (length=17)
string '30-09-2015, 15:20' (length=17)
string '04-06-2016, 12:37' (length=17)
string '01-10-2015, 04:00' (length=17)

Ответ 2

Он работает нормально. Я использую PHP Cron Parser. В нем есть все, что нам нужно.

Или иначе вы могли бы проще и эффективнее использовать Cron Expression. Некоторые справочные ответы уже здесь и здесь в Stack Overflow.

Для Cron Expression вы можете использовать:

$cron = Cron\CronExpression::factory('@daily');
$cron->isDue();
echo $cron->getNextRunDate()->format('Y-m-d H:i:s');//this give next run date.
//echo $cron->getPreviousRunDate()->format('Y-m-d H:i:s');//this give previous run date.

И это точно то же самое, что и у Cron Expression,

введите описание изображения здесь

Все, что вам нужно сделать, просто используйте PHP Cron Expression. Надеюсь, это поможет.

Ответ 3

Это почти синтаксис Cron, поэтому зачем создавать что-то собственное, которое выполняет точно такую ​​же работу?

Довольно легко перейти к действительному синтаксису cron и использовать простую библиотеку для синтаксического анализа и т.д.

Примером может быть https://github.com/mtdowling/cron-expression, где вы можете получить следующую дату выполнения бесплатно:

$cron->getNextRunDate()->format('Y-m-d H:i:s');

У вас также будет возможность использовать значения, такие как 0/15, для запуска каждые 15 минут и т.д. Другим преимуществом является то, что синтаксис cron является общеизвестным, что предотвращает неправильное толкование формата.

Ответ 4

Я предполагал, что интервал этого времени ежедневно. Если нет, вы можете отрегулировать строку strtotime.

Вы хотите повернуть дату в метку времени, а затем добавить один день на нее. PHP делает это довольно легко.

Предполагая, что вы уже проанализировали дату в международном формате 2015-07-17 2:10, вы можете сделать следующее:

$date = '2015-07-17 2:10'; // Parsed date
$timestamp = strtotime($date);
if($timestamp > time()){
    // Date hasn't hit yet
} else {
    $next_run = strtotime("+1 day", $timestamp);
}

Ответ 5

Отъезд https://github.com/fightbulc/moment.php, он предлагает возможность добавлять/вычитать секунды, минуты, часы, месяц и годы. Например (взято из демонстрационного сайта github):

$m = new \Moment\Moment('2012-05-15T12:30:00', 'CET');
echo $m->addHours(2)->format(); // 2012-05-15T14:30:00+0200

$m = new \Moment\Moment('2012-05-15T12:30:00', 'CET');
echo $m->subtractDays(7)->subtractMinutes(15)->format(); //  2012-05-08T12:15:00+0200

Ответ 6

Зачем беспокоиться о том, запущен ли он или когда его следующее время выполнения?

Вы можете запускать script каждую минуту, проверяя все соответствующие задания, а затем выполняете их?

Не должно быть большой проблемы с написанием SQL-запроса, который соответствует всем возможностям для определенного момента времени:)

Ответ 7

Вы можете просто использовать родную DateTime classe.

$timeString = '2015-07-16 2:10';
$date = DateTime::createFromFormat('Y-m-d H:i', $timeString);
$date->modify('+1 day');

$now = new DateTime();

if ($date > $now) {
    // The date is in the future
} else {
    // The date is today, or is in the past
}

Но если вам нужен настоящий синтаксис cron, ответ by @echox намного лучше.