在 mongodb 本地时区聚合
Posted
技术标签:
【中文标题】在 mongodb 本地时区聚合【英文标题】:Aggregating in local timezone in mongodb 【发布时间】:2015-09-29 23:14:42 【问题描述】:我正在构建将在意大利使用的 mongodb 和 nodejs 应用程序。意大利时区是 +02:00 。这意味着如果有人在 7 月 11 日凌晨 1 点保存一些数据,那么它将保存为 7 月 10 日晚上 11:00,因为 mongo 以 UTC 保存日期。 我们需要显示日期明智的 tx 计数。所以我按日期查询分组。但它显示前一天的交易。应该有什么解决方法。
> db.txs.insert(txid:"1",date : new Date("2015-07-11T01:00:00+02:00"))
> db.txs.insert(txid:"2",date : new Date("2015-07-11T05:00:00+02:00"))
> db.txs.insert(txid:"3",date : new Date("2015-07-10T21:00:00+02:00"))
> db.txs.find().pretty()
"_id" : ObjectId("55a0a55499c6740f3dfe14e4"),
"txid" : "1",
"date" : ISODate("2015-07-10T23:00:00Z")
"_id" : ObjectId("55a0a55599c6740f3dfe14e5"),
"txid" : "2",
"date" : ISODate("2015-07-11T03:00:00Z")
"_id" : ObjectId("55a0a55699c6740f3dfe14e6"),
"txid" : "3",
"date" : ISODate("2015-07-10T19:00:00Z")
> db.txs.aggregate([
$group:
_id:
day:$dayOfMonth:"$date",
month:$month:"$date",
year:$year:"$date"
,
count:$sum:1
])
"_id" : "day" : 11, "month" : 7, "year" : 2015 , "count" : 1
"_id" : "day" : 10, "month" : 7, "year" : 2015 , "count" : 2
它在 7 月 10 日显示 2 笔交易,在 7 月 11 日显示 1 笔交易。但我们需要显示 7 月 11 日的 2 笔交易和 7 月 10 日的 1 笔交易。
当时是意大利的 7 月 11 日
db.txs.insert(txid:"1",date : new Date("2015-07-11T01:00:00+02:00"))
发生但 mongo 将日期存储为:
ISODate("2015-07-10T23:00:00Z")
【问题讨论】:
如果“新西兰”的人想查看“意大利”的人提交的数据怎么办?那你应该如何存储时间呢?这就是为什么使用 UTC 日期的原因,因为它们对每个人都代表相同的时间点。在您的“客户端”上转换为本地时间,并对“查询参数”执行相同的操作,获取本地日期,然后将它们转换回 UTC。这样查询和数据在全球范围内都是一致的。 @BlakesSeven 但很可能是该组通过显示上一个日期的记录。在 mongo 上进行分组查询时是否可以传递一些参数,如日期区域? 【参考方案1】:在 mongo 3.6 版本中添加了时区,mongo doc
用时区提取日期部分的表达式是
date: <dateExpression>, timezone: <tzExpression>
我们可以在获取日期部分时指定时区或偏移量
管道
> db.txs.aggregate([
... $group:
... _id:
... day: $dayOfMonth: date :"$date", timezone : "Europe/Rome", // timezone
... month: $month: date : "$date", timezone : "+02:00", //offset
... year: $year: date : "$date", timezone : "+02:00" //offset
... ,
... count:$sum:1
...
... ])
结果
"_id" : "day" : 10, "month" : 7, "year" : 2015 , "count" : 1
"_id" : "day" : 11, "month" : 7, "year" : 2015 , "count" : 2
>
timezone列表
【讨论】:
【参考方案2】:处理时区是一个“客户端”问题,因此您应该通过时区偏移量修改“查询”时间,以便允许在 UI 中选择“本地”时间等等。以当地时间表示日期的 UI 显示也是如此。
这同样适用于您的 arggregation 原则。只需通过时区偏移量进行调整。应用日期数学而不是使用日期聚合运算符:
var tzOffset = 2;
db.txs.aggregate([
"$group":
"_id":
"$subtract": [
"$add": [
"$subtract": [ "$date", new Date("1970-01-01") ] ,
tzOffset * 1000 * 60 * 60
],
"$mod": [
"$add": [
"$subtract": [ "$date", new Date("1970-01-01") ] ,
tzOffset * 1000 * 60 * 60
],
1000 * 60 * 60 * 24
]
]
,
"count": "$sum": 1
]).forEach(function(doc)
printjson( "_id": new Date(doc._id), "count": doc.count )
);
这给了你:
"_id" : ISODate("2015-07-10T00:00:00Z"), "count" : 1
"_id" : ISODate("2015-07-11T00:00:00Z"), "count" : 2
因此,当您$subtract
一个 BSON 日期与另一个 BSON 日期时,结果是自 unix 纪元以来的毫秒数。然后只需通过“添加”“时区偏移”来再次调整它,无论是正向时间还是负向时间,再次从时间值转换为有效毫秒。
然后四舍五入是一个简单的模 $mod
以从“一天中的毫秒数”中获取余数,然后将其删除以仅将调整后的日期四舍五入到当天。
这里生成的数值很容易重新转换为日期,因为所有语言库“日期”对象都将 epoch 中的毫秒(或秒)作为构造函数参数。
同样,这完全是关于修改数据响应以从“客户端”的“语言环境”呈现,而不是改变数据的存储方式。如果您想在您的应用程序中获得真正的位置,那么您可以在任何地方对时区偏移进行修改,就像上面介绍的那样。
--
实际上,您可以在聚合框架本身中创建日期,并使用更多的日期数学。只需将纪元日期添加回转换后的日期即可:
db.txs.aggregate([
"$group":
"_id":
"$add": [
"$subtract": [
"$add": [
"$subtract": [ "$date", new Date(0) ] ,
tzOffset * 1000 * 60 * 60
],
"$mod": [
"$add": [
"$subtract": [ "$date", new Date(0) ] ,
tzOffset * 1000 * 60 * 60
],
1000 * 60 * 60 * 24
]
],
new Date(0);
]
,
"count": "$sum": 1
])
【讨论】:
@OmervanKloeten 叹息!您需要更加努力地考虑这一点,我并不是说要“打折 DST”,而是如果这是对预期输出日期范围的考虑,那么您可以通过分解来自客户端区域设置数据的调整来“处理它” (即这些日期直到 DST 休息和这些日期之后)进行相应的调整。在 GMT 中存储数据库内容“很有意义”,因为它是一个稳定的点,您可以从中进行所有调整。这就是这里的教训。以上是关于在 mongodb 本地时区聚合的主要内容,如果未能解决你的问题,请参考以下文章
使用 Java 在 MongoDB 中根据本地时区插入/检索日期