web-dev-qa-db-fra.com

Calculer la différence de date en année, mois, jour

J'ai la requête suivante:

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'}  
   }}]
])

Le résultat de la requête ci-dessus est: 

{
    "_id" : ObjectId("59fabb20d7905ef056f55ac1"),
    "duration" : 2.34794520547945
}

/* 2 */
{
    "_id" : ObjectId("59fab630203f02f035301fc3"),
    "duration" : 2.51232876712329
}

Mais ce que je veux faire, c'est obtenir sa durée au format year + month + day, quelque chose comme: 2 y, 3 m, 20 d. Un autre point, si un cours est donné dans le champ to est nul, et un autre champ isGoingOn: true, donc ici, je devrais calculer la durée en utilisant la date actuelle au lieu du champ to. Et l'utilisateur a un tableau de sous-documents de cours

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
   }
]

Un autre point est le suivant: cette date peut ne pas être continue dans un sous-document à l’autre sous-document. Un utilisateur peut suivre un cours pendant 1 an puis, au bout de deux ans, il commence son prochain cours pendant 1 an et 3 mois (cela signifie que cet utilisateur a au total une durée de cours de 2 ans et de 3 mois). . Ce que je veux, c’est d’obtenir la différence de date de chaque sous-document dans le tableau educations, et de les additionner. Supposons que dans mon exemple de données Java durée du cours soit de 6 mois et 22 jours, PHP durée du cours est de 1 an et 6 mois et 22 jours, et le dernier est du 8 février 2017 à maintenant, et ça continue, donc mon la durée de l'éducation est la somme de ces intervalles.

8
jones

S'il vous plaît essayez cette agrégation pour obtenir la différence de date en jours, mois et années, ajouté le calcul de plusieurs étapes $addFields et réduire les différences à la date, la plage de mois sans dépassement de valeur, et l'hypothèse est ici 1 mois = 30 jours

pipeline

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

résultat

{
    "_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
Saravana

Vous pouvez simplifier votre code en utilisant le traitement côté client avec moment js library.

Tous les calculs de date et heure sont gérés par la bibliothèque moment js. Utilisez duration pour calculer le temps réduit diff

Utilisez reduction pour ajouter la diff de temps sur tous les éléments du tableau, suivie de la durée du moment pour afficher le temps en années/mois/jours.

Il résout deux problèmes: 

  1. Vous donne la différence exacte en années mois et jours entre deux dates.
  2. Vous donne le format attendu.

Par exemple:

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 ";

Intégration MongoDb:

var reducedDiff = db.getCollection('user').find({},{education:1}).reduce(function(...
0
Veeram

Eh bien, vous pouvez simplement utiliser l'opérateur de regroupement date existant s au lieu d'utiliser les mathématiques pour convertir en "jours" comme vous l'avez actuellement:

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 ] }
  }}
])

C'est "en quelque sorte" une approximation des "jours" et des "mois" sans les opérations nécessaires pour être "certaines" années bissextiles, mais vous obtiendrez un résultat qui devrait être "assez proche" pour la plupart des objectifs.

Vous pouvez même le faire sans $unwind tant que votre version de MongoDB est 3.2 ou supérieure:

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 ] }
            }
          }
        }
      }
    }
  }}
]) 

En effet, à partir de MongoDB 3.4, vous pouvez utiliser $sum directement avec un tableau d’expressions ou toute liste d’expressions par étapes comme $addFields ou $project , et le $map peut appliquer ces mêmes expressions "opérateur d'agrégation de date" à chaque élément du tableau au lieu de faire $unwind en premier.

Ainsi, les calculs principaux peuvent vraiment être faits dans une partie de la "réduction" du tableau, puis chaque total peut être ajusté par les "diviseurs" généraux pour les années, et le "modulo" ou le "reste" de tout dépassement dans les mois et jours.

Retourne essentiellement:

{
    "_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
    }
}

Le 11 novembre 2017

0
Neil Lunn