很多业务系统的营业时间并不遵循自然日(0:00-24:00),比如酒吧、KTV、便利店、夜班生产等场景。这类系统面临的共同挑战是:**如何定义”一天”**?
本文以酒吧下单系统为例,梳理跨日处理的常见方案和设计要点。
场景描述
某酒吧的营业时间为每日 21:00 至次日 05:00,且每个营业日归属于开始日期。
举个例子:
- 10月1日营业日:10月1日 21:00 ~ 10月2日 05:00
- 10月2日营业日:10月2日 21:00 ~ 10月3日 05:00
系统按营业日划分,而非自然日。这带来了一系列问题。
核心问题:时间归属
假设客人在凌晨 2:00(即10月2日 02:00)下单,预定”当天晚上 10 点”的桌台。
这里”当天”究竟指什么?
1 2 3 4 5 6
| 时间轴: 10/1 21:00 ──────── 10/2 00:00 ──────── 10/2 05:00 ──── ... ──── 10/2 21:00 │ │ │ │ 营业日开始 自然日切换 营业日结束 下个营业日开始 ↑ 客人凌晨2点下单
|
分析:
- 凌晨 2:00 仍属于10月1日营业日(因为尚未到 05:00 结束时间)
- 但”晚上 10 点”(22:00)属于10月2日营业日(21:00 之后即进入新营业日)
所以”当天晚上 10 点” → 实际指向下一个营业日的时段。
通用设计方案
处理跨日业务的核心是引入”营业日偏移“概念:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| function getBusinessDay(date: Date, openHour: number, closeHour: number): string { const hour = date.getHours(); if (hour < closeHour) { const prevDay = new Date(date); prevDay.setDate(prevDay.getDate() - 1); return formatDate(prevDay); } return formatDate(date); }
function getBusinessDayRange(businessDay: string, openHour: number, closeHour: number) { const start = new Date(`${businessDay}T${String(openHour).padStart(2, '0')}:00:00`); const end = new Date(start); end.setDate(end.getDate() + 1); end.setHours(closeHour, 0, 0, 0); return { start, end }; }
const date = new Date('2024-10-02T02:00:00'); console.log(getBusinessDay(date, 21, 5)); console.log(getBusinessDayRange('2024-10-01', 21, 5));
|
预定场景的完整逻辑
回到酒吧预定问题,完整的处理流程如下:
- 判断当前营业日:凌晨 2:00 → 属于 10月1日营业日
- **解析”晚上10点”**:22:00 已超过 21:00 → 属于 10月2日营业日
- 存储预定:将记录保存到 10月2日营业日的桌台状态中
- 前端展示:用户选择 10月2日时,显示 22:00 的预定信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| function handleReservation( reserveTime: Date, targetTimeStr: string ) { const currentBizDay = getBusinessDay(reserveTime, 21, 5); const targetHour = parseInt(targetTimeStr.split(':')[0]); let targetBizDay: string; if (targetHour >= 21) { targetBizDay = getNextBusinessDay(currentBizDay); } else { targetBizDay = currentBizDay; } saveReservation(targetBizDay, targetTimeStr, reserveTime); return { message: `预定成功,请在 ${targetBizDay} 日查看您在 ${targetTimeStr} 的预定`, displayDay: targetBizDay }; }
|
常见坑点
1. 数据库字段设计
永远不要只用自然日期当唯一标识,应该同时存储:
| 字段 | 示例 | 说明 |
|---|
business_day | 2024-10-01 | 营业日(业务维度) |
created_at | 2024-10-02 02:00:00 | 实际创建时间(自然时间) |
reserve_at | 2024-10-02 22:00:00 | 预定目标时间 |
2. 时区问题
如果系统涉及时区,跨日问题会叠加时区偏移,建议统一使用 UTC 存储,展示层再转换。
3. 夏令时
部分国家有夏令时调整,一年中有两天不是 24 小时。如果营业时间接近凌晨 2 点,要注意可能跳过的时段。
4. 数据统计
日报、月报的统计维度应以营业日为准,而非自然日。不做区分会导致账对不上。
总结
跨日处理的核心不是算法复杂,而是定义清晰。关键在于:
- 明确”一天”在业务语境下的起止时间
- 所有时间相关的逻辑统一使用营业日概念
- 数据库同时保留营业日和自然时间两个维度
- 前端展示时做好营业日到自然日期的映射
把这几条理清楚,跨日问题基本就解决了。