
本文介绍如何在 php(兼容 laravel)中,基于原始工作时间段和需排除的占用时段,精确计算出连续、不重叠的可用预约插槽,并返回标准化的时间段数组。
本文介绍如何在 php(兼容 laravel)中,基于原始工作时间段和需排除的占用时段,精确计算出连续、不重叠的可用预约插槽,并返回标准化的时间段数组。
在构建预约类系统(如美发、医疗、课程预订)时,常需将商家设定的「工作总时段」(如 08:00–13:00 和 16:00–21:00)与临时「不可用时段」(如 09:30–11:00、17:00–18:00)进行逻辑差集运算,最终输出用户可选的、互斥且连续的可用插槽。该过程本质是时间区间差集计算,需兼顾排序、边界对齐与跨段处理。
以下是一个健壮、可复用的 PHP 实现方案(Laravel 中可直接封装为辅助函数或 Service):
✅ 核心逻辑说明
- 将所有时间统一转为分钟整数(如 09:30 → 570),便于数值比较;
- 对待移除时段按结束时间升序排序,确保逐段切割时无遗漏;
- 遍历每个原始工作时段(如 08:00–13:00),在其内部依次“挖掉”所有与其重叠的移除时段;
- 每次移除操作产生至多两个新区间:[原开始, 移除开始] 和 [移除结束, 原结束];
- 自动跳过完全不重叠或被完全覆盖的无效移除项;
- 支持多段工作时间(如午休间隙 13:00–16:00)——本方案默认将其视为自然分隔,无需额外标记。
? 完整可运行代码(兼容 PHP 7.4+)
<?php function timeToMinutes(string $time): int
{
[$h, $m] = explode(':', $time);
return (int)$h * 60 + (int)$m;
}
function minutesToTime(int $minutes): string
{
$h = floor($minutes / 60);
$m = $minutes % 60;
return sprintf('%02d:%02d', $h, $m);
}
/**
* 从多个工作时段中剔除指定占用时段,返回可用插槽列表
*
* @param array $workingTimes [['start' => '08:00', 'end' => '13:00'], ...]
* @param array $blockedTimes [['start' => '09:30', 'end' => '11:00'], ...]
* @return array [['start' => '08:00', 'end' => '09:30'], ...]
*/
function calculateAvailableSlots(array $workingTimes, array $blockedTimes): array
{
$result = [];
foreach ($workingTimes as $work) {
$start = timeToMinutes($work['start']);
$end = timeToMinutes($work['end']);
// 初始化当前待处理区间
$intervals = [[$start, $end]];
// 对每个占用时段,逐一裁剪所有现存区间
foreach ($blockedTimes as $block) {
$bStart = timeToMinutes($block['start']);
$bEnd = timeToMinutes($block['end']);
$newIntervals = [];
foreach ($intervals as [$iStart, $iEnd]) {
// 当前区间完全在占用时段左侧或右侧 → 保留
if ($iEnd = $bEnd) {
$newIntervals[] = [$iStart, $iEnd];
continue;
}
// 左侧有剩余 → [iStart, bStart]
if ($iStart $bEnd) {
$newIntervals[] = [$bEnd, $iEnd];
}
// 若完全被覆盖,则不添加任何新区间
}
$intervals = $newIntervals;
}
// 将分钟区间转回 'HH:MM' 格式并加入结果
foreach ($intervals as [$s, $e]) {
if ($s minutesToTime($s),
'end' => minutesToTime($e),
];
}
}
}
return $result;
}
// 示例数据
$workingTimes = [
['start' => '08:00', 'end' => '13:00'],
['start' => '16:00', 'end' => '21:00'],
];
$blockedTimes = [
['start' => '09:30', 'end' => '11:00'],
['start' => '17:00', 'end' => '18:00'],
];
$output = calculateAvailableSlots($workingTimes, $blockedTimes);
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 )
// )
⚠️ 注意事项与最佳实践
- 时区一致性:确保所有时间字符串均基于同一时区(推荐使用 UTC 存储,展示时转换);
- 输入校验:生产环境务必验证 $workingTimes 和 $blockedTimes 中每个时段的合法性(如 start
- 性能优化:若需高频调用(如千级并发预约页),建议将算法预计算缓存,或改用数据库区间运算(MySQL 8.0+ 的 TIMESTAMPDIFF 或 PostgreSQL 的 tsrange);
-
Laravel 集成建议:可封装为 App\Services\TimeSlotService,并在控制器中调用:
$slots = TimeSlotService::calculate($listing->working_hours, $bookedIntervals);
该方案简洁、无外部依赖、边界处理严谨,已通过多组重叠/嵌套/边缘案例验证,可作为预约系统核心时间调度模块稳定使用。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











