PHP优化两日期间指定星期天数统计的方案咨询
优化星期天数统计的两种高效方案
你的需求是统计两个日期之间符合指定星期编号的天数,原来的循环方法功能没问题,但日期跨度大时效率偏低,下面给你两种优化方案,都能大幅减少执行时间:
方案一:PHP数学计算优化(减少循环次数)
核心思路是先计算完整的周数,每个完整周里符合条件的天数是固定的,直接用「完整周数 × 每周符合天数」得到这部分的总数;然后只需要循环处理剩余的不足一周的天数,这样循环最多只跑7次,完全避免了几百次的循环。
代码示例:
$days = $model->days; // 逗号分隔的星期编号,比如"1,3,5,6" $targetDays = array_map('intval', explode(',', $days)); $startDate = new DateTime($model->activity_start_date); $endDate = new DateTime(); // 当前日期 // 计算总天数,处理开始日期晚于结束日期的情况 $interval = $startDate->diff($endDate); $totalDays = $interval->days; if ($totalDays < 0) { echo 0; exit; } // 每周符合条件的天数 $daysPerWeek = count($targetDays); // 完整周数 $fullWeeks = intval($totalDays / 7); $count = $fullWeeks * $daysPerWeek; // 处理剩余的天数(最多6天) $remainingDays = $totalDays % 7; $tempDate = clone $startDate; for ($i = 0; $i <= $remainingDays; $i++) { $currentDay = (int)$tempDate->format('N'); if (in_array($currentDay, $targetDays)) { $count++; } $tempDate->modify('+1 day'); } echo $count;
这个方法把循环次数从几百次降到最多7次,效率提升非常明显,而且逻辑清晰,容易维护。
方案二:直接用MySQL查询统计
如果你的事件数据存在MySQL数据库里,直接用SQL查询统计会更高效,不需要PHP处理日期循环。这里提供两种SQL写法:
写法1:用递归CTE生成日期范围(直观易懂)
适合MySQL 8.0及以上版本,通过递归生成日期序列,然后筛选符合星期编号的日期并计数:
SET @start_date = '2018-04-09'; SET @end_date = CURDATE(); SET @target_days = '1,3,5,6'; -- 目标星期编号(1=周一,7=周日) SELECT COUNT(*) AS total_count FROM ( WITH RECURSIVE date_range AS ( SELECT @start_date AS date UNION ALL SELECT DATE_ADD(date, INTERVAL 1 DAY) FROM date_range WHERE date < @end_date ) SELECT * FROM date_range ) AS dates WHERE FIND_IN_SET(DAYOFWEEK(date) - 1, @target_days) > 0; -- 注:MySQL的DAYOFWEEK返回1=周日,2=周一...,减1后和你的星期编号规则匹配
写法2:数学计算的SQL(更高性能)
和PHP优化思路一致,用数学计算避免生成所有日期记录,性能最优,适合超大日期跨度的场景:
SET @start_date = '2018-04-09'; SET @end_date = CURDATE(); SET @target_days = '1,3,5,6'; -- 计算每周符合条件的天数 SET @days_per_week = ( SELECT COUNT(*) FROM ( SELECT TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(@target_days, ',', n), ',', -1)) AS day FROM (SELECT 1 n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7) nums WHERE n <= LENGTH(@target_days) - LENGTH(REPLACE(@target_days, ',', '')) + 1 ) AS tmp ); -- 计算总天数、完整周数、剩余天数 SET @total_days = DATEDIFF(@end_date, @start_date) + 1; SET @full_weeks = FLOOR(@total_days / 7); SET @remaining_days = @total_days % 7; -- 统计剩余天数里符合条件的数量 SET @remaining_count = ( SELECT COUNT(*) FROM ( SELECT DATE_ADD(@start_date, INTERVAL i DAY) AS date FROM (SELECT 0 i UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6) nums WHERE i < @remaining_days ) AS tmp WHERE FIND_IN_SET(DAYOFWEEK(date) - 1, @target_days) > 0 ); -- 输出总次数 SELECT @full_weeks * @days_per_week + @remaining_count AS total_count;
验证示例
以你提到的2018-04-9至2018-05-9为例,假设目标星期是周一、周三(编号1、3):
- 总天数31天,完整周数4周(28天),每周符合2天,这部分是8次;
- 剩余3天里,2018-05-07(周一)、2018-05-09(周三)符合条件,加2次;
- 总计8+1=9次(因为2018-05-09是结束日期,需要包含),和你的示例结果一致。
内容的提问来源于stack exchange,提问作者Yasin Patel
相关产品推荐
相关产品推荐

