I've got a cheap website that doesn't let me use cron to schedule tasks (like database backups), so I had to do it myself. I found pseudo-cron, which looks cool but has some bugs and was more complicated than I wanted, so I wrote a simple PHP script to do what I wanted.
They key is that while I can't schedule tasks to run, I can count on one event happening at least a couple times a day: hits on the website. So I add a require_once('/path/to/scripts/scheduler.php')
at the start of every page, and, as long as the scheduled tasks don't take too long, the website user never notices that I'm taking advantage of him.
At each call, the script pulls a single job to run from a mySQL database, runs it and updates the next scheduled time.
The table 'schedule' has 4 fields:
- id: Varchar
- Unique identifier for that job.
- frequency: Varchar
- How often to run the job. Will be passed to strtotime. Typical values 'next week', 'next Sunday', 'next month', 'next year'.
- If it evaluates to a day in the past, the year is incremented until it is in the future. That way 'April 15' will be the coming April 15.
- Special case: numbers are interpreted as days of the month (at midnight); '28' will run on the next 28th of the month. Use 0 for the last of the month, -1 for the second to last of the month.
- Note that this uses mktime which corrects dates forward; so a frequency of 31 will trigger on Jan 31, Mar 2, Mar 31, May 1, May 31, Jul 1, Jul 31, Aug 31, Oct 1, Oct 31 and Dec 1. Again, use 0 for the last day of the month
- Note: you can do things like '+6 hours' for time scheduling as well, though I haven't tested this.
- scheduled: Datetime
- The next scheduled run time; set to NULL or a point in the past to have the job run immediately
- command: Text
- Text that will be passed unchanged to eval.
// for debugging, use 'example.com/scheduler.php?scheduledebugdate=2009-02-12' to force the script
// to pretend that the current time is 2009-02-12
$now = $_GET['scheduledebugdate'] ? strtotime($_GET['scheduledebugdate']) : time();
$sqlnow = date ('Y-m-d H:i:s', $now);
$sql = mysql_connect($server, $user, $password); mysql_select_db ($database, $sql);
$result = mysql_query ("SELECT * FROM schedule WHERE `scheduled` IS NULL OR `scheduled` <= '$sqlnow' LIMIT 1", $sql);
// note only one job at a time; otherwise the website user who doesn't know he's triggering
// this code may wait forever. Change the LIMIT term if you want to run more than one job
while ($row = mysql_fetch_assoc ($result)) {
eval($row['command']);
$f = $row['frequency'];
if (is_numeric($f)){
$d = getdate($now);
do {
$nexttime = mktime($d['hours'], $d['minutes'], $d['seconds'], $d['mon']++, $f, $d['year']);
}while ($nexttime <= $now);
}else{
$nexttime = strtotime($f, $now);
while ($nexttime <= $now) $nexttime = strtotime('+1 year', $nexttime);
}
$nexttime = date ('Y-m-d H:i:s', $nexttime);
mysql_query ("UPDATE schedule SET `scheduled`='$nexttime' WHERE `id` = '${row['id']}'", $sql);
}
Leave a Reply