【问题标题】: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,所以我的教育时间是这些时间间隔的总和。

【问题讨论】:

    标签: mongodb mongoose aggregation-framework


    【解决方案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来添加所有数组元素的时间差异,然后使用时刻持续时间来输出以年/月/日为单位的时间。

      它解决了两个问题:

      1. 为您提供两个日期之间年月日的准确差异。
      2. 为您提供预期的格式。

      例如:

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

      【讨论】:

        猜你喜欢
        • 2019-08-03
        • 1970-01-01
        • 1970-01-01
        • 2016-07-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-04-07
        • 1970-01-01
        相关资源
        最近更新 更多