MongoDB聚合计算日期差时扣除周日时长的实现方法
解决MongoDB聚合中扣除周日时长的问题
你可以通过计算区间内的周日总数,再从总时长中扣除对应小时数的方式实现需求。以下是两种可行的聚合管道写法:
简洁版(推荐新手使用)
这个版本将所有计算整合在一个$project阶段,逻辑清晰且避免重复代码:
db.collections.aggregate([ { $project: { waitTime: { $dateDiff: { startDate: ISODate("2023-08-10T10:24:51.303Z"), endDate: ISODate("2023-08-30T10:24:51.303Z"), unit: "hour" } }, exceptSundays: { $let: { vars: { start: ISODate("2023-08-10T10:24:51.303Z"), end: ISODate("2023-08-30T10:24:51.303Z"), dayMs: 1000 * 60 * 60 * 24 // 一天的毫秒数 }, in: { $subtract: [ // 总时长(小时) { $dateDiff: { startDate: "$$start", endDate: "$$end", unit: "hour" } }, { $multiply: [ // 计算区间内的周日数量 { $subtract: [ // 结束日期对应的完整周数(从纪元开始,每周一个周日) { $floor: { $divide: [{ $toLong: "$$end" }, "$$dayMs" * 7] } }, // 开始日期前一天对应的完整周数 { $floor: { $divide: [{ $toLong: { $subtract: ["$$start", "$$dayMs"] } }, "$$dayMs" * 7] } } ] }, 24 // 每个周日扣除24小时 ] } ] } } } } } ])
分步拆解版(适合理解逻辑)
如果需要逐步理解每个计算环节,可以用多个$project阶段拆分:
db.collections.aggregate([ // 第一步:计算基础数据 { $project: { waitTime: { $dateDiff: { startDate: ISODate("2023-08-10T10:24:51.303Z"), endDate: ISODate("2023-08-30T10:24:51.303Z"), unit: "hour" } }, // 转换日期为毫秒数,方便后续计算 startMs: { $toLong: ISODate("2023-08-10T10:24:51.303Z") }, endMs: { $toLong: ISODate("2023-08-30T10:24:51.303Z") } } }, // 第二步:计算周日总数 { $project: { waitTime: 1, totalSundays: { $subtract: [ { $floor: { $divide: ["$endMs", 1000*60*60*24*7] } }, { $floor: { $divide: [{ $subtract: ["$startMs", 1000*60*60*24] }, 1000*60*60*24*7] } } ] } } }, // 第三步:计算扣除周日后的时长 { $project: { waitTime: 1, exceptSundays: { $subtract: ["$waitTime", { $multiply: ["$totalSundays", 24] }] } } } ])
核心逻辑说明
- 周日数量计算:
- 从Unix纪元(1970-01-01)到目标日期的毫秒数,除以一周的毫秒数(
7*24*60*60*1000)取整,得到该日期前的完整周数(每一周对应一个周日)。 - 用结束日期的完整周数减去开始日期前一天的完整周数,即可得到区间内包含的周日总数。
- 从Unix纪元(1970-01-01)到目标日期的毫秒数,除以一周的毫秒数(
- 时长扣除:每个周日对应24小时,用总时长减去
周日数*24即可得到目标结果。
适配特殊场景
如果你的日期区间包含部分周日(比如开始于周日下午,或结束于周日上午),且需要只扣除实际占用的周日时长,可以在计算周日数后,额外判断首尾日期是否为周日,再调整扣除的小时数。但根据你的示例需求,当前写法已完全适用。
内容的提问来源于stack exchange,提问作者Sachin ganeshkar
相关产品推荐
相关产品推荐

