--- a/busui/owa/includes/CronParser.php +++ b/busui/owa/includes/CronParser.php @@ -1,1 +1,604 @@ - +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 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;
+ var $year;
+ var $month;
+ var $day;
+ var $hour;
+ var $minute;
+ var $minutes_arr = array(); //minutes array based on cron string
+ var $hours_arr = array(); //hours array based on cron string
+ var $months_arr = array(); //months array based on cron string
+
+ 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 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";
+ }
+ //echo nl2br($this->debug);
+ }
+
+ /**
+ * 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
+ 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
+ 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
+ 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
+ function _getLastMonth()
+ {
+ $months = $this->_getMonthsArray();
+ $month = array_pop($months);
+
+ return $month;
+ }
+
+ 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;
+ }
+
+ function _getLastHour()
+ {
+ $hours = $this->_getHoursArray();
+ $hour = array_pop($hours);
+
+ return $hour;
+ }
+
+ 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
+ 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.
+ 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);
+ 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;
+ }
+
+ 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;
+ }
+
+ function _getMinutesArray()
+ {
+ if (empty($this->minutes_arr))
+ {
+ $minutes = array();
+
+ if ($this->bits[0] == '*')
+ {
+ for ($i = 0; $i <= 60; $i++)
+ {
+ $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;
+ }
+
+ 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;
+ }
+
+}
+?>