$dateSubtract(聚合)
定义
$dateSubtract
版本 5.0 中的新增功能。
将
Date()
对象递减指定数量的时间单位。$dateSubtract
表达式的语法如下:{ $dateSubtract: { startDate: <Expression>, unit: <Expression>, amount: <Expression>, timezone: <tzExpression> } } 返回
Date()
。startDate
可以是解析为 Date、Timestamp 或 ObjectId 类型的任何表达式。无论使用哪种数据类型作为输入,返回的值都将是Date()
对象。字段必需/可选说明startDate
必需unit
必需unit
用来衡量从startDate
中减去的amount
时间。unit
是一个表达式,可解析为以下字符串之一:year
quarter
week
month
day
hour
minute
second
millisecond
amount
必需timezone
Optional执行操作的时区。
<tzExpression>
必须是一个有效表达式,解析为格式为 Olson 时区标识符或 UTC 偏移的字符串。如果没有提供timezone
,则结果显示在UTC
中。format示例Olson 时区标识符"America/New_York" "Europe/London" "GMT" UTC 偏移+/-[hh]:[mm], e.g. "+04:45" +/-[hh][mm], e.g. "-0530" +/-[hh], e.g. "+03"
行为
时间测量
MongoDB 遵循流行的数据库使用情况并使用 UTC 时间。dateSubtract
表达式始终采用 UTC 格式的startDate
并返回 UTC 格式的结果。如果指定了 timezone
,则将使用指定的 timezone
完成计算。当计算涉及夏令时 (DST) 时,时区尤其重要。
如果 unit
为 month
或更大,则操作将根据该月的最后一天进行调整。例如,在三月的最后一天减去一个 month
,即可演示“该月最后一天”调整。
{ $dateSubtract: { startDate: ISODate("2021-03-31T12:10:05Z"), unit: "month", amount: 1 } }
请注意,返回的日期 ISODate("2021-02-28T12:10:05Z")
是 28 日而不是 31 日,因为二月的天数比三月的天数少。
时区
在 <timezone>
字段中使用 Olson 时区标识符时,如果适用于指定的时区,MongoDB 会应用 DST 偏移量。
例如,考虑包含以下文档的 sales
集合:
{ "_id" : 1, "item" : "abc", "price" : 20, "quantity" : 5, "date" : ISODate("2017-05-20T10:24:51.303Z") }
以下聚合说明了 MongoDB 如何处理 Olson 时区标识符的 DST 偏移量。该示例使用 $hour
和 $minute
操作符返回 date
字段的相应部分:
db.sales.aggregate([ { $project: { "nycHour": { $hour: { date: "$date", timezone: "-05:00" } }, "nycMinute": { $minute: { date: "$date", timezone: "-05:00" } }, "gmtHour": { $hour: { date: "$date", timezone: "GMT" } }, "gmtMinute": { $minute: { date: "$date", timezone: "GMT" } }, "nycOlsonHour": { $hour: { date: "$date", timezone: "America/New_York" } }, "nycOlsonMinute": { $minute: { date: "$date", timezone: "America/New_York" } } } }])
操作返回以下结果:
{ "_id": 1, "nycHour" : 5, "nycMinute" : 24, "gmtHour" : 10, "gmtMinute" : 24, "nycOlsonHour" : 6, "nycOlsonMinute" : 24 }
示例
减去固定数量
考虑系统连接时间的集合,如下所示:
db.connectionTime.insertMany( [ { custId: 457, login: ISODate("2020-12-25T19:04:00"), logout: ISODate("2020-12-28T09:04:00") }, { custId: 457, login: ISODate("2021-01-27T05:12:00"), logout: ISODate("2021-01-28T13:05:00") }, { custId: 458, login: ISODate("2021-01-22T06:27:00"), logout: ISODate("2021-01-31T11:00:00") }, { custId: 459, login: ISODate("2021-02-14T20:14:00"), logout: ISODate("2021-02-17T16:05:00") }, { custId: 460, login: ISODate("2021-02-26T02:44:00"), logout: ISODate("2021-02-18T14:13:00") } ] )
由于服务问题,您需要从 2021 年 1 月的每次注销时间中减去 3 小时。您可以在聚合管道中使用 $dateSubtract
来递减 logoutTime
。
db.connectionTime.aggregate( [ { $match: { $expr: { $eq: [ { $year: "$logout" }, 2021 ] }, $expr: { $eq: [ { $month: "$logout" }, 1 ] } } }, { $project: { logoutTime: { $dateSubtract: { startDate: "$logout", unit: "hour", amount: 3 } } } }, { $merge: "connectionTime" } ] )
在 $match
阶段也进行了两次类似的比较。首先,$year
和 $month
操作符分别从 logoutTime
Date 对象中提取年份和月份。然后检查月份和年份,看看它们是否与选择目标匹配。由于 "January" 被编码为 "1",因此当年份和月份 $expr
($eq
) 等于 "2021" 和 "1" 时,为 true。
$project
阶段使用 $dateSubtract
从每份选定文档的 logoutTime
中减去 3 小时。
最后,$merge
阶段更新集合,为修改后的文档编写新的 logoutTime
。
注意
与 $out
不同,$merge
阶段仅更新匹配的文档,而保留集合的其余部分。有关更多详细信息,请参见:$out 与 $merge 的比较。
生成的文档如下所示:
{ "_id" : ObjectId("603dd94b044b995ad331c0b5"), "custId" : 457, "login" : ISODate("2020-12-25T19:04:00Z"), "logout" : ISODate("2020-12-28T09:04:00Z") } { "_id" : ObjectId("603dd94b044b995ad331c0b6"), "custId" : 457, "login" : ISODate("2021-01-27T05:12:00Z"), "logout" : ISODate("2021-01-28T13:05:00Z"), "logoutTime" : ISODate("2021-01-28T10:05:00Z") } { "_id" : ObjectId("603dd94b044b995ad331c0b7"), "custId" : 458, "login" : ISODate("2021-01-22T06:27:00Z"), "logout" : ISODate("2021-01-31T11:00:00Z"), "logoutTime" : ISODate("2021-01-31T08:00:00Z") } { "_id" : ObjectId("603dd94b044b995ad331c0b8"), "custId" : 459, "login" : ISODate("2021-02-14T20:14:00Z"), "logout" : ISODate("2021-02-17T16:05:00Z") } { "_id" : ObjectId("603dd94b044b995ad331c0b9"), "custId" : 460, "login" : ISODate("2021-02-26T02:44:00Z"), "logout" : ISODate("2021-02-18T14:13:00Z") }
按相对日期进行筛选
您希望向过去一周使用过您的服务的客户发送调查问卷。$dateSubtract
表达式可以创建相对于查询执行时间的范围筛选器。
db.connectionTime.aggregate( [ { $match: { $expr: { $gt: [ "$logoutTime", { $dateSubtract: { startDate: "$$NOW", unit: "week", amount: 1 } } ] } } }, { $project: { _id: 0, custId: 1, loggedOut: { $dateToString: { format: "%Y-%m-%d", date: "$logoutTime" } } } } ] )
内置聚合变量 $$NOW
返回 ISODate 格式的当前日期时间。$match
阶段使用 $$NOW
的值来获取今天的日期。然后比较表达式 ($expr
) 使用大于 ($gt
) 和 $dateSubtract
的值筛选集合,以匹配过去一周包含 logoutTime
的文档。
$project
阶段使用$dateToString
表达式将日期转换为更具可读性的格式。 如果不进行转换,MongoDB 会以ISODate格式返回日期,并采用 UTC 时区。
输出显示上周有两位客户登出。
{ "custId" : 459, "loggedOut" : "2021-02-17" } { "custId" : 460, "loggedOut" : "2021-02-18" }
调整夏令时
所有日期均以 UTC 时间存储在内部。指定 timezone
时,$dateSubtract
使用当地时间进行计算。结果以 UTC 时间显示。
您的客户分布在多个时区,您想了解如果您在 day
或 hour
之前计费,夏令时可能会对计费周期产生什么影响。
创建此连接时间的集合:
db.billing.insertMany( [ { location: "America/New_York", login: ISODate("2021-03-14T10:00:00-0500"), logout: ISODate("2021-03-14T18:00:00-0500") }, { location: "America/Mexico_City", login: ISODate("2021-03-14T10:00:00-00:00"), logout: ISODate("2021-03-15T08:00:00-0500") } ] )
首先从每个文档中的 login
日期减去 1 天,然后减去 24 小时。
db.billing.aggregate( [ { $project: { _id: 0, location: 1, start: { $dateToString: { format: "%Y-%m-%d %H:%M", date: "$login" } }, days: { $dateToString: { format: "%Y-%m-%d %H:%M", date: { $dateSubtract: { startDate: "$login", unit: "day", amount: 1, timezone: "$location" } } } }, hours: { $dateToString: { format: "%Y-%m-%d %H:%M", date: { $dateSubtract: { startDate: "$login", unit: "hour", amount: 24, timezone: "$location" } } } }, startTZInfo: { $dateToString: { format: "%Y-%m-%d %H:%M", date: "$login", timezone: "$location" } }, daysTZInfo: { $dateToString: { format: "%Y-%m-%d %H:%M", date: { $dateSubtract: { startDate: "$login", unit: "day", amount: 1, timezone: "$location" } }, timezone: "$location" } }, hoursTZInfo: { $dateToString: { format: "%Y-%m-%d %H:%M", date: { $dateSubtract: { startDate: "$login", unit: "hour", amount: 24, timezone: "$location" } }, timezone: "$location" } }, } } ] ).pretty()
$dateToString
表达式将输出重新格式化,以提高可读性。现将结果汇总如下:
字段 | New York | 墨西哥城 |
---|---|---|
开始 | 2021-03-14 15:00 | 2021-03-14 15:00 |
开始,TZ Info | 2021-03-14 11:00 | 2021-03-14 04:00 |
1 天 | 2021-03-13 16:00 | 2021-03-13 15:00 |
1 天,TZinfo | 2021-03-13 11:00 | 2021-03-13 09:00 |
24 小时 | 2021-03-13 15:00 | 2021-03-13 15:00 |
24 小时,TZInfo | 2021-03-13 10:00 | 2021-03-13 09:00 |
该图表突出显示以下几点:
未格式化的日期以 UTC 格式返回。纽约的
$login
为 UTC -5,但start
、days
和hours
行显示 UTC 时间。3 月 14 日是纽约而不是墨西哥的夏令时开始时间。当一个位置切换到夏令时并从一个
day
跨越到下一个时,计算出的时间会进行调整。DST 修改
day
的长度,而不是hour
的长度。hours
的 DST 没有变化。仅当测量值unit
不低于day
且计算跨越指定的timezone
中的时钟变化时,才会对 DST 进行调整。