在mongodb中按本地时区进行聚合

10
我正在使用mongodb和nodejs构建一个应用程序,将在意大利使用。意大利时区为+02:00。这意味着,如果有人在7月11日的01:00保存了一些数据,那么由于mongo将日期保存为UTC,它将显示为10月7日的11:00 pm。 我们需要按日期显示tx计数。因此,我在日期上进行了分组查询。但是它显示了前一天的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")

2
如果来自“新西兰”的某人想要查看由“意大利”中的某人提交的数据,那该怎么办呢?那么你应该如何存储时间呢?这正是使用UTC日期的原因,因为它们代表了每个人的相同时间点。在您的“客户端”上将其转换为本地时间,并对“查询参数”执行相同操作,将本地日期转换回UTC。这样,全球的查询和数据都是一致的。 - Blakes Seven
@BlakesSeven 但问题是按组显示记录在先前的日期。在Mongo进行分组查询时,是否可以传递一些参数,如日期区间? - Rohit Bansal
2个回答

8
在Mongo 3.6版本中,已经添加了时区功能。Mongo文档。提取带有时区的日期部分的表达式为:
{ 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 }
> 

时区列表


4
处理时区是一个“客户端”问题,因此您应该通过时区偏移修改“查询”时间,以便允许在UI中选择“本地”时间等。同样适用于UI显示,其中日期应以本地时间表示。
对于聚合原则也是如此。只需按时区偏移进行调整即可。使用日期数学而不是使用日期聚合运算符:
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 }

因此,当您从另一个BSON日期中减去$subtract时,结果是自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之前和之后的这些日期)来“处理它”。将数据库内容存储在GMT中“非常合理”,因为这是一个稳定的点,你可以从这里进行所有的调整。这就是这里的教训。 - Blakes Seven

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接