Some good thoughts in other answers here. I would also point out that you should give consideration to using PHP's DateTime, DateInterval, DatePeriod, and related classes if you find yourself needing to do more complex date handling (like displaying all scheduled tasks in calendar in GUI admin tool)
You might have a DB table containing task schedule rules that would look something like:
id - unique auto-increment
name - human-readable task name
owner - perhaps forieg key to user tables so you know who owns tasks
interval - An string interval specification as used in DateInterval
start_time - Datetime When rule goes into effect
end_time - Datetime When rule is no longer in effect
script_path - path to script of some sort of command recognized by your applcation
last_execution - Datetime for last time script was triggered
next_execution - Datetime in which you store value calculated to be next execution point
active - maybe a flag to enable/disable a rule
perhaps other admin fields like created_time, error_tracking, etc.
And you could easily build a collection of of DatePeriod objects you can iterate on from each table row. That might look something like:
// have one authoritative now that you use in this script
$now = DateTime();
$now_sql = $now->format('Y-m-d H:i:s');
$sql = <<<EOT
SELECT
id,
name,
interval,
/* etc */
FROM task_rules
WHERE
active = 1
AND
(IS_NULL(start_time) OR start_time <= '{$now_sql}')
AND
(IS_NULL(end_time) OR eend_time > '{$now_sql}')
/* Add this filter if you are trying to query this table
for overdue events */
AND
next_execution <= '{$now_sql}'
/* any other filtering you might want to do */
/* Any ORDER BY and LIMIT clauses */
EOT;
$tasks = array();
//logic to read rows from DB
while ($row = /* Your DB fetch mechanism */) {
// build your task (probably could be its own class,
// perhaps saturated via DB retrieval process), but this is jist
$task = new stdClass();
$task->id = $row->id
$task->name = $row->name;
$task->interval = $row->interval;
$task->start_time = $row->start_time;
// etc. basically map DB row to an object
// start building DateTime and related object representations
// of your tasks
$task->dateInterval = new DateInterval($task->interval);
// determine start/end dates for task sequence
if(empty($task->start_time)) {
// no defined start date, so build start date from last executed time
$task->startDateTime = DateTime::createFromFormat(
'Y-m-d H:i:s',
$task->last_execution
);
} else {
// start date known, so we want to base period sequence on start date
$task->startDateTime = DateTime::createFromFormat(
'Y-m-d H:i:s',
$task->start_date
);
}
if(empty($task->end_time)) {
// No defined end. So set artificial end date based on app needs
// (like we need to show next week, month, year)
$end_datetime = clone $now;
$end_datetime->modify(+ 1 month);
$task->endDateTime = $end_datetime;
} else {
$task->endDateTime = DateTime::createFromFormat(
'Y-m-d H:i:s',
$task->end_time
);
}
$task->datePeriod = new DatePeriod(
$task->startDateTime,
$task->dateInterval,
$task->endDateTime
);
// iterate datePeriod to build array of occurences
// which is more useful than just working with Traversable
// interface of datePeriod and allows you to filter out past
// scheduled occurences
$task->future_occurrences = [];
foreach ($task->datePeriod as $occurence) {
if ($occurence < $now) {
// this is occcurrence in past, do nothing
continue;
}
$task->future_occurrences[] = $occurence;
}
$task->nextDateTime = null;
if(count($task->future_occurrences) > 0) {
$task->nextDateTime = $task->future_occurrences[0];
$task->next_execution = $task->nextDateTime->format('Y-m-d H:i:s');
}
$tasks[] = $task;
}
Here $tasks would contain an array of objects each representing a single rule along with tangible PHP DateTime, DatePeriod constructs you can use to to execute and/or display tasks.
For example:
// execute all tasks
// just using a simple loop example here
foreach($tasks as $task) {
$command = 'php ' . $task->script_path;
exec($command);
// update DB
$sql = <<<EOT
UPDATE task_rules
SET
last_execution = '{$now_sql}',
next_execution = '{$task->next_execution}'
WHERE id = {$task->id}
EOT;
// and execute using DB tool of choice
}