$dateSubtract
聚合运算符将Date()
对象按指定的时间单位递减。从版本5.0开始支持。
{
$dateSubtract: {
startDate: <Expression>,
unit: <Expression>,
amount: <Expression>,
timezone: <tzExpression>
}
}
返回一个日期对象Date()
,startDate
可以是任何能被解析为日期、时间戳或对象Id的表达式,这三种类型都会返回Date()
对象。
参数字段说明:
|字段|是否必须|描述|
|-|-|
|startDate
|是|开始日期(UTC),可以是日期、时间戳或对象Id表达式|
|unit
|是|要增加的时间的单位,单位可以是能被解析为下列值的表达式:year
、quarter
、week
、month
、day
、hour
、minute
、second
、millisecond
|
|amount
|是|以units
为单位,在startDate
基础上的增量,amount
是可以被解析为整数、小数或双精度数的表达式|
|timezone
|否|执行操作的时区,
必须是能被解析为奥尔森时区标识符格式的字符串或UTC偏移量,如果timezone
不指定,返回值显示为UTC
|
MongoDB遵循流行的数据库用法,以UTC为时间单位工作。dateSubtract
表达式总是以 UTC 为起始日期,并以 UTC 为结果返回。如果指定了时区,计算将使用指定的时区进行。当计算涉及夏令时(DST)时,时区尤为重要。
如果单位是一个月或更大,操作会根据该月的最后一天进行调整。例如,在 10 月的最后一天增加一个月,这就是 "月末最后一天 "调整。
{
$dateSubtract:
{
startDate: ISODate("2021-03-31T12:10:05Z"),
unit: "month",
amount: 1
}
}
注意:返回的日期ISODate("2020-11-30T12:10:05Z")
是30日,而不是 31日,因为11月的天数比10月少。
在
字段中使用 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
操作符返回日期字段的相应部分:
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
来递减注销时间。
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
日期对象中提取年份和月份;然后检查月份和年份是否与选择目标相匹配;由于"January"被编码为"1",因此当年和月等于($eq
)"2021"和 "1"时,$expr
为真。
$project
阶段使用$dateSubtract
从每个选定文档的logoutTime
中减去3个小时。
最后,$merge
阶段会更新集合,为修改后的文档写入新的logoutTime
。
**注意:**与$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
过滤集合,以匹配注销时间在过去一周内的文档。
$project
阶段使用$dateToString
表达式将日期转换为更易读的格式,如果不进行转换,MongoDB将以ISODate格式返回日期,输出结果显示上周有两名客户注销。
{ "custId" : 459, "loggedOut" : "2021-02-17" }
{ "custId" : 460, "loggedOut" : "2021-02-18" }
所有日期在内部都以UTC时间存储。如果指定了时区,$dateSubtract
将使用当地时间进行计算。计算结果以UTC
显示。
本例假如客户分布在多个时区,按天或按小时计费,现在要了解夏令时对计费期的影响。
创建billing
集合:
db.billing.insertMany(
[
{
location: "America/New_York",
login: ISODate("2021-03-13T10:00:00-0500"),
logout: ISODate("2021-03-14T18:00:00-0500")
},
{
location: "America/Mexico_City",
login: ISODate("2021-03-13T10:00:00-00:00"),
logout: ISODate("2021-03-14T08: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
表达式对输出进行了重新格式化,以提高可读性。结果汇总如下:
|字段|纽约|墨西哥城|
|-|-|
|start|2021-03-14 15:00|2021-03-14 15:00|
|Start, TZ Info|2021-03-14 11:00|2021-03-14 04:00|
|1 Day|2021-03-13 16:00|2021-03-13 15:00|
|1 Day, TZ Info|2021-03-13 11:00|2021-03-13 09:00|
|24 Hours|2021-03-13 15:00|2021-03-13 15:00|
|24 Hours, TZ Info|2021-03-13 10:00|2021-03-13 09:00|
上表强调了几点:
未格式化的日期以 UTC 返回。纽约的$login
是 UTC-5,但开始、天数和小时行显示的时间是 UTC 时间。
3月14日是纽约的夏令时开始日,但不是墨西哥的夏令时开始日。当某地切换到DST并从某一天跨入下一天时,计算的时间会进行调整。
夏令时改变的是一天的长度,而不是小时。夏令时不会改变小时数。只有当测量单位为日或更大且计算跨越指定时区的时钟变化时,才会对 DST 进行调整。