在员工考勤管理场景中,统计员工缺勤天数需要计算员工请假日期范围与考核周期日期范围的交集,PHP提供了多种日期处理工具,能够高效完成这类计算任务。

基础概念与日期范围表示
日期范围通常由起始日期和结束日期组成,在PHP中可以使用DateTime类来表示单个日期,也可以自定义结构体存储范围信息。比如员工请假范围可以表示为从2024-05-10到2024-05-15,考核周期范围是从2024-05-01到2024-05-31,两者的交集就是缺勤的有效天数。
基础的日期范围定义方式
我们可以先定义一个简单的日期范围类,方便后续操作:
<?php
/**
* 日期范围类
*/
class DateRange {
public DateTime $start;
public DateTime $end;
public function __construct(string $startDate, string $endDate) {
$this->start = new DateTime($startDate);
$this->end = new DateTime($endDate);
// 确保起始日期不晚于结束日期
if ($this->start > $this->end) {
throw new InvalidArgumentException('起始日期不能晚于结束日期');
}
}
}
日期范围交集计算的核心逻辑
两个日期范围的交集计算逻辑很简单:交集的起始日期是两个范围起始日期的较大值,交集的结束日期是两个范围结束日期的较小值。如果起始日期大于结束日期,说明两个范围没有交集。
通用交集计算方法实现
我们可以给DateRange类添加交集计算的方法:
<?php
class DateRange {
public DateTime $start;
public DateTime $end;
public function __construct(string $startDate, string $endDate) {
$this->start = new DateTime($startDate);
$this->end = new DateTime($endDate);
if ($this->start > $this->end) {
throw new InvalidArgumentException('起始日期不能晚于结束日期');
}
}
/**
* 计算与另一个日期范围的交集
* @param DateRange $other 另一个日期范围
* @return DateRange|null 有交集返回交集范围,无交集返回null
*/
public function getIntersection(DateRange $other): ?DateRange {
// 计算交集的起始和结束日期
$intersectStart = max($this->start, $other->start);
$intersectEnd = min($this->end, $other->end);
// 无交集的情况
if ($intersectStart > $intersectEnd) {
return null;
}
return new DateRange(
$intersectStart->format('Y-m-d'),
$intersectEnd->format('Y-m-d')
);
}
}
员工缺勤天数统计实战
有了基础的交集计算方法,就可以实现员工缺勤天数的统计功能。假设我们需要统计某员工在指定考核周期内的缺勤天数,员工可能有多次请假记录,需要分别计算每次请假与考核周期的交集,再汇总天数。
统计单个员工缺勤天数
下面的代码实现了统计功能,同时排除了周末(可根据需求调整排除规则):
<?php
/**
* 统计员工在考核周期内的缺勤天数
* @param array $leaveRecords 请假记录数组,每个元素为['start' => 'Y-m-d', 'end' => 'Y-m-d']
* @param string $assessStart 考核周期起始日期
* @param string $assessEnd 考核周期结束日期
* @return int 缺勤天数
*/
function countAbsenceDays(array $leaveRecords, string $assessStart, string $assessEnd): int {
$assessRange = new DateRange($assessStart, $assessEnd);
$totalDays = 0;
foreach ($leaveRecords as $record) {
$leaveRange = new DateRange($record['start'], $record['end']);
$intersection = $leaveRange->getIntersection($assessRange);
if ($intersection === null) {
continue;
}
// 计算交集范围内的有效天数,排除周末
$current = clone $intersection->start;
$end = clone $intersection->end;
while ($current <= $end) {
$dayOfWeek = $current->format('w');
// 0是周日,6是周六,排除周末
if ($dayOfWeek != 0 && $dayOfWeek != 6) {
$totalDays++;
}
$current->modify('+1 day');
}
}
return $totalDays;
}
// 测试示例
$leaveRecords = [
['start' => '2024-05-10', 'end' => '2024-05-15'],
['start' => '2024-05-20', 'end' => '2024-05-22'],
];
$assessStart = '2024-05-01';
$assessEnd = '2024-05-31';
$absenceDays = countAbsenceDays($leaveRecords, $assessStart, $assessEnd);
echo "员工本月缺勤天数:{$absenceDays}天";
方案对比与性能说明
除了上述基于DateTime的方案,也可以使用时间戳计算或者循环遍历日期的方式,但DateTime方案的优势更明显:
- 内置日期比较、修改方法,无需手动处理月份天数、闰年等边界问题
- 代码可读性强,逻辑清晰,后续维护成本低
- 对于常规考勤场景的日期范围计算,性能完全足够,无需过度优化
如果处理超大量的日期范围计算(比如数万条请假记录),可以考虑先将日期转换为时间戳整数,用整数比较代替对象比较,进一步提升性能,但常规业务场景下上述方案已经足够高效。