Laravel 10中基于用户偏好计算下一个可通知时间的问题排查
问题分析与修正方案
你的代码核心错误在于计算下一个可通知日期时,直接将用户定义的周几数值作为天数添加,而非计算当前日期到目标周几的实际天数差;同时依赖$notifiableDays数组的排序顺序,导致数组乱序时结果错误。
具体问题点
- 天数计算逻辑错误:例如当前是周一(用户定义的1),目标周二(用户定义的2),原代码用
addDays(2)会得到周三,正确应该是addDays(1)。 - 依赖数组排序:如果
$notifiableDays不是按升序排列,会取到第一个大于当前周几的数值,而非最近的那个。 - 跨周计算错误:当所有可通知日期都在当前日期之前时,直接取数组第一个元素添加天数,而非计算下一周的最近日期。
修正后的完整代码
/** * Get the next notifiable datetime based on configured setup */ public function getNextNotifiableDatetimeAttribute(): string { $now = Carbon::now('UTC'); $notifiableDays = $this->notify_on_days; // if on holiday, return next date as on_holiday_until if ($this->on_holiday_until && $this->on_holiday_until > $now) { return Carbon::parse($this->on_holiday_until)->toDateTimeString(); } // 转换Carbon的周几格式为用户定义的格式(1=周一,7=周日) $currentUserDay = $now->dayOfWeek === 0 ? 7 : $now->dayOfWeek; // 如果当天可通知且在时段内,直接返回当前时间 if (in_array($currentUserDay, $notifiableDays) && $this->isWithinTimeSlot($now)) { return $now->toDateTimeString(); } // 计算到下一个可通知日期的最小天数差 $minDaysToAdd = 7; // 初始化为一周的最大天数 foreach ($notifiableDays as $day) { if ($day === $currentUserDay) { // 当天可通知但已过时段,取下周同一天 $daysToAdd = 7; } elseif ($day > $currentUserDay) { // 本周内的后续日期,计算天数差 $daysToAdd = $day - $currentUserDay; } else { // 跨周的日期,计算到下一周该日期的天数差 $daysToAdd = $day + 7 - $currentUserDay; } // 保留最小的天数差 if ($daysToAdd < $minDaysToAdd) { $minDaysToAdd = $daysToAdd; } } // 计算下一个可通知日期 $nextNotifyDatetime = Carbon::createMidnightDate( $now->year, $now->month, $now->day )->addDays($minDaysToAdd); if (!$this->isWithinTimeSlot($nextNotifyDatetime)) { $nextNotifyDatetime = $this->setToNextAvailableTime($nextNotifyDatetime); } return $nextNotifyDatetime->toDateTimeString(); } /** * Check if the given datetime is within the user's time slot. * * @param Carbon\Carbon $datetime * @return bool */ private function isWithinTimeSlot(Carbon $datetime): bool { // if the user has time-sensitive notifications enabled then we should // disregard the time slot. if ($this->prefers_time_sensitive_notifications) { return true; } switch ($this->notify_time_slot) { case 'anytime': // anytime will notify regardless of current time return true; case 'morning': return $datetime->hour >= 6 && $datetime->hour < 12; case 'afternoon': return $datetime->hour >= 12 && $datetime->hour < 18; case 'evening': return $datetime->hour >= 18 && $datetime->hour < 23; case 'daytime': return $datetime->hour >= 9 && $datetime->hour < 18; default: // Handle additional time slots as needed. return false; } } /** * Set the given datetime to the next available time slot. * * @param Carbon\Carbon $datetime * @return Carbon\Carbon */ private function setToNextAvailableTime(Carbon $datetime): Carbon { switch ($this->notify_time_slot) { case 'morning': return $datetime->setHour(6)->setMinute(0)->setSecond(0); case 'afternoon': return $datetime->setHour(12)->setMinute(0)->setSecond(0); case 'evening': return $datetime->setHour(18)->setMinute(0)->setSecond(0); case 'daytime': return $datetime->addDay()->setHour(9)->setMinute(0)->setSecond(0); default: // Handle additional time slots as needed. return $datetime; } }
修正逻辑说明
- 统一周几格式:将Carbon的
dayOfWeek(0=周日,1=周一)转换为用户定义的格式(1=周一,7=周日),避免格式不匹配导致的判断错误。 - 计算最小天数差:遍历所有可通知日期,分别计算本周内、跨周的天数差,保留最小的那个,确保找到最近的可通知日期。
- 脱离数组排序依赖:无论
$notifiableDays数组顺序如何,都能正确找到最近的可通知日期。
内容的提问来源于stack exchange,提问作者Ryan H
相关产品推荐
相关产品推荐

