payfrit-api/api/admin/scheduledTasks/_cronUtils.php
John Mizerek 4d806d4e1e Port admin, cron, and receipt endpoints from CFML to PHP
- admin/quickTasks: list, create, save, delete
- admin/scheduledTasks: list, save, delete, toggle, run, runDue
- cron: expireStaleChats, expireTabs
- receipt: public order receipt page (no auth, UUID-secured)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 15:57:25 -07:00

59 lines
2.2 KiB
PHP

<?php
/**
* Shared cron expression utilities for scheduled tasks.
*/
/**
* Parse a 5-part cron expression and calculate the next run time from now.
* Supports: minute hour day month weekday
* Weekday ranges like 1-5 (Mon-Fri) are supported.
* Returns a DateTime in UTC.
*/
function calculateNextRun(string $cronExpression): DateTime {
$parts = preg_split('/\s+/', trim($cronExpression));
if (count($parts) !== 5) {
return new DateTime('+1 day', new DateTimeZone('UTC'));
}
[$cronMinute, $cronHour, $cronDay, $cronMonth, $cronWeekday] = $parts;
// Start from next minute
$check = new DateTime('now', new DateTimeZone('UTC'));
$check->modify('+1 minute');
$check->setTime((int) $check->format('H'), (int) $check->format('i'), 0);
$maxIterations = 400 * 24 * 60;
for ($i = 0; $i < $maxIterations; $i++) {
$m = (int) $check->format('i');
$h = (int) $check->format('G');
$d = (int) $check->format('j');
$mo = (int) $check->format('n');
$dow = (int) $check->format('w'); // 0=Sunday
$matchMinute = ($cronMinute === '*' || (is_numeric($cronMinute) && $m === (int) $cronMinute));
$matchHour = ($cronHour === '*' || (is_numeric($cronHour) && $h === (int) $cronHour));
$matchDay = ($cronDay === '*' || (is_numeric($cronDay) && $d === (int) $cronDay));
$matchMonth = ($cronMonth === '*' || (is_numeric($cronMonth) && $mo === (int) $cronMonth));
$matchWeekday = ($cronWeekday === '*');
if (!$matchWeekday) {
if (str_contains($cronWeekday, '-')) {
$range = explode('-', $cronWeekday);
if (count($range) === 2 && is_numeric($range[0]) && is_numeric($range[1])) {
$matchWeekday = ($dow >= (int) $range[0] && $dow <= (int) $range[1]);
}
} elseif (is_numeric($cronWeekday)) {
$matchWeekday = ($dow === (int) $cronWeekday);
}
}
if ($matchMinute && $matchHour && $matchDay && $matchMonth && $matchWeekday) {
return $check;
}
$check->modify('+1 minute');
}
// Fallback
return new DateTime('+1 day', new DateTimeZone('UTC'));
}