存储在MongoDB中的时间是标准时间UTC +0:00,而中国时区是+8.00 由于时差的存在在使用聚合函数$dateToString时发现统计不准问题.
为了重现问题,准备如下数据测试
db.sales.find({}) /* 1 createdAt:2018/7/9 下午6:20:31*/ { "_id" : ObjectId("5b4336ef72af54527036d7fc"), "item" : "冰淇淋", "price" : 5, "quantity" : 2, "date" : ISODate("2014-03-01T16:00:00.000+08:00") }, /* 2 createdAt:2018/7/9 下午6:21:13*/ { "_id" : ObjectId("5b43371972af54527036d7fd"), "item" : "雪糕", "price" : 3, "quantity" : 2, "date" : ISODate("2014-03-02T06:00:00.000+08:00") }
测试聚合使用$dateToString格式化时间
db.sales.aggregate( [ { $project: { time: { $dateToString: { format: "%Y-%m-%d", date: "$date" } } , item:true } } ] );
发现格式化后显示的时间就存在时间差导致的问题,原因是聚合操作默认是根据标准时间UTC来进行计算的.
发现问题官方文档查找到对dateToString操作提供时区操作,实例如下:
db.sales.aggregate( [ { $project: { yearMonthDayUTC: { $dateToString: { format: "%Y-%m-%d", date: "$date" } }, timewithOffsetNY: { $dateToString: { format: "%H:%M:%S:%L%z", date: "$date", timezone: "America/New_York" } }, timewithOffset430: { $dateToString: { format: "%H:%M:%S:%L%z", date: "$date", timezone: "+04:30" } }, minutesOffsetNY: { $dateToString: { format: "%Z", date: "$date", timezone: "America/New_York" } }, minutesOffset430: { $dateToString: { format: "%Z", date: "$date", timezone: "+04:30" } } } } ] );
执行产生以下错误信息,查询当前数据库版本发现当前数据库版本还不支持timezone参数
{ "message" : "Unrecognized argument to $dateToString: timezone", "stack" : "MongoError: Unrecognized argument to $dateToString: timezone" + "at queryCallback (/tmp/.mount_nosqlbC2yIY4/app/resources/app.asar/node_modules/mongodb-core/lib/cursor.js:223:25)" + "at /tmp/.mount_nosqlbC2yIY4/app/resources/app.asar/node_modules/mongodb-core/lib/connection/pool.js:541:18" + "at _combinedTickCallback (internal/process/next_tick.js:131:7)" + "at process._tickCallback (internal/process/next_tick.js:180:9)", "name" : "MongoError", "ok" : 0, "errmsg" : "Unrecognized argument to $dateToString: timezone", "code" : 18534, "codeName" : "Location18534" }
db.version(); //查询数据库版本
执行发现当前数据库版本: 3.4.10
3.4版官网文档: https://docs.mongodb.com/v3.4/reference/operator/aggregation/dateToString/index.html
对低版本数据库不支持timezone参数时导致时差问题,最终通过$add操作补齐时差,实例如下:
db.sales.aggregate( [ { $project: { time: { $dateToString: { format: "%Y-%m-%d", date: { $add: ["$date", 8 * 3600000] } } } , item:true } } ] );
除了聚合函数中要注意时差问题,在使用客户端工具写脚本时也需要注意时间的时差问题
//准备测试数据 db.time_test.drop(); for(var hours=1; hours < 24 ; hours ++){ db.time_test.insert([ {date:"2018-07-30", hours:hours, time: new Date("2018-07-30 " + hours +":00:00.000"), current_time:new Date()}, {date:"2018-07-31", hours:hours, time: new Date("2018-07-31 " + hours +":00:00.000"), current_time:new Date()}, ]); } db.time_test.find();
查询发现new Date() 也是需要注意时问题,测试运行如下:
db.time_test.find({time:{$gte:new Date("2018-07-31")}}); //上面的等价下面的查询,没传时间则默认是 00:00:00.000 + 当前地区时差 db.time_test.find({time:{$gte:new Date("2018-07-31 08:00:00.000")}});
总结: NoSQLBooster 工具new Date("2018-07-31")不指定时间则时间是 00:00:00.000 + 当前地区时差.