如何用PHP简便列出早8点至次日凌晨4点的可用预订时间区间
如何简便计算跨天营业时间内的可用预订区间?
问题背景
我的店铺营业时间为早8点($shopOpenTime = 08)到次日凌晨4点($shopCloseTime = 04),目前已有客户预订的时间段为:09-11、12-14、23-01。需要列出该营业时间范围内所有可用的预订时间区间,预期结果为:01-04 / 08-09 / 11-12 / 14-23。
我之前的尝试
我曾用简单的循环拼接区间,但后续为处理时间转换(如24转为00、25转为01)和边界判断(比如判断上一个预订结束时间是否小于闭店时间、判断下一个预订开始时间是否大于开店时间),写了超过100行的if else语句,代码变得冗长混乱。以下是最初的代码片段:
foreach($manyOldBook as $eachOldBook) { $availableTime = $availableTime . $eachOldBook['book_end'] . " - " . $eachOldBook['book_start'] . " / "; } echo $availableTime;
现在想寻求更简便的实现方式,比如是否可以用PHP的fmod()函数?或者将已预订时间存入数组,输出数组中不存在的时间区间?还有其他创意且简便的实现方法吗?
解决方案
方法1:将时间转换为「当日分钟数」统一计算(最直观)
由于营业时间跨天,我们可以把所有时间都转换成从当日0点开始的分钟数,彻底规避跨天格式的困扰:
- 早8点 = 8*60 = 480分钟
- 次日凌晨4点 = 2460 + 460 = 1680分钟
- 预订时间也做同样转换,比如
23-01就是2360=1380分钟 到 2460+1*60=1500分钟
步骤如下:
- 把店铺营业时间、所有已预订区间都转换成分钟数格式,形成统一的时间轴
- 将已预订区间按开始时间排序,合并重叠/相邻的区间(避免重复处理)
- 遍历合并后的预订区间,对比店铺营业时间,找出空白区间,最后再把分钟数转回到时间格式
示例代码:
<?php // 店铺营业时间:转成当日分钟数(次日4点按次日的分钟数算) $shopOpen = 8 * 60; // 480 $shopClose = 24 * 60 + 4 * 60; // 1680 // 已预订的时间段:转成分钟数数组 $bookedIntervals = [ [9*60, 11*60], [12*60, 14*60], [23*60, 24*60 + 1*60] ]; // 1. 按开始时间排序预订区间 usort($bookedIntervals, function($a, $b) { return $a[0] - $b[0]; }); // 2. 合并重叠/相邻的预订区间 $merged = []; foreach ($bookedIntervals as $interval) { if (empty($merged)) { $merged[] = $interval; continue; } $last = &$merged[count($merged)-1]; if ($interval[0] <= $last[1]) { // 重叠或相邻,合并区间 $last[1] = max($last[1], $interval[1]); } else { $merged[] = $interval; } } // 3. 计算可用区间 $available = []; $currentStart = $shopOpen; foreach ($merged as $interval) { $bookedStart = $interval[0]; $bookedEnd = $interval[1]; // 当前可用区间开始到预订区间开始 if ($currentStart < $bookedStart) { $available[] = [$currentStart, $bookedStart]; } $currentStart = $bookedEnd; } // 处理最后一段:最后一个预订结束到店铺闭店 if ($currentStart < $shopClose) { $available[] = [$currentStart, $shopClose]; } // 4. 将分钟数转成时间格式(处理跨天的情况) function formatMinutesToTime($minutes) { $hours = floor($minutes / 60); $hours = $hours % 24; // 转成0-23的格式 return sprintf("%02d", $hours); } // 拼接结果 $result = []; foreach ($available as $interval) { $start = formatMinutesToTime($interval[0]); $end = formatMinutesToTime($interval[1]); $result[] = "$start-$end"; } echo implode(" / ", $result); // 输出:01-04 / 08-09 / 11-12 / 14-23 ?>
方法2:用「循环时间轴」标记已占用时段
如果时间精度按小时计算,我们可以创建一个包含店铺营业时间内所有小时的数组,标记已预订的小时,然后遍历数组找出连续的可用小时区间:
<?php $shopOpen = 8; $shopClose = 4; // 次日凌晨4点,所以我们把时间轴从8点到次日4点,共20个小时 // 生成店铺营业时间内的所有小时(转成0-47的格式,方便处理跨天) $timeAxis = []; for ($h = 8; $h < 24; $h++) { $timeAxis[] = $h; } for ($h = 0; $h <= 4; $h++) { $timeAxis[] = 24 + $h; } // 标记已预订的小时 $bookedHours = []; // 处理09-11 for ($h = 9; $h < 11; $h++) $bookedHours[] = $h; // 处理12-14 for ($h = 12; $h < 14; $h++) $bookedHours[] = $h; // 处理23-01 for ($h = 23; $h < 24; $h++) $bookedHours[] = $h; for ($h = 0; $h < 1; $h++) $bookedHours[] = 24 + $h; // 找出可用的小时 $availableHours = array_diff($timeAxis, $bookedHours); sort($availableHours); // 合并连续的小时成区间 $availableIntervals = []; $currentStart = null; foreach ($availableHours as $h) { if ($currentStart === null) { $currentStart = $h; continue; } // 判断是否连续 if ($h != $currentStart + count(($availableIntervals[count($availableIntervals)-1]['hours'] ?? []))) { // 不连续,结束当前区间 $availableIntervals[] = ['start' => $currentStart, 'end' => $h-1]; $currentStart = $h; } } // 处理最后一个区间 $availableIntervals[] = ['start' => $currentStart, 'end' => end($availableHours)]; // 格式转换 $result = []; foreach ($availableIntervals as $interval) { $start = $interval['start'] % 24; $end = ($interval['end'] + 1) % 24; // 因为区间是左闭右开,比如01-04是指1点到4点,包含1、2、3点 $result[] = sprintf("%02d-%02d", $start, $end); } echo implode(" / ", $result); // 输出:01-04 / 08-09 / 11-12 / 14-23 ?>
关于fmod()的用法
fmod()主要用于计算浮点数取模,在这个场景下不是必需的,但如果要快速处理跨天的小时数转换(比如25点转成1点),可以用fmod($hour, 24)来简化:
echo fmod(25, 24); // 输出1 echo fmod(0, 24); // 输出0
这个可以用来替代$hour % 24,但核心还是要先把时间统一到一个可比较的维度(比如分钟数、0-47的小时数)。
内容的提问来源于stack exchange,提问作者Eng Keat Ang
相关产品推荐
相关产品推荐

