
本文详解如何在 laravel 7 中基于 carbon 和数据库查询,准确计算两个日期间的有效工作日天数,自动剔除周末(如周五)和已发布的法定节假日,并动态得出最终请假天数。
本文详解如何在 laravel 7 中基于 carbon 和数据库查询,准确计算两个日期间的有效工作日天数,自动剔除周末(如周五)和已发布的法定节假日,并动态得出最终请假天数。
在处理员工请假申请时,仅用 end_date - start_date + 1 计算总天数是不严谨的——必须扣除每周固定休息日(如配置为周五)和系统中已发布的法定节假日(如 2022-05-26)。Laravel 生态中的 Carbon 库为此类日期逻辑提供了简洁、可靠的解决方案。
✅ 正确做法:分三步精准剔除非工作日
1. 使用 Carbon 计算「自然工作日」(排除默认周末)
Carbon 的 diffInWeekdays() 方法默认排除周六(6)和周日(0),但注意:它不识别自定义周末(如题中“周五为休息日”)。因此需先统一将“自定义休息日”纳入逻辑:
use Carbon\Carbon;
$startDate = Carbon::parse($request->start_date);
$endDate = Carbon::parse($request->end_date);
// Step 1: 获取所有日期范围内的日期实例(含起止日)
$dates = collect();
for ($date = $startDate->copy(); $date->lte($endDate); $date->addDay()) {
$dates->push($date->copy());
}
// Step 2: 过滤出「非工作日」——包括:① 配置的 weekly holiday(如 'Fri');② 数据库中的 holiday
$weeklyOffDays = WorkingDay::where('working_status', 1)->pluck('day')->map(fn($d) => strtoupper(substr($d, 0, 3)))->toArray(); // ['FRI']
$holidays = Holiday::where('publication_status', 1)
->whereBetween('date', [$startDate->format('Y-m-d'), $endDate->format('Y-m-d')])
->pluck('date')
->map(fn($d) => Carbon::parse($d)->format('Y-m-d'))
->toArray();
$nonWorkingDays = $dates->filter(function ($date) use ($weeklyOffDays, $holidays) {
$dayAbbr = $date->format('D'); // e.g., 'FRI', 'SAT'
$dateStr = $date->format('Y-m-d');
return in_array($dayAbbr, $weeklyOffDays) || in_array($dateStr, $holidays);
})->count();
$totalCalendarDays = $startDate->diffInDays($endDate) + 1;
$effectiveWorkDays = $totalCalendarDays - $nonWorkingDays;
2. 关键注意事项
- ❗ diffInWeekdays() 不可直接用于自定义周末场景(如题中 Fri=1),它硬编码了 Sat/Sun 排除逻辑;
- ✅ 务必使用 whereBetween() 限定节假日查询范围,避免全表扫描;
- ✅ 周末配置应支持多选(如 ['Fri', 'Sat']),WorkingDay::pluck('day') 需统一格式化为大写缩写;
- ✅ 若启用了半日假($request->halfday == 1),最终结果应设为 0.5,而非参与减法运算。
3. 完整控制器逻辑示例
public function store(Request $request)
{
$request->validate([
'start_date' => 'required|date',
'end_date' => 'required|date|after_or_equal:start_date',
]);
$start = Carbon::parse($request->start_date);
$end = Carbon::parse($request->end_date);
// 获取自定义休息日(如 ['FRI'])
$offDays = WorkingDay::where('working_status', 1)
->pluck('day')
->map(fn($d) => strtoupper(substr($d, 0, 3)))
->toArray();
// 查询范围内法定节假日
$holidayDates = Holiday::where('publication_status', 1)
->whereBetween('date', [$start->format('Y-m-d'), $end->format('Y-m-d')])
->pluck('date')
->map(fn($d) => Carbon::parse($d)->format('Y-m-d'))
->toArray();
// 枚举所有日期并统计非工作日
$nonWorkingCount = 0;
for ($d = $start->copy(); $d->lte($end); $d->addDay()) {
$dayCode = $d->format('D');
$dateCode = $d->format('Y-m-d');
if (in_array($dayCode, $offDays) || in_array($dateCode, $holidayDates)) {
$nonWorkingCount++;
}
}
$totalDays = $start->diffInDays($end) + 1;
$finalDays = $request->halfday ? 0.5 : max(0, $totalDays - $nonWorkingCount);
$leave = new Leave(); // 注意:原代码中误用 User 模型,应为 Leave
$leave->start_date = $request->start_date;
$leave->end_date = $request->end_date;
$leave->total_days = $finalDays;
$leave->save();
return response()->json(['total_days' => $finalDays]);
}
? 总结:不要依赖 diffInWeekdays() 处理业务定制的休息日;通过显式遍历日期 + 条件过滤,逻辑清晰、可维护性强,且兼容任意组合的周休日与多节假日场景。同时确保数据库索引覆盖 date 和 publication_status 字段以提升查询性能。











