MongoDB 聚合 $dateToString 时差问题解决

Jul 10, 2018 阅读(12939)

标签: MongoDB

存储在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
            } 
            
        }
    ]
);

image.png


发现格式化后显示的时间就存在时间差导致的问题,原因是聚合操作默认是根据标准时间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
            } 
            
        }
    ]
);

image.png



除了聚合函数中要注意时差问题,在使用客户端工具写脚本时也需要注意时间的时差问题

//准备测试数据
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")}});

image.png

总结: NoSQLBooster 工具new Date("2018-07-31")不指定时间则时间是 00:00:00.000 + 当前地区时差.


MongoDB学习园