518 lines
17 KiB
PHP
518 lines
17 KiB
PHP
<?php
|
|
/**
|
|
* Playcool Project
|
|
*
|
|
* LICENSE
|
|
*
|
|
* http://www.playcool.com/license/ice
|
|
*
|
|
* @category ICE
|
|
* @package ChangeMe
|
|
* @subpackage ChangeMe
|
|
* @copyright Copyright (c) 2008 Zeed Technologies PRC Inc. (http://www.inews.com.cn)
|
|
* @author xSharp ( GTalk: xSharp@gmail.com )
|
|
* @since 2009-11-19
|
|
* @version SVN: $Id$
|
|
*/
|
|
|
|
/* Usage example:
|
|
|
|
$cron_str0 = "0,12,30-51 3,21-23,10 1-25 9-12,1 0,3-7";
|
|
require_once("CronParser.php");
|
|
$cron = new CronParser();
|
|
$cron->calcLastRan($cron_str0);
|
|
// $cron->getLastRanUnix() returns an Unix timestamp
|
|
echo "Cron '$cron_str0' last due at: " . date('r', $cron->getLastRanUnix()) . "<p>";
|
|
// $cron->getLastRan() returns last due time in an array
|
|
print_r($cron->getLastRan());
|
|
echo "Debug:<br>" . nl2br($cron->getDebug());
|
|
|
|
$cron_str1 = "3 12 * * *";
|
|
if ($cron->calcLastRan($cron_str1))
|
|
{
|
|
echo "<p>Cron '$cron_str1' last due at: " . date('r', $cron->getLastRanUnix()) . "<p>";
|
|
print_r($cron->getLastRan());
|
|
}
|
|
else
|
|
{
|
|
echo "Error parsing";
|
|
}
|
|
echo "Debug:<br>" . nl2br($cron->getDebug());
|
|
|
|
*#######################################################################################################
|
|
*/
|
|
|
|
class Zeed_Task_Cron
|
|
{
|
|
|
|
private $bits = Array(); //exploded String like 0 1 * * *
|
|
private $now = Array(); //Array of cron-style entries for time()
|
|
private $lastRan; //Timestamp of last ran time.
|
|
private $taken;
|
|
private $debug;
|
|
private $year;
|
|
private $month;
|
|
private $day;
|
|
private $hour;
|
|
private $minute;
|
|
private $minutes_arr = array(); //minutes array based on cron string
|
|
private $hours_arr = array(); //hours array based on cron string
|
|
private $months_arr = array(); //months array based on cron string
|
|
|
|
/**
|
|
* @param string $cronString
|
|
* @return integer|boolean|mixed $cronString
|
|
*/
|
|
public static function parseCron($cronString = null)
|
|
{
|
|
$cronParser = new self();
|
|
if($cronParser->calcLastRan($cronString)){
|
|
return $cronParser->getLastRanUnix();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function getLastRan()
|
|
{
|
|
return explode(",", strftime("%M,%H,%d,%m,%w,%Y", $this->lastRan)); //Get the values for now in a format we can use
|
|
}
|
|
|
|
/**
|
|
* 获取(理论上)上一次执行的UNIX时间戳
|
|
*
|
|
* @return integer
|
|
*/
|
|
public function getLastRanUnix()
|
|
{
|
|
return $this->lastRan;
|
|
}
|
|
|
|
function getDebug()
|
|
{
|
|
return $this->debug;
|
|
}
|
|
|
|
function debug($str)
|
|
{
|
|
if (is_array($str)) {
|
|
$this->debug .= "\nArray: ";
|
|
foreach ($str as $k => $v) {
|
|
$this->debug .= "$k=>$v, ";
|
|
}
|
|
} else {
|
|
$this->debug .= "\n$str";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assumes that value is not *, and creates an array of valid numbers that
|
|
* the string represents. Returns an array.
|
|
*/
|
|
function expand_ranges($str)
|
|
{
|
|
if (strstr($str, ",")) {
|
|
$arParts = explode(',', $str);
|
|
foreach ($arParts as $part) {
|
|
if (strstr($part, '-')) {
|
|
$arRange = explode('-', $part);
|
|
for($i = $arRange[0]; $i <= $arRange[1]; $i ++) {
|
|
$ret[] = $i;
|
|
}
|
|
} else {
|
|
$ret[] = $part;
|
|
}
|
|
}
|
|
} elseif (strstr($str, '-')) {
|
|
$arRange = explode('-', $str);
|
|
for($i = $arRange[0]; $i <= $arRange[1]; $i ++) {
|
|
$ret[] = $i;
|
|
}
|
|
} else {
|
|
$ret[] = $str;
|
|
}
|
|
$ret = array_unique($ret);
|
|
sort($ret);
|
|
return $ret;
|
|
}
|
|
|
|
function daysinmonth($month, $year)
|
|
{
|
|
return date('t', mktime(0, 0, 0, $month, 1, $year));
|
|
}
|
|
|
|
/**
|
|
* Calculate the last due time before this moment
|
|
*/
|
|
function calcLastRan($string)
|
|
{
|
|
|
|
$tstart = microtime();
|
|
$this->debug = "";
|
|
$this->lastRan = 0;
|
|
$this->year = NULL;
|
|
$this->month = NULL;
|
|
$this->day = NULL;
|
|
$this->hour = NULL;
|
|
$this->minute = NULL;
|
|
$this->hours_arr = array();
|
|
$this->minutes_arr = array();
|
|
$this->months_arr = array();
|
|
|
|
$string = preg_replace('/[\s]{2,}/', ' ', $string);
|
|
|
|
if (preg_match('/[^-,* \\d\/]/', $string) !== 0) {
|
|
$this->debug("Cron String contains invalid character");
|
|
return false;
|
|
}
|
|
|
|
$this->debug("<b>Working on cron schedule: $string</b>");
|
|
$this->bits = @explode(" ", $string);
|
|
|
|
if (count($this->bits) != 5) {
|
|
$this->debug("Cron string is invalid. Too many or too little sections after explode");
|
|
return false;
|
|
}
|
|
|
|
//put the current time into an array
|
|
$t = strftime("%M,%H,%d,%m,%w,%Y", time());
|
|
$this->now = explode(",", $t);
|
|
|
|
$this->year = $this->now[5];
|
|
|
|
$arMonths = $this->_getMonthsArray();
|
|
|
|
do {
|
|
$this->month = array_pop($arMonths);
|
|
} while ($this->month > $this->now[3]);
|
|
|
|
if ($this->month === NULL) {
|
|
$this->year = $this->year - 1;
|
|
$this->debug("Not due within this year. So checking the previous year " . $this->year);
|
|
$arMonths = $this->_getMonthsArray();
|
|
$this->_prevMonth($arMonths);
|
|
} elseif ($this->month == $this->now[3]) { //now Sep, month = array(7,9,12)
|
|
$this->debug("Cron is due this month, getting days array.");
|
|
$arDays = $this->_getDaysArray($this->month, $this->year);
|
|
|
|
do {
|
|
$this->day = array_pop($arDays);
|
|
} while ($this->day > $this->now[2]);
|
|
|
|
if ($this->day === NULL) {
|
|
$this->debug("Smallest day is even greater than today");
|
|
$this->_prevMonth($arMonths);
|
|
} elseif ($this->day == $this->now[2]) {
|
|
$this->debug("Due to run today");
|
|
$arHours = $this->_getHoursArray();
|
|
|
|
do {
|
|
$this->hour = array_pop($arHours);
|
|
} while ($this->hour > $this->now[1]);
|
|
|
|
if ($this->hour === NULL) { // now =2, arHours = array(3,5,7)
|
|
$this->debug("Not due this hour and some earlier hours, so go for previous day");
|
|
$this->_prevDay($arDays, $arMonths);
|
|
} elseif ($this->hour < $this->now[1]) { //now =2, arHours = array(1,3,5)
|
|
$this->minute = $this->_getLastMinute();
|
|
} else { // now =2, arHours = array(1,2,5)
|
|
$this->debug("Due this hour");
|
|
$arMinutes = $this->_getMinutesArray();
|
|
do {
|
|
$this->minute = array_pop($arMinutes);
|
|
} while ($this->minute > $this->now[0]);
|
|
|
|
if ($this->minute === NULL) {
|
|
$this->debug("Not due this minute, so go for previous hour.");
|
|
$this->_prevHour($arHours, $arDays, $arMonths);
|
|
} else {
|
|
$this->debug("Due this very minute or some earlier minutes before this moment within this hour.");
|
|
}
|
|
}
|
|
} else {
|
|
$this->debug("Cron was due on " . $this->day . " of this month");
|
|
$this->hour = $this->_getLastHour();
|
|
$this->minute = $this->_getLastMinute();
|
|
}
|
|
} else { //now Sep, arrMonths=array(7, 10)
|
|
$this->debug("Cron was due before this month. Previous month is: " . $this->year . '-' . $this->month);
|
|
$this->day = $this->_getLastDay($this->month, $this->year);
|
|
if ($this->day === NULL) {
|
|
//No scheduled date within this month. So we will try the previous month in the month array
|
|
$this->_prevMonth($arMonths);
|
|
} else {
|
|
$this->hour = $this->_getLastHour();
|
|
$this->minute = $this->_getLastMinute();
|
|
}
|
|
}
|
|
|
|
$tend = microtime();
|
|
$this->taken = $tend - $tstart;
|
|
$this->debug("Parsing $string taken " . $this->taken . " seconds");
|
|
|
|
//if the last due is beyond 1970
|
|
if ($this->minute === NULL) {
|
|
$this->debug("Error calculating last due time");
|
|
return false;
|
|
} else {
|
|
$this->debug("LAST DUE: " . $this->hour . ":" . $this->minute . " on " . $this->day . "/" . $this->month . "/" . $this->year);
|
|
$this->lastRan = mktime($this->hour, $this->minute, 0, $this->month, $this->day, $this->year);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//get the due time before current month
|
|
private function _prevMonth($arMonths)
|
|
{
|
|
$this->month = array_pop($arMonths);
|
|
if ($this->month === NULL) {
|
|
$this->year = $this->year - 1;
|
|
if ($this->year <= 1970) {
|
|
$this->debug("Can not calculate last due time. At least not before 1970..");
|
|
} else {
|
|
$this->debug("Have to go for previous year " . $this->year);
|
|
$arMonths = $this->_getMonthsArray();
|
|
$this->_prevMonth($arMonths);
|
|
}
|
|
} else {
|
|
$this->debug("Getting the last day for previous month: " . $this->year . '-' . $this->month);
|
|
$this->day = $this->_getLastDay($this->month, $this->year);
|
|
|
|
if ($this->day === NULL) {
|
|
//no available date schedule in this month
|
|
$this->_prevMonth($arMonths);
|
|
} else {
|
|
$this->hour = $this->_getLastHour();
|
|
$this->minute = $this->_getLastMinute();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//get the due time before current day
|
|
private function _prevDay($arDays, $arMonths)
|
|
{
|
|
$this->debug("Go for the previous day");
|
|
$this->day = array_pop($arDays);
|
|
if ($this->day === NULL) {
|
|
$this->debug("Have to go for previous month");
|
|
$this->_prevMonth($arMonths);
|
|
} else {
|
|
$this->hour = $this->_getLastHour();
|
|
$this->minute = $this->_getLastMinute();
|
|
}
|
|
}
|
|
|
|
//get the due time before current hour
|
|
private function _prevHour($arHours, $arDays, $arMonths)
|
|
{
|
|
$this->debug("Going for previous hour");
|
|
$this->hour = array_pop($arHours);
|
|
if ($this->hour === NULL) {
|
|
$this->debug("Have to go for previous day");
|
|
$this->_prevDay($arDays, $arMonths);
|
|
} else {
|
|
$this->minute = $this->_getLastMinute();
|
|
}
|
|
}
|
|
|
|
//not used at the moment
|
|
private function _getLastMonth()
|
|
{
|
|
$months = $this->_getMonthsArray();
|
|
$month = array_pop($months);
|
|
|
|
return $month;
|
|
}
|
|
|
|
private function _getLastDay($month, $year)
|
|
{
|
|
//put the available days for that month into an array
|
|
$days = $this->_getDaysArray($month, $year);
|
|
$day = array_pop($days);
|
|
|
|
return $day;
|
|
}
|
|
|
|
private function _getLastHour()
|
|
{
|
|
$hours = $this->_getHoursArray();
|
|
$hour = array_pop($hours);
|
|
|
|
return $hour;
|
|
}
|
|
|
|
private function _getLastMinute()
|
|
{
|
|
$minutes = $this->_getMinutesArray();
|
|
$minute = array_pop($minutes);
|
|
|
|
return $minute;
|
|
}
|
|
|
|
//remove the out of range array elements. $arr should be sorted already and does not contain duplicates
|
|
private function _sanitize($arr, $low, $high)
|
|
{
|
|
$count = count($arr);
|
|
for($i = 0; $i <= ($count - 1); $i ++) {
|
|
if ($arr[$i] < $low) {
|
|
$this->debug("Remove out of range element. {$arr[$i]} is outside $low - $high");
|
|
unset($arr[$i]);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
for($i = ($count - 1); $i >= 0; $i --) {
|
|
if ($arr[$i] > $high) {
|
|
$this->debug("Remove out of range element. {$arr[$i]} is outside $low - $high");
|
|
unset($arr[$i]);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
//re-assign keys
|
|
sort($arr);
|
|
return $arr;
|
|
}
|
|
|
|
//given a month/year, list all the days within that month fell into the week days list.
|
|
private function _getDaysArray($month, $year = 0)
|
|
{
|
|
if ($year == 0) {
|
|
$year = $this->year;
|
|
}
|
|
|
|
$days = array();
|
|
|
|
//return everyday of the month if both bit[2] and bit[4] are '*'
|
|
if ($this->bits[2] == '*' and $this->bits[4] == '*') {
|
|
$days = $this->getDays($month, $year);
|
|
} else {
|
|
//create an array for the weekdays
|
|
if ($this->bits[4] == '*') {
|
|
for($i = 0; $i <= 6; $i ++) {
|
|
$arWeekdays[] = $i;
|
|
}
|
|
} else {
|
|
$arWeekdays = $this->expand_ranges($this->bits[4]);
|
|
$arWeekdays = $this->_sanitize($arWeekdays, 0, 7);
|
|
|
|
//map 7 to 0, both represents Sunday. Array is sorted already!
|
|
if (in_array(7, $arWeekdays)) {
|
|
if (in_array(0, $arWeekdays)) {
|
|
array_pop($arWeekdays);
|
|
} else {
|
|
$tmp[] = 0;
|
|
array_pop($arWeekdays);
|
|
$arWeekdays = array_merge($tmp, $arWeekdays);
|
|
}
|
|
}
|
|
}
|
|
$this->debug("Array for the weekdays");
|
|
$this->debug($arWeekdays);
|
|
|
|
if ($this->bits[2] == '*') {
|
|
$daysmonth = $this->getDays($month, $year);
|
|
} else {
|
|
$daysmonth = $this->expand_ranges($this->bits[2]);
|
|
// so that we do not end up with 31 of Feb
|
|
$daysinmonth = $this->daysinmonth($month, $year);
|
|
$daysmonth = $this->_sanitize($daysmonth, 1, $daysinmonth);
|
|
}
|
|
|
|
//Now match these days with weekdays
|
|
foreach ($daysmonth as $day) {
|
|
$wkday = date('w', mktime(0, 0, 0, $month, $day, $year));
|
|
if (in_array($wkday, $arWeekdays)) {
|
|
$days[] = $day;
|
|
}
|
|
}
|
|
}
|
|
$this->debug("Days array matching weekdays for $year-$month");
|
|
$this->debug($days);
|
|
$this->days_arr = $days;
|
|
return $days;
|
|
}
|
|
|
|
//given a month/year, return an array containing all the days in that month
|
|
function getDays($month, $year)
|
|
{
|
|
$daysinmonth = $this->daysinmonth($month, $year);
|
|
$this->debug("Number of days in $year-$month : $daysinmonth");
|
|
$days = array();
|
|
for($i = 1; $i <= $daysinmonth; $i ++) {
|
|
$days[] = $i;
|
|
}
|
|
return $days;
|
|
}
|
|
|
|
private function _getHoursArray()
|
|
{
|
|
if (empty($this->hours_arr)) {
|
|
$hours = array();
|
|
|
|
if ($this->bits[1] == '*') {
|
|
for($i = 0; $i <= 23; $i ++) {
|
|
$hours[] = $i;
|
|
}
|
|
} else {
|
|
$hours = $this->expand_ranges($this->bits[1]);
|
|
$hours = $this->_sanitize($hours, 0, 23);
|
|
}
|
|
|
|
$this->debug("Hour array");
|
|
$this->debug($hours);
|
|
$this->hours_arr = $hours;
|
|
}
|
|
return $this->hours_arr;
|
|
}
|
|
|
|
private function _getMinutesArray()
|
|
{
|
|
if (empty($this->minutes_arr)) {
|
|
$minutes = array();
|
|
|
|
if ($this->bits[0] == '*') {
|
|
for($i = 0; $i <= 59; $i ++) {
|
|
$minutes[] = $i;
|
|
}
|
|
} elseif (preg_match('/^\*\/([0-9]{1,})/', $this->bits[0], $matchs)) {
|
|
for($i = 0; $i <= 59; $i = $i + $matchs[1]) {
|
|
$minutes[] = $i;
|
|
}
|
|
} else {
|
|
$minutes = $this->expand_ranges($this->bits[0]);
|
|
$minutes = $this->_sanitize($minutes, 0, 59);
|
|
}
|
|
$this->debug("Minutes array");
|
|
$this->debug($minutes);
|
|
$this->minutes_arr = $minutes;
|
|
}
|
|
return $this->minutes_arr;
|
|
}
|
|
|
|
private function _getMonthsArray()
|
|
{
|
|
if (empty($this->months_arr)) {
|
|
$months = array();
|
|
if ($this->bits[3] == '*') {
|
|
for($i = 1; $i <= 12; $i ++) {
|
|
$months[] = $i;
|
|
}
|
|
} else {
|
|
$months = $this->expand_ranges($this->bits[3]);
|
|
$months = $this->_sanitize($months, 1, 12);
|
|
}
|
|
$this->debug("Months array");
|
|
$this->debug($months);
|
|
$this->months_arr = $months;
|
|
}
|
|
return $this->months_arr;
|
|
}
|
|
|
|
}
|
|
|
|
// End ^ LF ^ UTF-8
|