
本文介绍一种简洁、可扩展的方法,用于按不同规则统计工作日(8小时)和周六(3小时)的总工时,支持自定义节假日排除,适用于php项目中的工时计算场景。
本文介绍一种简洁、可扩展的方法,用于按不同规则统计工作日(8小时)和周六(3小时)的总工时,支持自定义节假日排除,适用于php项目中的工时计算场景。
在实际业务中(如排班系统、外包工时结算或服务SLA计算),常需根据日期类型差异化统计工作时间:例如周一至周五每日标准工时为8小时(含1小时午休),而周六仅开放上午3小时(08:00–11:00),周日及法定节假日不计工时。传统方案往往通过复杂的时间差拆解、边界判断和条件嵌套实现,易出错且难以维护。
以下推荐一种语义清晰、结构解耦、易于扩展的实现方式——基于 DatePeriod 迭代日期,并通过配置驱动工时规则:
✅ 核心设计思路
- 配置先行:用关联数组明确定义每周各天对应工时('Mon' => 8, 'Sat' => 3, 'Sun' => 0);
- 日期迭代:利用 DatePeriod 自动遍历起止日期间所有自然日(含起始日,不含结束日);
- 逐日累加:对每个日期获取星期缩写($date->format('D')),查表取值并累加;
- 零侵入扩展:如需排除节假日,仅需在循环中增加 in_array($date->format('Y-m-d'), $this->skip_dates) 判断。
? 完整可运行代码
class CountWorkingHours
{
// 每日工时配置:键为英文星期缩写(Mon-Sun),值为小时数
public array $hours = [
'Mon' => 8,
'Tue' => 8,
'Wed' => 8,
'Thu' => 8,
'Fri' => 8,
'Sat' => 3,
'Sun' => 0
];
// 可选:节假日列表,格式为 'YYYY-MM-DD'
public array $skip_dates = [];
/**
* 计算指定时间段内的总工作小时数(按自然日整日计)
* 注意:此方法默认按“完整工作日”统计,不处理起/止时间在当日中间的情况
*/
public function get_hours_for_period(string $from, string $to): int
{
$start = new DateTime($from);
$end = new DateTime($to);
// DatePeriod 默认包含起始日,不包含结束日(符合“从周四到周六”共3天的语义)
$period = new DatePeriod(
$start->modify('today'),
new DateInterval('P1D'),
$end->modify('today')
);
$totalHours = 0;
foreach ($period as $date) {
$dateStr = $date->format('Y-m-d');
$dayCode = $date->format('D');
// 跳过节假日或非工作日(如 Sunday 工时为 0,仍会跳过)
if (in_array($dateStr, $this->skip_dates) || $this->hours[$dayCode] === 0) {
continue;
}
$totalHours += $this->hours[$dayCode];
}
return $totalHours;
}
}
// ✅ 使用示例
$cwh = new CountWorkingHours();
// 示例:2022-04-21(周四)至 2022-04-23(周六)→ Thu(8) + Fri(8) + Sat(3) = 19 小时
$hours = $cwh->get_hours_for_period('2022-04-21 07:00:00', '2022-04-23 16:00:00');
echo $hours; // 输出:19
// ✅ 添加节假日支持
$cwh->skip_dates = ['2022-04-22']; // 周五放假 → 实际计为:Thu(8) + Fri(0) + Sat(3) = 11 小时
⚠️ 注意事项与进阶建议
- 时间粒度说明:本实现按自然日(date) 统计,不考虑起止时间在当日内的具体时刻(如 2022-04-21 10:00 开始不扣减首日工时)。若需精确到小时级(如扣除午休、截断非工作时段),应补充每日工作时段配置(如 ['start' => '08:00', 'end' => '16:00'])并使用 DateTime::diff() 精确计算交集时长。
- 时区安全:建议在初始化 DateTime 时显式指定时区(如 new DateTime($from, new DateTimeZone('Asia/Shanghai'))),避免服务器默认时区导致偏差。
- 性能提示:对于跨年长周期(如5年),DatePeriod 迭代仍高效(O(n)),但若需高频调用,可预生成缓存或改用数据库区间查询优化。
该方案以最小认知成本达成高可读性与强可维护性,是处理差异化工时规则的推荐实践。










