在PHP中计算cron next-run-time

时间:2010-07-26 19:33:01

标签: php cron

我正在自己的个人框架中设计一个任务调度程序,并试图避免不那么灵活的“每隔 n 分钟/小时/天运行”的方法,这种方法会更容易实行。我想做的是模仿cron调度。我已经有功能来分割模式并计算当前下一个日期(一天中的某个日期)的下一个值,但如果有比我正在做的更容易的东西,或者可能更好的话,不想继续前进做我想做的事。

/**
  * Takes pattern(s) for various time attributes and calculates the next time the task should run
  *
  * @param mixed $minute Pattern or value of minute: 0-59 or 15,45 or * or * /5 (every 5 minutes)
  * @param mixed $hour Pattern or value of hour: 0-23 or 0,6,12 or * or * /2 (every 2 hours)
  * @param mixed $date Pattern or value of date: 1-31 or 1,15 or * or * /5 (every 5 days)
  * @param mixed $day Pattern or value of weekday: 0-7 or 0,1,7 or * or * /7 (every 7 days) - takes precedence over date
  * @return string Timestamp of next run time
  */
 public static function calcNextRun( $minute, $hour, $date, $day )
 {
  # Simplest first, if all * then we run every minute. Return timestamp for next whole minute
  if ( $minute == '*' && $hour == '*' && $date == '*' && $day == '*' )
   return mktime( date( "H" ), date( "i" ), 0 ) + 60; # Prettier than time() + 60, isn't that reason enough?

  # Default to current values
  $nextDate = date( "d" );
  $nextMonth = date( "m" );
  $nextYear = date( "Y" );
  $nextDay = date( "N" );
  $nextHour = date( "H" );
  $nextMinute = date( "i" );

  # Calculate month date to run on, using multiple dates in the presence of , or -
  if( strstr( $date, ',' ) || strstr( $date, '-' ) )
  {
   # Variable to determine whether the date has been set or not
   $dateSet = false;

   # Determine if there's a range in thurr
   $rangeExists = ( strstr( $date, '-' ) ) ? true : false ;

   # Set up the $dates array, exploding if multiple values is present
   $dates = array();
   if ( strstr( $date, ',' ) )
    $dates = explode( ',', $date );
   else
    $dates[] = $date;

   # If we have a range(s) present then we expand them into full stuffs
   foreach ( $dates as $key => $val )
    if ( strstr( $val, '-' ) )
     $dates = array_merge( $dates, self::expandRange( $val ) ); # Merge the expanded range into the $dates array

   # Loop through the $dates array and remove any lingering ranges
   foreach ( $dates as $key => $val )
    if ( strstr( $val, '-' ) )
     unset( $dates[ $key ] );

   # Sort the array
   sort( $dates );

   # Determine the next lowest value
   foreach( $dates as $val )
   {
    # If the value is higher than the maximum number of dates this month, lower it to that
    if ( $val > date( "t" ) )
     $val = date( "t" );

    # If $val is higher than today's date, we use that
    if ( $val > date( "d" ) )
    {
     $nextDate = $val;
     $dateSet = true;
     break; # We're done, we have our value
    }
   }

   # If the date has not been set, add one to the month and use the lowest value in the array
   if ( !$dateSet )
   {
    # Increment the month. Maybe the year. Hurr hurr
    if ( $nextMonth == 12 )
    {
     $nextMonth = 1;
     $nextYear++;
    }
    else
     $nextMonth++;

    # Set the next day to the lowest value in the array
    $nextDate = $dates[0];
   }
  }
  elseif ( strstr( $date, '/' ) ) # Every n days
  {
   $parts = explode( '/', $date );
   $numDays = array_pop( $parts );

   # Calculate the timestamp of n days from now
   $nDayTime = time() + ( $numDays * 86400 ); # 86400 seconds in a day

   # Update values of $nextVars
   $nextDate = date( "d", $nDayTime );
   $nextMonth = date( "m", $nDayTime );
   $nextYear = date( "Y", $nDayTime );
   $nextDay = date( "N", $nDayTime );
  }
  elseif ( $date == (int)$date )
  {
   if ( $date < date( "j" ) )
   {
    # Determine if the month pushes into the next year
    if ( $nextMonth == 12 )
    {
     $nextMonth = 1;
     $nextYear++;
    }
    else
     $nextMonth++;
   }

   $nextDate = $date;
  }

  # Return the new timestamp!
  return mktime( $nextHour, $nextMinute, 0, $nextMonth, $nextDate, $nextYear );
 }

 /**
  * Takes a range and returns an array with all values belonging to that range
  *
  * @param string $range Two values split by a hyphen, ie: 1-5, 0-9, etc.
  * @return array Array of values between the two parts of the range
  */
 private static function expandRange( $range )
 {
  # Get the parts of the range
  $range = explode( '-', $range );

  # Sort just in case the range is handed to us backwards. <_<
  sort( $range );

  # Set up our return array
  $returnArray = array();

  # Populate the return array with all values between min and max
  for($i=$range[0];$i<=$range[1];$i++)
   $returnArray[] = $i;

  return $returnArray;
 }

我不介意使用cron使用的所有五个参数,但无论哪种方式,我都希望能够轻松计算与提供的模式匹配的下一个时间戳。

有没有人有任何建议来完成这个?我正在考虑创建一个函数,它将采用一种模式(1-7,10,15或* / 5或*或其他)和当前的val(当前分钟,一天中的某一天,无论如何)并返回下一个值从该模式匹配或高于当前值。

1 个答案:

答案 0 :(得分:9)

我为PHP创建了一个CRON解析器,可以处理您的调度需求。它支持一切,包括范围增量(3-59 / 12,* / 2),范围(3-5),哈希(3#2),一个月的最后一个工作日/月的最后一天(5L,L),最近工作日到当月的某一天(15W),以及可选的年份字段。

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

用法:

<?php

// Works with predefined scheduling definitions
$cron = Cron\CronExpression::factory('@daily');
$cron->isDue();
$cron->getNextRunDate();
$cron->getPreviousRunDate();

// Works with complex expressions
$cron = new Cron\CronExpression::factory('15 2,6-12 */15 1 *');
$cron->getNextRunDate();

Calculate when a cron job will be executed then next time