calcLastRan($cron_str0); // $cron->getLastRanUnix() returns an Unix timestamp echo "Cron '$cron_str0' last due at: " . date('r', $cron->getLastRanUnix()) . "

"; // $cron->getLastRan() returns last due time in an array print_r($cron->getLastRan()); echo "Debug:
" . nl2br($cron->getDebug()); $cron_str1 = "3 12 * * *"; if ($cron->calcLastRan($cron_str1)) { echo "

Cron '$cron_str1' last due at: " . date('r', $cron->getLastRanUnix()) . "

"; print_r($cron->getLastRan()); } else { echo "Error parsing"; } echo "Debug:
" . 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("Working on cron schedule: $string"); $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