计算年、月、日的日期差异
Posted
技术标签:
【中文标题】计算年、月、日的日期差异【英文标题】:Calculate date difference in year, month, day 【发布时间】:2018-04-24 11:35:57 【问题描述】:我有以下疑问:
db.getCollection('user').aggregate([
$unwind: "$education",
$project:
duration: "$divide":[$subtract: ['$education.to', '$education.from'] , 1000 * 60 * 60 * 24 * 365]
,
$group:
_id: '$_id',
"duration": $sum: '$duration'
]
])
以上查询结果为:
"_id" : ObjectId("59fabb20d7905ef056f55ac1"),
"duration" : 2.34794520547945
/* 2 */
"_id" : ObjectId("59fab630203f02f035301fc3"),
"duration" : 2.51232876712329
但我想做的是在year
+ month
+ day
格式中获取其持续时间,例如:2 y, 3 m, 20 d
。
还有一点,如果课程正在进行,to
字段为空,而另一个字段isGoingOn: true
,所以在这里我应该使用当前日期而不是to
字段来计算持续时间。
并且用户有课程子文档数组
education: [
"courseName": "Java",
"from" : ISODate("2010-12-08T00:00:00.000Z"),
"to" : ISODate("2011-05-31T00:00:00.000Z"),
"isGoingOn": false
,
"courseName": "php",
"from" : ISODate("2013-12-08T00:00:00.000Z"),
"to" : ISODate("2015-05-31T00:00:00.000Z"),
"isGoingOn": false
,
"courseName": "mysql",
"from" : ISODate("2017-02-08T00:00:00.000Z"),
"to" : null,
"isGoingOn": true
]
另一点是:该日期在一个子文档中可能与另一个子文档不连续。一个用户可能有一个课程 1 年,然后两年后,他/她开始了他/她的下一个课程 1 年零 3 个月(即该用户总共有 2 年零 3 个月的课程时长) .
我想要的是获取educations数组中每个子文档的日期差异,并将它们相加。假设在我的示例数据中Java
课程持续时间为 6 个月 22 天,PHP
课程持续时间为 1 年 6 个月 22 天,最后一个是从 2017 年 2 月 8 日到现在on,所以我的教育时间是这些时间间隔的总和。
【问题讨论】:
【参考方案1】:你可以简单地使用现有的date aggregation operators,而不是像现在这样使用数学转换为“天”:
db.getCollection('user').aggregate([
"$unwind": "$education" ,
"$group":
"_id": "$_id",
"years":
"$sum":
"$subtract": [
"$subtract": [
"$year": "$ifNull": [ "$education.to", new Date() ] ,
"$year": "$education.from"
],
"$cond":
"if":
"$gt": [
"$month": "$ifNull": [ "$education.to", new Date() ] ,
"$month": "$education.from"
]
,
"then": 0,
"else": 1
]
,
"months":
"$sum":
"$add": [
"$subtract": [
"$month": "$ifNull": [ "$education.to", new Date() ] ,
"$month": "$education.from"
],
"$cond":
"if":
"$gt": [
"$month": "$ifNull": ["$education.to", new Date() ] ,
"$month": "$education.from"
]
,
"then": 0,
"else": 12
]
,
"days":
"$sum":
"$add": [
"$subtract": [
"$dayOfYear": "$ifNull": [ "$education.to", new Date() ] ,
"$dayOfYear": "$education.from"
],
"$cond":
"if":
"$gt": [
"$month": "$ifNull": [ "$education.to", new Date() ] ,
"$month": "$education.from"
]
,
"then": 0,
"else": 365
]
,
"$project":
"years":
"$add": [
"$years",
"$add": [
"$floor": "$divide": [ "$months", 12 ] ,
"$floor": "$divide": [ "$days", 365 ]
]
]
,
"months":
"$mod": [
"$add": [
"$months",
"$floor":
"$multiply": [
"$divide": [ "$days", 365 ] ,
12
]
],
12
]
,
"days": "$mod": [ "$days", 365 ]
])
它“有点”是“天”和“月”的近似值,没有必要的操作来“确定”闰年,但它会得到对大多数目的来说应该“足够接近”的结果.
只要您的 MongoDB 版本是 3.2 或更高版本,您甚至可以在没有 $unwind
的情况下执行此操作:
db.getCollection('user').aggregate([
"$addFields":
"duration":
"$let":
"vars":
"edu":
"$map":
"input": "$education",
"as": "e",
"in":
"$let":
"vars": "toDate": "$ifNull": ["$$e.to", new Date()] ,
"in":
"years":
"$subtract": [
"$subtract": [
"$year": "$$toDate" ,
"$year": "$$e.from"
],
"$cond":
"if": "$gt": [ "$month": "$$toDate" , "$month": "$$e.from" ] ,
"then": 0,
"else": 1
]
,
"months":
"$add": [
"$subtract": [
"$ifNull": [ "$month": "$$toDate" , new Date() ] ,
"$month": "$$e.from"
],
"$cond":
"if": "$gt": [ "$month": "$$toDate" , "$month": "$$e.from" ] ,
"then": 0,
"else": 12
]
,
"days":
"$add": [
"$subtract": [
"$ifNull": [ "$dayOfYear": "$$toDate" , new Date() ] ,
"$dayOfYear": "$$e.from"
],
"$cond":
"if": "$gt": [ "$month": "$$toDate" , "$month": "$$e.from" ] ,
"then": 0,
"else": 365
]
,
"in":
"$let":
"vars":
"years": "$sum": "$$edu.years" ,
"months": "$sum": "$$edu.months" ,
"days": "$sum": "$$edu.days"
,
"in":
"years":
"$add": [
"$$years",
"$add": [
"$floor": "$divide": [ "$$months", 12 ] ,
"$floor": "$divide": [ "$$days", 365 ]
]
]
,
"months":
"$mod": [
"$add": [
"$$months",
"$floor":
"$multiply": [
"$divide": [ "$$days", 365 ] ,
12
]
],
12
]
,
"days": "$mod": [ "$$days", 365 ]
])
这是因为从 MongoDB 3.4 开始,您可以将$sum
直接与$addFields
或$project
等阶段的表达式数组或任何列表一起使用,而$map
可以应用这些相同的“日期聚合运算符”针对每个数组元素的表达式,而不是先执行$unwind
。
所以主要的数学运算真的可以在“减少”数组的一部分中完成,然后每个总数都可以通过年份的一般“除数”以及任何超限的“模数”或“余数”进行调整在月份和日期。
基本上返回:
"_id" : ObjectId("5a07688e98e4471d8aa87940"),
"education" : [
"courseName" : "Java",
"from" : ISODate("2010-12-08T00:00:00.000Z"),
"to" : ISODate("2011-05-31T00:00:00.000Z"),
"isGoingOn" : false
,
"courseName" : "PHP",
"from" : ISODate("2013-12-08T00:00:00.000Z"),
"to" : ISODate("2015-05-31T00:00:00.000Z"),
"isGoingOn" : false
,
"courseName" : "Mysql",
"from" : ISODate("2017-02-08T00:00:00.000Z"),
"to" : null,
"isGoingOn" : true
],
"duration" :
"years" : 3.0,
"months" : 3.0,
"days" : 259.0
鉴于 2017 年 11 月 11 日
【讨论】:
您的查询分别计算year, month, day
,我想要的是1 year and 2 month and 10 day
,而不是1 year, 14 month, 396 day
@jones 这不会发生。您可以使用生成的值来形成一个“字符串”,但这只是将这些值连接成一个完整的字符串,这在客户端代码中确实更有意义。你真的应该让数据库自然地“减少”内容。就像这里显示的那样。并注意我实际做了什么,因为我确实相应地调整了所有的年、月和日。
@jones 认为我误读了您的评论。再看,我确实在调整每个部分。只是这里的数学与你假设的不同。我从来没有到14个月。它要么是正面的,要么是负面的,然后进行相应的调整。与年份相同,基于月份。
我认为我的评论不够清楚,假设用户加入的课程时长是1 year and 2 month and 10 days
,我想这样计算,而不是像计算1 year
,但在月份等于14 month
,白天是410 day
具有相同数据的查询返回: "_id" : ObjectId("59fabb20d7905ef056f55ac1"), "years" : 1.0, "months" : 15.0, "days" : 492.0 /* 2 */ "_id" : ObjectId("59fab630203f02f035301fc3"), "years" : 1.0, "months" : 17.0, "days" : 551.0
【参考方案2】:
请尝试此聚合以获取日、月和年的日期差异,添加多个 $addFields
阶段计算并减少日期差异,月份范围没有下溢,这里假设为 1 个月 = 30 天
管道
db.edu.aggregate(
[
$addFields :
trainingPeriod :
$map :
input : "$education",
as : "t",
in :
year: $subtract: [$year : $ifNull : ["$$t.to", new Date()], $year : "$$t.from"],
month: $subtract: [$month : $ifNull : ["$$t.to", new Date()], $month : "$$t.from"],
dayOfMonth: $subtract: [$dayOfMonth : $ifNull : ["$$t.to", new Date()], $dayOfMonth : "$$t.from"]
,
$addFields :
trainingPeriod :
$map :
input : "$trainingPeriod",
as : "d",
in :
year: "$$d.year",
month: $cond : [$lt : ["$$d.dayOfMonth", 0], $subtract : ["$$d.month", 1], "$$d.month" ],
day: $cond : [$lt : ["$$d.dayOfMonth", 0], $add : [30, "$$d.dayOfMonth"], "$$d.dayOfMonth" ]
,
$addFields :
trainingPeriod :
$map :
input : "$trainingPeriod",
as : "d",
in :
year: $cond : [$lt : ["$$d.month", 0], $subtract : ["$$d.year", 1], "$$d.year" ],
month: $cond : [$lt : ["$$d.month", 0], $add : [12, "$$d.month"], "$$d.month" ],
day: "$$d.day"
,
$addFields :
total :
$reduce :
input : "$trainingPeriod",
initialValue : year : 0, month : 0, day : 0,
in :
year: $add : ["$$this.year", "$$value.year"],
month: $add : ["$$this.month", "$$value.month"],
day: $add : ["$$this.day", "$$value.day"]
,
$addFields :
total :
year : "$total.year",
month : $add : ["$total.month", $floor : $divide : ["$total.day", 30]],
day : $mod : ["$total.day", 30]
,
$addFields :
total :
year : $add : ["$total.year", $floor : $divide : ["$total.month", 12]],
month : $mod : ["$total.month", 12],
day : "$total.day"
]
).pretty()
结果
"_id" : ObjectId("5a895d4721cbd77dfe857f95"),
"education" : [
"courseName" : "Java",
"from" : ISODate("2010-12-08T00:00:00Z"),
"to" : ISODate("2011-05-31T00:00:00Z"),
"isGoingOn" : false
,
"courseName" : "PHP",
"from" : ISODate("2013-12-08T00:00:00Z"),
"to" : ISODate("2015-05-31T00:00:00Z"),
"isGoingOn" : false
,
"courseName" : "Mysql",
"from" : ISODate("2017-02-08T00:00:00Z"),
"to" : null,
"isGoingOn" : true
],
"trainingPeriod" : [
"year" : 0,
"month" : 5,
"day" : 23
,
"year" : 1,
"month" : 5,
"day" : 23
,
"year" : 1,
"month" : 0,
"day" : 10
],
"total" :
"year" : 2,
"month" : 11,
"day" : 26
>
【讨论】:
【参考方案3】:您可以使用带有moment js
库的客户端处理来简化您的代码。
所有的日期时间数学都由 moment js 库处理。使用duration
计算减少时间diff
使用reduce来添加所有数组元素的时间差异,然后使用时刻持续时间来输出以年/月/日为单位的时间。
它解决了两个问题:
-
为您提供两个日期之间年月日的准确差异。
为您提供预期的格式。
例如:
var education = [
"courseName": "Java",
"from" : new Date("2010-12-08T00:00:00.000Z"),
"to" : new Date("2011-05-31T00:00:00.000Z"),
"isGoingOn": false
,
"courseName": "PHP",
"from" : new Date("2013-12-08T00:00:00.000Z"),
"to" : new Date("2015-05-31T00:00:00.000Z"),
"isGoingOn": false
,
"courseName": "Mysql",
"from" : new Date("2017-02-08T00:00:00.000Z"),
"to" : null,
"isGoingOn": true
];
var reducedDiff = education.reduce(function(prevVal, elem)
if(elem.isGoingOn) elem.to = new Date();
var diffDuration = moment(elem.to).diff(moment(elem.from));
return prevVal + diffDuration;
, 0);
var duration = moment.duration(reducedDiff);
alert(duration.years() +" y, " + duration.months() + " m, " + duration.days() + " d " );
var durationstr = duration.years() +" y, " + duration.months() + " m, " + duration.days() + " d ";
MongoDb 集成:
var reducedDiff = db.getCollection('user').find(,education:1).reduce(function(...
【讨论】:
以上是关于计算年、月、日的日期差异的主要内容,如果未能解决你的问题,请参考以下文章