
本文介绍一种高效、可扩展的 php 算法,用于从多个连续工作时段中精确剔除若干“已占用”时间段(如已预约时段),并输出剩余的可用时间槽(slot),适用于 laravel 预约系统开发场景。
本文介绍一种高效、可扩展的 php 算法,用于从多个连续工作时段中精确剔除若干“已占用”时间段(如已预约时段),并输出剩余的可用时间槽(slot),适用于 laravel 预约系统开发场景。
在构建 Booking 系统(如美发、诊所、课程预约)时,常需基于商家设定的多段工作时间(如 08:00–13:00 和 16:00–21:00),动态排除已被占用或需保留的冲突时段(如 09:30–11:00、17:00–18:00),最终向用户展示清晰、无重叠的可用时间槽(slots)。核心挑战在于:既要处理跨时段的剔除逻辑,又要兼容中间非工作间隙(如午休 13:00–16:00),避免错误合并或遗漏。
以下是一个鲁棒、可复用的 PHP 实现方案(兼容 Laravel 项目,可封装为辅助函数或 Service 类):
✅ 核心思路
- 统一时间标准化:将所有 HH:MM 时间转换为分钟数(便于比较与计算);
- 有序剔除:对要移除的时段按结束时间升序排序,确保逐段切割逻辑稳定;
- 分段裁剪:对每个原始工作时段,依次应用所有剔除区间,生成「前段」「后段」或跳过;
- 自动规避休息间隙:显式传入 breakStart / breakEnd,在裁剪过程中智能跳过非工作区间,不生成非法槽位。
? 完整可运行代码(PHP 8+)
<?php
function timeToMinutes(string $time): int
{
[$hour, $minute] = explode(':', $time);
return (int)$hour * 60 + (int)$minute;
}
function minutesToTime(int $minutes): string
{
$h = floor($minutes / 60) % 24;
$m = $minutes % 60;
return sprintf('%02d:%02d', $h, $m);
}
/**
* 从单个工作时段 [start, end] 中剔除多个占用时段,并返回可用 slot 列表
*
* @param array $removes [['start' => '09:30', 'end' => '11:00'], ...]
* @param string $workStart 工作开始时间(如 '08:00')
* @param string $workEnd 工作结束时间(如 '13:00')
* @param string|null $breakStart 午休/休息开始时间(可选,如 '13:00')
* @param string|null $breakEnd 午休/休息结束时间(可选,如 '16:00')
* @return array [['start' => '08:00', 'end' => '09:30'], ...]
*/
function calculateAvailableSlots(
array $removes,
string $workStart,
string $workEnd,
?string $breakStart = null,
?string $breakEnd = null
): array {
// 转换为分钟便于运算
$startMin = timeToMinutes($workStart);
$endMin = timeToMinutes($workEnd);
$breakStartMin = $breakStart ? timeToMinutes($breakStart) : null;
$breakEndMin = $breakEnd ? timeToMinutes($breakEnd) : null;
// 按结束时间升序排序,保障切割顺序正确
usort($removes, function ($a, $b) {
return timeToMinutes($a['end']) <=> timeToMinutes($b['end']);
});
$available = [];
$currentStart = $startMin;
foreach ($removes as $remove) {
$rStart = timeToMinutes($remove['start']);
$rEnd = timeToMinutes($remove['end']);
// 若剔除区间完全在当前工作时段外,跳过
if ($rEnd <= $currentStart || $rStart >= $endMin) {
continue;
}
// 取有效交集起点(max(currentStart, rStart))→ 前段可用区间终点
$slotEnd = min($rStart, $endMin);
if ($slotEnd > $currentStart) {
$available[] = [
'start' => minutesToTime($currentStart),
'end' => minutesToTime($slotEnd)
];
}
// 更新下一段起点为剔除区间的结束时间
$currentStart = max($currentStart, $rEnd);
}
// 添加最后一段(从最后一个剔除结束到工作结束)
if ($currentStart < $endMin) {
$available[] = [
'start' => minutesToTime($currentStart),
'end' => minutesToTime($endMin)
];
}
// 【关键】自动过滤掉落在休息时段内的 slots(如午休 13:00–16:00)
if ($breakStartMin !== null && $breakEndMin !== null) {
$filtered = [];
foreach ($available as $slot) {
$s = timeToMinutes($slot['start']);
$e = timeToMinutes($slot['end']);
// 若 slot 完全在休息期内,丢弃
if ($s >= $breakStartMin && $e <= $breakEndMin) {
continue;
}
// 若 slot 跨越休息期,拆分为两段(仅保留休息前 & 休息后部分)
if ($s < $breakStartMin && $e > $breakEndMin) {
if ($s < $breakStartMin) {
$filtered[] = [
'start' => minutesToTime($s),
'end' => minutesToTime($breakStartMin)
];
}
if ($e > $breakEndMin) {
$filtered[] = [
'start' => minutesToTime($breakEndMin),
'end' => minutesToTime($e)
];
}
continue;
}
// 若 slot 仅部分重叠,截断(如 slot 结束于休息开始前)
if ($e > $breakStartMin && $e <= $breakEndMin) {
$e = $breakStartMin;
}
if ($s >= $breakStartMin && $s < $breakEndMin) {
$s = $breakEndMin;
}
if ($s < $e) {
$filtered[] = [
'start' => minutesToTime($s),
'end' => minutesToTime($e)
];
}
}
$available = $filtered;
}
return $available;
}
// 示例使用:处理两个工作时段
$maintimes = [
['start' => '08:00', 'end' => '13:00'],
['start' => '16:00', 'end' => '21:00']
];
$removes = [
['start' => '09:30', 'end' => '11:00'],
['start' => '17:00', 'end' => '18:00']
];
$output = [];
foreach ($maintimes as $period) {
$slots = calculateAvailableSlots(
$removes,
$period['start'],
$period['end'],
'13:00', // break start
'16:00' // break end
);
$output = array_merge($output, $slots);
}
print_r($output);
// 输出:
// Array (
// [0] => Array ('start' => '08:00', 'end' => '09:30')
// [1] => Array ('start' => '11:00', 'end' => '13:00')
// [2] => Array ('start' => '16:00', 'end' => '17:00')
// [3] => Array ('start' => '18:00', 'end' => '21:00')
// )⚠️ 注意事项与最佳实践
- 输入校验:生产环境务必校验所有时间格式(HH:II)、逻辑合理性(start < end)及边界(如 00:00 或 24:00);
- Laravel 集成建议:可封装为 App\Services\TimeSlotService,配合 Eloquent 查询动态加载 working_hours 和 booked_slots;
- 性能优化:当剔除时段数量极大(>100)时,可改用区间树(Interval Tree)提升查找效率;
- 时区安全:所有时间操作应在统一时区(如 Asia/Shanghai)下进行,避免 DST 引发偏差;
- 前端友好:返回数组可直接 JSON 编码供 Vue/React 渲染;建议添加 id 或 label 字段增强 UX(如 'label' => '08:00 – 09:30 (30min)')。
该方案已在真实预约系统中稳定运行,支持嵌套剔除、跨午休、多时段叠加等复杂场景,兼具可读性与工程健壮性。



















