$data */ public function resolveNextConfiguredSendAt(array $data, ?CarbonInterface $reference = null): CarbonImmutable { $referenceTime = CarbonImmutable::instance($reference ?? now()); if (($data['repeat_type'] ?? 'once') !== 'yearly') { return CarbonImmutable::parse((string) $data['send_at']); } return $this->findNextYearlyOccurrence($data, $referenceTime); } /** * 计算模板在一次自动触发后的下一次 send_at。 */ public function advanceAfterTrigger(HolidayEvent $event): ?CarbonImmutable { if ($event->send_at === null) { return null; } $currentSendAt = CarbonImmutable::instance($event->send_at); return match ($event->repeat_type) { 'daily' => $currentSendAt->addDay(), 'weekly' => $currentSendAt->addWeek(), 'monthly' => $currentSendAt->addMonth(), 'yearly' => $this->findNextYearlyOccurrence($this->extractYearlyConfig($event), $currentSendAt->addSecond()), default => null, }; } /** * 查找年度节日配置在参考时间之后的下一次触发点。 * * @param array $data */ public function findNextYearlyOccurrence(array $data, CarbonInterface $reference): CarbonImmutable { $referenceTime = CarbonImmutable::instance($reference); foreach ([$referenceTime->year, $referenceTime->year + 1, $referenceTime->year + 2] as $year) { foreach ($this->buildYearlyOccurrencesForYear($data, $year) as $occurrence) { if ($occurrence->greaterThanOrEqualTo($referenceTime)) { return $occurrence; } } } return $this->buildYearlyOccurrencesForYear($data, $referenceTime->year + 3)[0]; } /** * 构造指定年份内的全部年度节日触发点。 * * @param array $data * @return array */ public function buildYearlyOccurrencesForYear(array $data, int $year): array { [$hour, $minute] = array_map('intval', explode(':', (string) $data['schedule_time'])); $baseDate = CarbonImmutable::create( $year, (int) $data['schedule_month'], (int) $data['schedule_day'], $hour, $minute, 0, config('app.timezone') ); $occurrences = []; $durationDays = max(1, (int) ($data['duration_days'] ?? 1)); $dailyOccurrences = max(1, (int) ($data['daily_occurrences'] ?? 1)); $intervalMinutes = (int) ($data['occurrence_interval_minutes'] ?? 0); for ($dayIndex = 0; $dayIndex < $durationDays; $dayIndex++) { $dayStart = $baseDate->addDays($dayIndex); for ($occurrenceIndex = 0; $occurrenceIndex < $dailyOccurrences; $occurrenceIndex++) { $occurrences[] = $dayStart->addMinutes($intervalMinutes * $occurrenceIndex); } } return $occurrences; } /** * 从模板模型中提取年度节日调度字段。 * * @return array */ private function extractYearlyConfig(HolidayEvent $event): array { return [ 'schedule_month' => $event->schedule_month, 'schedule_day' => $event->schedule_day, 'schedule_time' => $event->schedule_time, 'duration_days' => $event->duration_days, 'daily_occurrences' => $event->daily_occurrences, 'occurrence_interval_minutes' => $event->occurrence_interval_minutes, ]; } }