【问题标题】:Aggregating in local timezone in mongodb在 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 上进行分组查询时是否可以传递一些参数,如日期区域?

标签: node.js mongodb datetime mongodb-query aggregation-framework


【解决方案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 中存储数据库内容“很有意义”,因为它是一个稳定的点,您可以从中进行所有调整。这就是这里的教训。
    猜你喜欢
    • 1970-01-01
    • 2016-12-22
    • 2019-06-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-03
    • 1970-01-01
    • 2012-05-10
    相关资源
    最近更新 更多