
本文介绍一种简洁、可扩展的 PHP 方案,通过 DatePeriod 遍历日期并按星期几动态匹配预设工时(如周一至周五 8 小时、周六 3 小时、周日 0 小时),精准计算跨多日的总工作小时数,支持节假日排除,且逻辑清晰、易于维护。
本文介绍一种简洁、可扩展的 php 方案,通过 `dateperiod` 遍历日期并按星期几动态匹配预设工时(如周一至周五 8 小时、周六 3 小时、周日 0 小时),精准计算跨多日的总工作小时数,支持节假日排除,且逻辑清晰、易于维护。
在实际排班或工时统计场景中,常需区分不同日期类型(如工作日、周六、周日、节假日)的工时标准。例如:周一至周五每日 8 小时(含午休)、周六仅上午 3 小时(08:00–11:00)、周日及法定假日为 0 小时。此时,若简单套用固定日工时或复杂的时间段交集计算,易导致逻辑冗余、边界错误或难以维护。
以下推荐一种语义清晰、配置驱动、面向迭代的实现方式:
✅ 核心思路
- 配置化工时规则:以星期缩写('Mon', 'Sat' 等)为键,定义每日标准工时;
- 日期遍历代替时间差拆解:使用 DatePeriod 按天生成所有日期对象,避免手动处理跨日、起止时间截断等复杂逻辑;
- 逐日累加,天然支持例外处理:每轮循环可灵活加入节假日、调休日等业务判断。
? 示例代码(完整可运行类)
class CountWorkingHours
{
// 【关键配置】按星期缩写定义每日标准工时(单位:小时)
public array $hours = [
'Mon' => 8,
'Tue' => 8,
'Wed' => 8,
'Thu' => 8,
'Fri' => 8,
'Sat' => 3,
'Sun' => 0,
];
// 【可选】配置节假日列表(格式:'Y-m-d')
public array $skip_dates = [
'2022-04-25', // 劳动节调休
'2022-04-30', // 周六但公司放假
];
/**
* 计算指定时间段内的总工作小时数(按自然日计,不细分起止时刻)
* 注意:本方法默认统计「完整日」工时;若需精确到起止时间点(如 09:30 开始),需额外扩展逻辑
*/
public function get_hours_for_period(string $from, string $to): int
{
$start = new DateTime($from);
$end = new DateTime($to);
// DatePeriod 包含起始日,不包含结束日 → 手动向后延一天确保覆盖终点日
$end->modify('+1 day');
$period = new DatePeriod($start, new DateInterval('P1D'), $end);
$totalHours = 0;
foreach ($period as $date) {
$dateStr = $date->format('Y-m-d');
$dayCode = $date->format('D'); // 返回 'Mon', 'Tue', ..., 'Sun'
// 跳过节假日或非工作日
if (in_array($dateStr, $this->skip_dates, true)) {
continue;
}
$totalHours += $this->hours[$dayCode] ?? 0;
}
return $totalHours;
}
}▶️ 使用示例
$cwh = new CountWorkingHours();
// 示例:2022-04-21(周四)07:00 至 2022-04-23(周六)16:00 → 应得: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'; // 强制将周五设为休息日
$hours = $cwh->get_hours_for_period('2022-04-21 07:00:00', '2022-04-23 16:00:00');
echo $hours; // 输出:8 + 0 + 3 = 11⚠️ 注意事项与扩展建议
- 起止时间精度说明:当前实现按「自然日」统计,即只要某日落在区间内,就计入其全天标准工时。若需支持“部分日工时”(如从周三 14:00 开始,只计剩余 2 小时),需在循环中增加对 $date 与 $from/$to 的时间比对,并结合 $this->hours_by_time 等更细粒度配置。
- 时区安全:建议初始化 DateTime 时显式指定时区(如 new DateTime($from, new DateTimeZone('Asia/Shanghai'))),避免服务器默认时区干扰。
- 性能提示:对于超长周期(如数年),DatePeriod 迭代仍高效;若需极致性能(如百万级日期),可改用日期数学批量计算周数+余数,但会牺牲可读性。
- 配置外置化:生产环境建议将 $hours 和 $skip_dates 移至配置文件或数据库,便于运维动态调整。
该方案摒弃了传统基于秒级差值与条件嵌套的脆弱逻辑,转而采用「声明式配置 + 清晰迭代」范式,显著提升可读性、可测试性与可维护性,是企业级工时系统值得借鉴的基础架构。

















