计算年、月、日的日期差异

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(...

【讨论】:

以上是关于计算年、月、日的日期差异的主要内容,如果未能解决你的问题,请参考以下文章

如何找到两个日期之间的差异c ++

如何计算两个日期之间的差异?

Firebase BigQuery 导出架构大小差异

天数差异(月/年) - Laravel

JavaScript 中两个日期的年、月、日之间的差异

如何在java中计算两个日期之间的年龄或差异