Firestore跨天营业时间时间范围查询无结果问题排查
问题:跨天营业时间的Firestore时间范围查询无结果
问题背景
有一个每10分钟运行的Cloud Function,用于计算餐厅门店的订单与客户数据。门店采用跨天营业时间(例如周二6:00至周三3:00),因此需要处理当日营业时间(本地当日6:00到23:59:59.999)和前一日剩余时段(本地当日00:00到03:00,属于前一日的营业时段)。但查询前一日剩余时段时,即使该时段有订单,查询结果始终为空。当前处于多伦多时区(UTC-5/冬令时),Firestore以UTC存储日期,仅在控制台显示本地时区。
现有代码
const getRangeForOneDay = async (forPreviousDay = false) => { const lowerBound = "06:00" const upperBound = "03:00"; const lowerBoundSplit = ["06", "00"]; const startOfToday = new Date(); startOfToday.setHours( forPreviousDay ? 0 : parseInt(lowerBoundSplit[0]), forPreviousDay ? 0 : parseInt(lowerBoundSplit[1]), 0, 0 ); const upperBoundSplit = ["03", "00"]; const endOfToday = new Date(); endOfToday.setHours( forPreviousDay ? parseInt(upperBoundSplit[0]) : 23, forPreviousDay ? parseInt(upperBoundSplit[1]) : 59, forPreviousDay ? 0 : 59, forPreviousDay ? 0 : 999 ); return [startOfToday, endOfToday]; }; const rangeToday = getRangeForOneDay(false); const rangeYesterday = getRangeForOneDay(true); const ranges = [rangeToday, rangeYesterday]; const branchId = "some real id"; for (const range of ranges) { const [startOfToday, endOfToday] = range; const orders = await admin .firestore() .collection("Orders") .where("branch_id", "==", branchId) .where("create_on", ">=", startOfToday) .where("create_on", "<=", endOfToday) .orderBy("create_on", "desc") .get(); console.log(`Orders length = ${orders.size}`); }
当前生成的时间范围
Current Day(Tuesday): From Date = 2022-11-08T06:00:00.000Z To Date = 2022-11-08T23:59:59.999Z Previous Day(Monday remaining hours data): From Date = 2022-11-08T00:00:00.000Z To Date = 2022-11-08T03:00:00.000Z
问题根源
核心问题是Cloud Function运行环境默认使用UTC时区,代码中的日期操作基于UTC时间,而非多伦多本地时间:
- 你需要查询的是多伦多本地时间的「当日00:00-03:00」,对应UTC时间是「当日05:00-08:00」(冬令时UTC-5)。
- 但代码中
setHours(0, 0, 0, 0)是设置UTC时间的0点,对应多伦多本地时间的「前一日19:00」,生成的查询范围是UTC「当日00:00-03:00」,对应多伦多本地「前一日19:00-22:00」,完全不匹配目标时段,因此查不到订单。
修复方案
需要明确基于多伦多时区(America/Toronto)计算本地日期和时段,避免依赖运行环境的UTC时区。以下是修复后的代码:
// 获取多伦多本地时间的日期对象 const getTorontoLocalDate = () => { const now = new Date(); const formatter = new Intl.DateTimeFormat('en-US', { timeZone: 'America/Toronto', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); const parts = formatter.formatToParts(now); const dateParts = parts.reduce((acc, part) => { acc[part.type] = part.value; return acc; }, {}); return new Date(`${dateParts.year}-${dateParts.month}-${dateParts.day}T${dateParts.hour}:${dateParts.minute}:${dateParts.second}`); }; // 生成多伦多本地时间对应的查询范围 const getRangeForOneDay = (forPreviousDay = false) => { const lowerBound = [6, 0]; // 多伦多本地营业时间开始(6:00) const upperBound = [3, 0]; // 多伦多本地营业时间结束(次日3:00) // 获取多伦多本地的当前日期 const torontoNow = getTorontoLocalDate(); // 多伦多本地的当日0点 const torontoStartOfDay = new Date(torontoNow.getFullYear(), torontoNow.getMonth(), torontoNow.getDate()); let start, end; if (!forPreviousDay) { // 多伦多本地当日6:00 到 23:59:59.999 start = new Date(torontoStartOfDay.getTime() + lowerBound[0] * 3600 * 1000 + lowerBound[1] * 60 * 1000); end = new Date(torontoStartOfDay.getTime() + 23 * 3600 * 1000 + 59 * 60 * 1000 + 999); } else { // 多伦多本地当日00:00 到 03:00(属于前一日营业时段) start = torontoStartOfDay; end = new Date(torontoStartOfDay.getTime() + upperBound[0] * 3600 * 1000 + upperBound[1] * 60 * 1000); } return [start, end]; }; // 后续查询逻辑保持不变 const rangeToday = getRangeForOneDay(false); const rangeYesterday = getRangeForOneDay(true); const ranges = [rangeToday, rangeYesterday]; const branchId = "some real id"; for (const range of ranges) { const [startOfToday, endOfToday] = range; const orders = await admin .firestore() .collection("Orders") .where("branch_id", "==", branchId) .where("create_on", ">=", startOfToday) .where("create_on", "<=", endOfToday) .orderBy("create_on", "desc") .get(); console.log(`Orders length = ${orders.size}`); }
修复说明
getTorontoLocalDate函数通过Intl.DateTimeFormat明确指定多伦多时区,获取当前的本地日期和时间,避免受运行环境UTC时区影响。getRangeForOneDay基于多伦多本地的当日0点,计算目标时段的起始和结束时间,生成的Date对象会自动转换为正确的UTC时间,与Firestore存储的订单时间匹配。
内容的提问来源于stack exchange,提问作者Omar Dulaimi
相关产品推荐
相关产品推荐

