如何用Day.js处理时区偏移,实现营业时间转换与判断?
问题描述
我在为地点开发营业时段功能,将营业时间以JSON格式存储在PostgreSQL表中,示例如下:
{ "dayOfTheWeek": "FRIDAY", "isOpen": true, "hours": [ { "open": "08:00", "closed": "12:00" }, { "open": "13:00", "closed": "22:00" } ] }
该地点使用中部时间(America/Chicago),时区信息单独存储在另一列:
"timezone": "America/Chicago"
目前遇到两个核心问题:
- 显示营业时间时,总是比实际快5小时(比如存储的
08:00显示成13:00),尝试使用dayjs的timezone和utc offset插件无效,手动减5小时能临时解决,但想通过时区字符串自动转换偏移量。 - 需要判断给定的UTC时间(比如
2023-04-14T09:00:00Z)是否处于该地点的营业时段内,当前代码因时区不匹配总是判断错误(比如9点UTC对应芝加哥时间4点,代码却和13点UTC比较,导致进入BEFORE分支)。
之前的代码片段:
// dateTimeStart = 2023-04-14T09:00:00 const selectedDay = dayjs(dateTimeStart).format('dddd').toUpperCase(); // FRIDAY for (let day of location.businessHours) { if (selectedDay === day.dayOfTheWeek) { if (day.isOpen) { let blockStart = dayjs(block.open, 'HH:mm'); // 2023-04-14T13:00:00.000Z let blockStop = dayjs(block.closed, 'HH:mm'); // 2023-04-14T17:00:00.000Z // 比较逻辑 } } }
解决方案
1. 修复dayjs时区插件失效问题
dayjs的timezone插件依赖utc插件,必须按顺序导入并启用,否则tz方法不会生效:
// 先安装依赖:npm install dayjs dayjs-plugin-timezone import dayjs from 'dayjs' import utc from 'dayjs/plugin/utc' import timezone from 'dayjs/plugin/timezone' // 先启用utc插件,再启用timezone插件 dayjs.extend(utc) dayjs.extend(timezone)
2. 正确转换并显示营业时间
存储的08:00是芝加哥本地时间,要正确显示该时区的时间,需把时分绑定到对应日期的芝加哥时区对象上:
// 以请求日期为例,创建芝加哥时区的08:00时间 const chicagoOpenTime = dayjs('2023-04-14').tz('America/Chicago').hour(8).minute(0); console.log(chicagoOpenTime.format('HH:mm')); // 输出 08:00(芝加哥本地时间) console.log(chicagoOpenTime.utc().format('HH:mm')); // 输出 13:00(对应UTC时间)
如果需要将时区字符串(如America/Chicago)转为小时偏移量,可以用utcOffset()方法:
// 偏移量单位为分钟,除以60转为小时数 const offsetHours = dayjs().tz('America/Chicago').utcOffset() / 60; console.log(offsetHours); // 夏令时返回-4,非夏令时返回-5
3. 正确判断请求时间是否在营业时段内
核心原则是统一时间基准:要么把请求的UTC时间转为地点本地时间,要么把营业时段的本地时间转为UTC时间,再进行比较。
方案一:将请求时间转为地点本地时间比较
// 传入的UTC请求时间 const requestedUtcTime = dayjs('2023-04-14T09:00:00Z'); // 转为地点时区的本地时间 const localRequestedTime = requestedUtcTime.tz(location.timezone); // 获取本地时间对应的星期(大写) const localDay = localRequestedTime.format('dddd').toUpperCase(); for (let day of location.businessHours) { if (localDay === day.dayOfTheWeek && day.isOpen) { for (let block of day.hours) { // 解析营业时段的时分 const [openHour, openMin] = block.open.split(':').map(Number); const [closeHour, closeMin] = block.closed.split(':').map(Number); // 创建本地时间对应的营业开始/结束时间(绑定到请求日期) const blockLocalStart = localRequestedTime.hour(openHour).minute(openMin).second(0); const blockLocalEnd = localRequestedTime.hour(closeHour).minute(closeMin).second(0); // 判断请求时间是否在营业区间内([]表示包含开始和结束时间) if (localRequestedTime.isBetween(blockLocalStart, blockLocalEnd, null, '[]')) { console.log('处于营业时段内'); } else { console.log('不处于营业时段内'); } } } }
方案二:将营业时段转为UTC时间比较
const requestedUtcTime = dayjs('2023-04-14T09:00:00Z'); const localRequestedTime = requestedUtcTime.tz(location.timezone); const localDay = localRequestedTime.format('dddd').toUpperCase(); for (let day of location.businessHours) { if (localDay === day.dayOfTheWeek && day.isOpen) { for (let block of day.hours) { const [openHour, openMin] = block.open.split(':').map(Number); const [closeHour, closeMin] = block.closed.split(':').map(Number); // 创建本地营业时间,再转为UTC const blockUtcStart = localRequestedTime.hour(openHour).minute(openMin).second(0).utc(); const blockUtcEnd = localRequestedTime.hour(closeHour).minute(closeMin).second(0).utc(); // 和UTC请求时间直接比较 if (requestedUtcTime.isBetween(blockUtcStart, blockUtcEnd, null, '[]')) { console.log('处于营业时段内'); } else { console.log('不处于营业时段内'); } } } }
关键注意事项
- 必须确保
utc和timezone插件按顺序加载,否则时区转换会失效 - 营业时段的时分需要绑定到请求时间对应的日期,避免跨天场景(比如营业到次日凌晨)
- 夏令时会自动处理:dayjs的timezone插件会根据日期自动应用对应时区的夏令时偏移量,无需手动调整
内容的提问来源于stack exchange,提问作者Damon
相关产品推荐
相关产品推荐

