getLastRan());
echo nl2br($cp->getDebug());*/
//
// Know bugs:
//
// It can't handle too complex step arguments.
//
// */12 or 1-3/4 are ok
// 1,4-5/20,2 or 1,2,3,4,5,10-30/5 isn't supported
//
class CronException extends Exception {
// Redefine the exception so message isn't optional
public function __construct($message, $code = 0) {
// some code
// make sure everything is assigned properly
parent::__construct($message, $code);
}
// custom string representation of object
public function __toString() {
return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
}
}
class CronParser{
var $bits = Array(); //exploded String like 0 1 * * *
var $now= Array(); //Array of cron-style entries for time()
var $lastRan; //Timestamp of last ran time.
var $taken;
var $debug;
function CronParser($string, $now = false, $validate_only = false){
$tstart = microtime();
$this->debug("Working on cron schedule: $string");
$this->bits = @explode(" ", $this->sanitize_input($string));
$this->validate();
if ($validate_only)
return;
if ($now)
$this->now = explode(",", $now);
else
$this->getNow();
$this->calcLastRan();
$this->calcNextRun();
$tend = microtime();
$this->taken = $tend-$tstart;
$this->debug("Parsing $string taken ".$this->taken." seconds");
}
function sanitize_input($string) {
$tmp = trim($string);
$output = "";
$jump = false;
for ($i=0; $ibits) != 5){
throw new CronException("Wrong number of parameters.");
}
$i=0;
foreach ($this->bits as $piece){
if ($piece == "*" || preg_match($range_pattern, $piece)) {
continue;
}
if (!preg_match($pattern, $piece)){
throw new CronException("Bad formatted entry: " . $piece);
}
foreach (explode(",",$piece) as $molecule) {
foreach (explode("-", $molecule) as $atom) {
if ($atom > $max[$i] || $atom < $min[$i]){
throw new CronException("Parameter out of range: " . $atom);
}
}
}
$i++;
}
}
function expandValues(){
$sol = array();
$minutes = $this->bits[0];
$hours = $this->bits[1];
$months = $this->bits[3];
if ($minutes == "*") {
$minutes = range(0,59);
} else if (strstr($minutes, "*/")) {
$tmp = explode("/", $minutes);
$minutes = $this->expand_ranges("0-59/".$tmp[1]);
}
else {
$minutes = $this->expand_ranges($minutes);
}
if ($hours == "*") {
$hours = range(0, 23);
} else if (strstr($hours, "*/")) {
$tmp = explode("/", $hours);
$minutes = $this->expand_ranges("0-23/".$tmp[1]);
}
else {
$hours = $this->expand_ranges($hours);
}
if ($months == "*") {
$months = range(1,12);
} else if (strstr($months, "*/")) {
$tmp = explode("/", $months);
$minutes = $this->expand_ranges("0-12/".$tmp[1]);
}
else {
$months = $this->expand_ranges($months);
}
return array("minutes" => $minutes, "hours" => $hours, "months" => $months);
}
function getNextArray($arr, $current) {
if (is_array($arr)) {
foreach ($arr as $v) {
if ($v > $current)
return $v;
}
}
return false;
}
function calcNextRun(){
$lastRan = $this->getLastRan();
$values = $this->expandValues();
$days = $this->getDaysArray($lastRan[3]);
var_dump($values);
ob_start();var_dump($values);$output=ob_get_contents();ob_end_clean();
$this->debug("NextRun! dump" . $output);
ob_start();var_dump($days);$output=ob_get_contents();ob_end_clean();
$this->debug("dias: " . $output);
$minute = $this->getNextArray($values["minutes"], $lastRan[0]);
if ($minute) {
$this->nextRun = mktime($lastRan[1], $minute, 0, $lastRan[3], $lastRan[2], $lastRan[5]);
return;
}
else {
$minute = $values["minutes"][0];
}
$this->debug("Next minute -> " . $minute);
$hour = $this->getNextArray($values["hours"], $lastRan[1]);
if ($hour) {
$this->nextRun = mktime($hour, $minute, 0, $lastRan[3], $lastRan[2], $lastRan[5]);
return;
}
else {
$hour = $values["hours"][0];
}
$day = $this->getNextArray($days, $lastRan[2]);
if ($day) {
$this->nextRun = mktime($hour, $minute, 0, $lastRan[3], $day, $lastRan[5]);
return;
}
// In this case we don't have an else statement because we
// need to calculate the next month first so we know wich day is the needed
$month = $this->getNextArray($values["months"], $lastRan[3]);
if ($month) {
$day = $this->getDaysArray($month);
$this->nextRun = mktime($hour, $minute, 0,$month, $day, $lastRan[5]);
return;
}
else {
$month = $values["months"][0];
$day = $this->getDaysArray($month, $lastRan[5]+1);
$this->nextRun = mktime($hour, $minute, 0,$month, $day, $lastRan[5]+1);
}
ob_start();var_dump($this->nextRun);$output=ob_get_contents();ob_end_clean();
$this->debug("output " . $output);
// var_dump($this->nextRan);
}
function getNow(){
$t = strftime("%M,%H,%d,%m,%w,%Y", time()); //Get the values for now in a format we can use
$this->now = explode(",", $t); //Make this an array
//$this->debug($this->now);
}
function getLastRan(){
return explode(",", strftime("%M,%H,%d,%m,%w,%Y", $this->lastRan)); //Get the values for now in a format we can use
}
function getNextRun(){
return explode(",", strftime("%M,%H,%d,%m,%w,%Y", $this->nextRun)); //Get the values for now in a format we can use
}
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";
}
//echo nl2br($this->debug);
}
function getExtremeMonth($extreme){
if ($extreme == "END"){
$year = $this->now[5] - 1;
} else {
$year = $this->now[5];
}
//Now determine start or end month in the last year
if ($this->bits[3] == "*" && $extreme == "END"){//Check month format
$month = 12;
} else if ($this->bits[3] == "*" && $extreme == "START"){
$month = 1;
} else {
$months = $this->expand_ranges($this->bits[3]);
if ($extreme == "END"){
sort($months);
} else {
rsort($months);
}
$month = array_pop($months);
}
//Now determine the latest day in the specified month
$day=$this->getExtremeOfMonth($month, $year, $extreme);
$this->debug("Got day $day for $extreme of $month, $year");
$hour = $this->getExtremeHour($extreme);
$minute = $this->getExtremeMinute($extreme);
return mktime($hour, $minute, 0, $month, $day, $year);
}
/**
* Assumes that value is not *, and creates an array of valid numbers that
* the string represents. Returns an array.
*/
function expand_ranges($str){
//$this->debug("Expanding $str");
//echo var_dump($str);
if (strstr($str, "/")){
$val = explode("/", $str);
$str = $val[0];
$step = $val[1];
if (count($val) != 2) {
throw new CronException("Bad formatted entry (step): " . $str);
}
}
if (strstr($str, ",")){
$tmp1 = explode(",", $str);
//$this->debug($tmp1);
$count = count($tmp1);
for ($i=0;$i<$count;$i++){//Loop through each comma-separated value
if (strstr($tmp1[$i], "-")){ //If there's a range in this place, expand that too
$tmp2 = explode("-", $tmp1[$i]);
//$this->debug("Range:");
//$this->debug($tmp2);
for ($j=$tmp2[0];$j<=$tmp2[1];$j++){
$ret[] = $j;
}
} else {//Otherwise, just add the value
$ret[] = $tmp1[$i];
}
}
} else if (strstr($str, "-")){//There might only be a range, no comma sep values at all. Just loop these
$range = explode("-", $str);
for ($i=$range[0];$i<=$range[1];$i++){
$ret[] = $i;
}
} else {//Otherwise, it's a single value
$ret[] = $str;
return $ret;
}
sort($ret);
if (isset($step)) {
$sol = array();
foreach (range(0,count($ret),$step) as $i) {
$sol[] = $ret[$i];
}
$ret = $sol;
}
return $ret;
}
/**
* Given a string representation of a set of weekdays, returns an array of
* possible dates.
*/
function getWeekDays($str, $month, $year){
$daysInMonth = $this->daysinmonth($month, $year);
if (strstr($str, ",")){
$tmp1 = explode(",", $str);
$count = count($tmp1);
for ($i=0;$i<$count;$i++){//Loop through each comma-separated value
if (strstr($tmp1[$i], "-")){ //If there's a range in this place, expand that too
$tmp2 = explode("-", $tmp1[$i]);
for ($j=$start;$j<=$tmp2[1];$j++){
for ($n=1;$n<=$daysInMonth;$n++){
if ($j == jddayofweek(gregoriantojd ( $month, $n, $year),0)){
$ret[] = $n;
}
}
}
} else {//Otherwise, just add the value
for ($n=1;$n<=$daysInMonth;$n++){
if ($tmp1[$i] == jddayofweek(gregoriantojd ( $month, $n, $year),0)){
$ret[] = $n;
}
}
}
}
} else if (strstr($str, "-")){//There might only be a range, no comma sep values at all. Just loop these
$range = explode("-", $str);
for ($i=$start;$i<=$range[1];$i++){
for ($n=1;$n<=$daysInMonth;$n++){
if ($i == jddayofweek(gregoriantojd ( $month, $n, $year),0)){
$ret[] = $n;
}
}
}
} else {//Otherwise, it's a single value
for ($n=1;$n<=$daysInMonth;$n++){
if ($str == jddayofweek(gregoriantojd ( $month, $n, $year),0)){
$ret[] = $n;
}
}
}
return $ret;
}
function daysinmonth($month, $year){
if(checkdate($month, 31, $year)) return 31;
if(checkdate($month, 30, $year)) return 30;
if(checkdate($month, 29, $year)) return 29;
if(checkdate($month, 28, $year)) return 28;
return 0; // error
}
/**
* Get the timestamp of the last ran time.
*/
function calcLastRan(){
$values = $this->expandValues();
echo "Calculating last ran \n";
echo var_dump($this->now);
$now = mktime($this->now[1],$this->now[0],0,$this->now[3],$this->now[2],$this->now[5]);
echo "Now -> ";
echo var_dump($now[0]);
if ($now < $this->getExtremeMonth("START")){
echo "\nCron is not due to have this year\n";
//The cron isn't due to have run this year yet. Getting latest last year
$this->debug("Last ran last year");
$tsLatestLastYear = $this->getExtremeMonth("END");
$this->debug("Timestamp of latest scheduled time last year is ".$tsLatestLastYear);
$this->lastRan = $tsLatestLastYear;
$year = date("Y", $this->lastRan);
$month = date("m", $this->lastRan);
$day = date("d", $this->lastRan);
$hour = date("h", $this->lastRan);
$minute = date("i", $this->lastRan);
} else { //Cron was due to run this year. Determine when it was last due
echo "\nCron is this year\n";
$this->debug("Cron was due to run earlier this year");
$year = $this->now[5];
echo "Anho: ".$year;
/*
$arMonths = $this->expand_ranges($this->bits[3]);
*/
$arMonths = $values["months"];
echo "\nMeses: ";
echo var_dump($arMonths);
echo "\nValue Meses: ";
echo var_dump($values["months"]);
if (!in_array($this->now[3], $arMonths) && $this->bits[3] != "*"){//Not due to run this month. Get latest of last month
$this->debug("Cron was not due to run this month at all. This month is ".$this->now[3]);
$this->debug("Months array: ");
$this->debug($arMonths);
sort($arMonths);
do{
$month = array_pop($arMonths);
} while($month > $this->now[3]);
$day = $this->getExtremeOfMonth($month, $this->now[5], "END");
$hour = $this->getExtremeHour("END");
$minute = $this->getExtremeMinute("END");
} else if ($now < $this->getExtremeOfMonth($this->now[3], $this->now[5], "START")){ //It's due in this month, but not yet.
$this->debug("It's due in this month, but not yet.");
sort($arMonths);
do{
$month = array_pop($arMonths);
} while($month > $this->now[3]);
$day = $this->getExtremeOfMonth($month, $this->now[5], "END");
$hour = $this->getExtremeHour("END");
$minute = $this->getExtremeMinute("END");
} else {//It has been due this month already
$this->debug("Cron has already been due to run this month (".$month = $this->now[3].")");
$month = $this->now[3];
$this->debug("Getting days array");
$days = $this->getDaysArray($this->now[3]);
if (!in_array($this->now[2], $days)){
$this->debug("Today not in the schedule. Getting latest last due day");
//No - Get latest last scheduled day
sort($days);
do{
$day = array_pop($days);
} while($day > $this->now[2]);
$hour = $this->getExtremeHour("END");
$minute = $this->getExtremeMinute("END");
} else if($this->now[1] < $this->getExtremeHour("START")){//Not due to run today yet
$this->debug("Cron due today, but not yet. Getting latest on last day");
sort($days);
do{
$day = array_pop($days);
} while($day >= $this->now[2]);
$hour = $this->getExtremeHour("END");
$minute = $this->getExtremeMinute("END");
} else {
$this->debug("Cron has already been due to run today");
$day = $this->now[2];
//Yes - Check if this hour is in the schedule?
$arHours = $this->expand_ranges($this->bits[1]);
if (!in_array($this->now[1], $arHours) && $this->bits[1] != "*"){
$this->debug("Cron not due in this hour, getting latest in last scheduled hour");
//No - Get latest last hour
sort($arHours);
do{
$hour = array_pop($arHours);
//$this->debug("hour is $hour, now is ".$this->now[1]);
} while($hour > $this->now[1]);
$minute = $this->getExtremeMinute("END");
} else if ($now < $this->getExtremeMinute("START") && $this->bits[1] != "*"){ //Not due to run this hour yet
sort($arHours);
do{
$hour = array_pop($arHours);
} while($hour >= $this->now[1]);
$minute = $this->getExtremeMinute("END");
} else {
//Yes, it is supposed to have run this hour already - Get last minute
$hour = $this->now[1];
if ($this->bits[0] != "*"){
$arMinutes = $this->expand_ranges($this->bits[0]);
$this->debug($arMinutes);
do{
$minute = array_pop($arMinutes);
} while($minute >= $this->now[0]);
//If the first time in the hour that the cron is due to run is later than now, return latest last hour
if($minute > $this->now[1] || $minute == ""){
$this->debug("Valid minute not set");
$minute = $this->getExtremeMinute("END"); //The minute will always be the last valid minute in an hour
//Get the last hour.
if ($this->bits[1] == "*"){
$hour = $this->now[1] - 1;
} else {
$arHours = $this->expand_ranges($this->bits[1]);
$this->debug("Array of hours:");
$this->debug($arHours);
sort($arHours);
do{
$hour = array_pop($arHours);
} while($hour >= $this->now[1]);
}
}
} else {
$minute = $this->now[0] -1;
}
}
}
}
}
echo "Last Ran calculated";
echo "\n##########################\n\n";
$this->debug("LAST RAN: $hour:$minute on $day/$month/$year");
//echo var_dump($this->getDebug());
$this->lastRan = mktime($hour, $minute, 0, $month, $day, $year);
}
function getExtremeOfMonth($month, $year, $extreme){
$daysInMonth = $this->daysinmonth($month, $year);
if ($this->bits[2] == "*"){
if ($this->bits[4] == "*"){//Is there a day range?
//$this->debug("There were $daysInMonth days in $month, $year");
if ($extreme == "END"){
$day = $daysInMonth;
} else {
$day=1;
}
} else {//There's a day range. Ignore the dateDay range and just get the list of possible weekday values.
$days = $this->getWeekDays($this->bits[4],$month, $year);
$this->debug($this->bits);
$this->debug("Days array for ".$this->bits[4].", $month, $year:");
$this->debug($days);
if ($extreme == "END"){
sort($days);
} else {
rsort($days);
}
$day = array_pop($days);
}
} else {
$days = $this->expand_ranges($this->bits[2]);
if ($extreme == "END"){
sort($days);
} else {
rsort($days);
}
do {
$day = array_pop($days);
} while($day > $daysInMonth);
}
//$this->debug("$extreme day is $day");
return $day;
}
function getDaysArray($month, $year=0){
if ($year == 0) {
$year = $this->now[5];
}
$this->debug("Getting days for $month");
$days = array();
if ($this->bits[4] != "*"){
$days = $this->getWeekDays($this->bits[4], $month, $year);
$this->debug("Weekdays:");
$this->debug($days);
}
if ($this->bits[2] != "*" && $this->bits[4] == "*") {
$days = $this->expand_ranges($this->bits[2]);
}
if ($this->bits[2] == "*" && $this->bits[4] == "*"){
//Just return every day of the month
$daysinmonth = $this->daysinmonth($month, $year);
$this->debug("Days in ".$month.", ".$year.": ".$daysinmonth);
for($i = 1;$i<=$daysinmonth;$i++){
$days[] = $i;
}
}
$this->debug($days);
return $days;
}
function getExtremeHour($extreme){
if ($this->bits[1] == "*"){
if ($extreme == "END"){
$hour = 23;
} else {
$hour = 0;
}
} else {
$hours = $this->expand_ranges($this->bits[1]);
if ($extreme == "END"){
sort($hours);
} else {
rsort($hours);
}
$hour = array_pop($hours);
}
//$this->debug("$extreme hour is $hour");
return $hour;
}
function getExtremeMinute($extreme){
if ($this->bits[0] == "*"){
if ($extreme == "END"){
$minute = 59;
} else {
$minute = 0;
}
} else {
$minutes = $this->expand_ranges($this->bits[0]);
if ($extreme == "END"){
sort($minutes);
} else {
rsort($minutes);
}
$minute = array_pop($minutes);
}
//$this->debug("$extreme minute is $minute");
return $minute;
}
}
?>