在 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 中根据本地时区插入/检索日期

Mongodb $ dayOfMonth with timezone

mongodb - 没有本地的聚合查找

关于MongoDB时区问题

在 MongoDB 中使用某些时区时出错

nodejs,mongodb不同时区问题