timezone; $cursor = $after?->copy()->timezone($timezone) ?? CarbonImmutable::now($timezone); if ($schedule->frequency === 'weekly') { return $this->nextWeeklyRun($schedule, $cursor); } return $this->nextDailyRun($schedule, $cursor); } protected function nextDailyRun(BackupSchedule $schedule, CarbonImmutable $cursor): ?CarbonImmutable { $time = $schedule->time_of_day; $attempts = 0; if ($cursor->format('H:i:s') >= $time) { $cursor = $cursor->addDay(); } while ($attempts++ < 14) { $candidate = $this->buildLocalSlot($schedule, $cursor); if ($candidate) { return $candidate; } $cursor = $cursor->addDay(); } return null; } protected function nextWeeklyRun(BackupSchedule $schedule, CarbonImmutable $cursor): ?CarbonImmutable { $allowed = $schedule->days_of_week ?? []; $allowed = array_filter($allowed, fn ($day) => is_numeric($day) && $day >= 1 && $day <= 7); $allowed = array_values($allowed); if (empty($allowed)) { return null; } $attempts = 0; while ($attempts++ < 21) { $dayOfWeek = $cursor->dayOfWeekIso; if (in_array($dayOfWeek, $allowed, true)) { $candidate = $this->buildLocalSlot($schedule, $cursor); $cursorUtc = $cursor->copy()->timezone('UTC'); if ($candidate && $candidate->greaterThan($cursorUtc)) { return $candidate; } } $cursor = $cursor->addDay()->startOfDay(); } return null; } protected function buildLocalSlot(BackupSchedule $schedule, CarbonImmutable $date): ?CarbonImmutable { $timezone = $schedule->timezone; $time = $schedule->time_of_day; $datePart = $date->format('Y-m-d'); $candidate = CarbonImmutable::createFromFormat('Y-m-d H:i:s', "{$datePart} {$time}", $timezone); if (! $candidate || $candidate->format('H:i:s') !== $time) { return null; } return $candidate->startOfMinute()->timezone('UTC'); } }