From 7c5579677d373dcd879610f5a4f2f67976c8c8bb Mon Sep 17 00:00:00 2001 From: johnnyq Date: Thu, 30 Jul 2026 18:21:47 -0400 Subject: [PATCH] Cron: daily jobs catch up when the dispatcher is invoked before their scheduled time, so an old single daily crontab entry still runs them --- cron/cron.php | 11 +++++++++-- includes/cron_jobs.php | 15 ++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/cron/cron.php b/cron/cron.php index b41e9e56..61f44fc2 100644 --- a/cron/cron.php +++ b/cron/cron.php @@ -96,9 +96,16 @@ function cronJobClaim($mysqli, array $job): bool if (!empty($row['cron_job_enabled'])) { if ($row['cron_job_schedule'] === 'Daily') { - // Due once today's scheduled time has passed, unless we have already run since it + // Due once the most recent scheduled time has passed, unless we already ran since + // it. Before today's time, that most recent occurrence was yesterday's - without + // this, a dispatcher only invoked before the scheduled time (the old single + // "0 2 * * * cron.php" crontab line against a 03:00 job) would never find the job + // due and it would silently never run again. $threshold = date('Y-m-d') . ' ' . substr((string)$row['cron_job_daily_at'], 0, 5) . ':00'; - $scheduled = ($now >= $threshold); + if ($now < $threshold) { + $threshold = date('Y-m-d', strtotime('-1 day')) . ' ' . substr((string)$row['cron_job_daily_at'], 0, 5) . ':00'; + } + $scheduled = true; } else { // Interval jobs get 30 seconds of slack. cron fires on the minute but a run can // start a second or two late, and an exact n-minute comparison would then find the diff --git a/includes/cron_jobs.php b/includes/cron_jobs.php index 57e67827..b1adf893 100644 --- a/includes/cron_jobs.php +++ b/includes/cron_jobs.php @@ -131,13 +131,18 @@ function cronJobNextRun(array $row): ?string $last_run = $row['cron_job_last_run_at']; if ($row['cron_job_schedule'] === 'Daily') { - $today = date('Y-m-d') . ' ' . substr((string)$row['cron_job_daily_at'], 0, 5) . ':00'; - - if ($last_run === null || $last_run < $today) { - return $today <= date('Y-m-d H:i:s') ? date('Y-m-d H:i:s') : $today; + // The most recent scheduled occurrence: today's, or yesterday's if today's has not + // arrived yet - matching how the dispatcher decides due-ness + $occurrence = date('Y-m-d') . ' ' . substr((string)$row['cron_job_daily_at'], 0, 5) . ':00'; + if ($occurrence > date('Y-m-d H:i:s')) { + $occurrence = date('Y-m-d', strtotime('-1 day')) . ' ' . substr((string)$row['cron_job_daily_at'], 0, 5) . ':00'; } - return date('Y-m-d H:i:s', strtotime($today) + 86400); + if ($last_run === null || $last_run < $occurrence) { + return date('Y-m-d H:i:s'); + } + + return date('Y-m-d H:i:s', strtotime($occurrence) + 86400); } if ($last_run === null) {