基于当前时间筛选活跃班次的逻辑问题及跨时区适配需求
问题描述
我有如下用户对象:
const user = { id: 3, name: "Clark Kent", mobileNumber: "1234567892", email: "example_user@gmail.com", role: "VAN_SALE", loc_update_permission: true, active_shifts: [ { id: 1, name: "Morning", startTime: "09:30:00", endTime: "05:30:00", coordinates: null, }, { id: 2, name: "Night", startTime: "06:30:00", endTime: "02:30:00", coordinates: null, }, ], };
需求是基于当前时间返回活跃班次:当时间处于或等于09:30:00至05:30:00区间时返回Morning班次,其他时间返回Night班次(数据库中以time类型存储班次起止时间)。
我写了对应的JavaScript函数,但测试时添加currentTime.setHours(currentTime.getHours() + 10)调整时间后,本该返回Night班次却仍返回Morning。另外,这个逻辑还需要适配IST、AST等不同时区,确保不同时区用户在当地时间9:00 AM时都能显示Morning班次。
问题分析
- 班次匹配逻辑bug:两个班次的时间范围存在重叠(比如18:00同时属于Morning和Night的区间),而代码按顺序遍历班次,先匹配到的Morning会直接返回,导致Night班次永远无法被触发。
- 时区适配缺失:当前代码使用运行环境的本地时区计算时间,无法根据用户所在时区转换当地时间,导致不同时区用户的判断结果错误。
解决方案
1. 修正班次匹配逻辑
首先要确保班次时间范围无重叠,比如把Night班次的时间调整为startTime: "05:30:00"、endTime: "09:30:00",这样两个班次刚好覆盖一整天且无交叉。同时修正判断逻辑,把原有的< endHours改为<= endHours,满足“等于区间边界也匹配”的需求。
修正后的函数:
function getCurrentActiveShift(user, userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone) { // 将当前时间转换为用户时区的本地时间,再计算总分钟数 const now = new Date(); const userLocalTime = new Date(now.toLocaleString("en-US", { timeZone: userTimeZone })); const currentTotalMinutes = userLocalTime.getHours() * 60 + userLocalTime.getMinutes(); for (const shift of user.active_shifts) { // 解析班次起止时间为总分钟数 const [startH, startM] = shift.startTime.split(":").map(Number); const shiftStartMinutes = startH * 60 + startM; const [endH, endM] = shift.endTime.split(":").map(Number); const shiftEndMinutes = endH * 60 + endM; let isActive = false; if (shiftEndMinutes < shiftStartMinutes) { // 处理跨午夜的班次:当前时间 >= 开始时间 或 <= 结束时间 isActive = currentTotalMinutes >= shiftStartMinutes || currentTotalMinutes <= shiftEndMinutes; } else { // 处理不跨午夜的班次:当前时间在起止时间之间(含边界) isActive = currentTotalMinutes >= shiftStartMinutes && currentTotalMinutes <= shiftEndMinutes; } if (isActive) { return shift; } } return null; }
2. 适配多时区
要支持不同时区,需要:
- 给每个用户对象添加时区字段(比如
timeZone: "Asia/Kolkata"对应IST,America/Puerto_Rico对应AST) - 调用函数时传入用户的时区,确保计算的是用户当地时间
修改后的用户对象示例:
const user = { id: 3, name: "Clark Kent", mobileNumber: "1234567892", email: "example_user@gmail.com", role: "VAN_SALE", loc_update_permission: true, timeZone: "Asia/Kolkata", // 存储用户所在时区 active_shifts: [ { id: 1, name: "Morning", startTime: "09:30:00", endTime: "05:30:00", coordinates: null, }, { id: 2, name: "Night", startTime: "05:30:00", endTime: "09:30:00", coordinates: null, }, ], };
调用函数:
const currentShift = getCurrentActiveShift(user, user.timeZone); if (currentShift) { console.log(`当前活跃班次:${currentShift.name}`); } else { console.log("当前无活跃班次"); }
3. 数据库存储优化建议
当前用time类型存储跨午夜的班次时间,无法明确结束时间是当天还是次日,建议调整存储方式:
- 方案1:使用数据库支持的时间区间类型(比如PostgreSQL的
tstzrange),同时关联用户时区,直接存储带时区的时间范围。 - 方案2:将起止时间存储为当日总分钟数(0-1439),比如
start_minute: 570(对应09:30)、end_minute: 330(对应05:30),减少前端解析成本。 - 方案3:如果班次是固定每日循环,保留
time类型存储,但必须确保班次之间无重叠,同时在用户表中新增time_zone字段存储用户时区。
内容的提问来源于stack exchange,提问作者Thorin
相关产品推荐
相关产品推荐

