Laravel中如何检索含重复规则的日历日期记录
解决方案
核心思路
- 调整查询逻辑,覆盖两种场景:
selected_date本身在起止日期范围内,或者selected_date在结束日期之前且存在重复规则(可能生成落在范围内的后续日期)。 - 基于
Carbon日期工具,根据重复规则(日/周/月)生成所有符合条件的日期,同时限制两个边界:不超过重复次数上限(after_occurence_value)、不超过指定的结束日期。 - 筛选出落在起止日期范围内的日期,整理成目标格式后排序输出。
修改后的服务层代码
use Carbon\Carbon; class CalendarTimeService { public function myquery() { $startDate = Carbon::parse('2025-02-01'); $endDate = Carbon::parse('2025-02-28'); // 获取所有可能生成有效日期的CalendarTime记录 $calendarTimes = CalendarTime::where(function($query) use ($startDate, $endDate) { // 场景1:selected_date本身在起止范围内 $query->whereBetween('selected_date', [$startDate, $endDate]) // 场景2:selected_date在结束日期前,且存在重复规则(可能生成后续有效日期) ->orWhere(function($q) use ($endDate) { $q->where('selected_date', '<=', $endDate) ->whereHas('recurrence'); }); }) ->with('recurrence') ->get(); $finalResult = []; foreach ($calendarTimes as $calendarTime) { $baseDate = Carbon::parse($calendarTime->selected_date); $recurrence = $calendarTime->recurrence; // 生成符合规则的所有日期 $dates = $this->generateRecurringDates( $baseDate, $recurrence?->repeat_occurence, $recurrence?->repeat_every ?? 1, $recurrence?->after_occurence_value ?? 1, $startDate, $endDate ); // 转换为目标格式 foreach ($dates as $date) { $finalResult[] = [ 'date' => $date->format('Y-m-d'), 'time_from' => $calendarTime->time_from, 'time_to' => $calendarTime->time_to, ]; } } // 按日期排序 usort($finalResult, function($a, $b) { return $a['date'] <=> $b['date']; }); return $finalResult; } /** * 根据重复规则生成符合条件的日期 * @param Carbon $baseDate 起始日期 * @param string|null $repeatOccurence 重复单位(day/week/month) * @param int $repeatEvery 重复间隔数 * @param int $maxOccurrences 最大重复次数 * @param Carbon $startDate 查询起始日期 * @param Carbon $endDate 查询结束日期 * @return Carbon[] */ private function generateRecurringDates(Carbon $baseDate, ?string $repeatOccurence, int $repeatEvery, int $maxOccurrences, Carbon $startDate, Carbon $endDate) { $dates = []; // 无重复规则时,仅检查起始日期是否在范围内 if (!$repeatOccurence) { if ($baseDate->between($startDate, $endDate)) { $dates[] = $baseDate->copy(); } return $dates; } $currentDate = $baseDate->copy(); $occurrenceCount = 0; // 循环生成日期,直到达到最大次数或超过结束日期 while ($occurrenceCount < $maxOccurrences && $currentDate <= $endDate) { // 仅保留在查询范围内的日期 if ($currentDate >= $startDate) { $dates[] = $currentDate->copy(); } // 根据重复单位计算下一个日期 switch ($repeatOccurence) { case 'day': $currentDate->addDays($repeatEvery); break; case 'week': $currentDate->addWeeks($repeatEvery); break; case 'month': $currentDate->addMonths($repeatEvery); break; default: // 未知重复规则,终止循环 break 2; } $occurrenceCount++; } return $dates; } }
关键细节说明
- 查询逻辑优化:避免漏掉
selected_date在查询起始日之前,但重复后生成的日期落在范围内的记录。 - 日期边界控制:生成日期时同时满足两个限制:不超过
after_occurence_value指定的最大次数,不超过查询的结束日期。 - Carbon工具使用:利用Carbon的日期加减、比较方法,简化日期操作的复杂度。
- 结果排序:最终结果按日期升序排列,保证输出顺序符合预期。
内容的提问来源于stack exchange,提问作者smzapp
相关产品推荐
相关产品推荐

