4

I am new to Mongo DB. Can anyone help me in how to get the number of weekdays between two Given dates.

$dayOfWeek of a date will give the value of the date 0 for sunday and 7 for saturday.

But my question is how can i increment the date from start date to end date to find the number of weekdays.

Sample Data:

 > db.data.insert({"startDate":ISODate('2016-0101'), "endDate":ISODate('2016-02-02')})  

    WriteResult({ "nInserted" : 1 })
    > db.data.find().pretty()
    { 
            "_id" : ObjectId("57c6e6a6a1e49d654caca17a"), 
            "startDate" : ISODate("2016-01-01T00:00:00Z"),
            "endDate" : ISODate("2016-02-02T00:00:00Z")
    }

Now need to find the number of Weekdays between the Start Date (2016-01-01) and End Date (2016-02-02).

I can write a function which accepts the start date and end date, but in that function how to iterate from the start date to end date?

Any help is requested.

1 Answers1

4

To find total amount of weekdays between 2 dates you can subtract total amount of weekends from total amount of days. The main problem here is to find total amount of weekends.

Example [2016-12-23; 2017-01-01]

_Dec23___Dec24___Dec25____... ____Dec30___Dec31___Jan01___...

__Fri______Sat_____Sun______...______Fri_____Sat_____Sun_____...

As you see, there are 4 weekends between those dates (Dec24, Dec25, Dec31 and Jan01).

Solution

One way to solve it programmatically is to use aggregation framework with its arithmetic and date aggregation operators.

It could be done with splitting the task into 2 subtasks:

  1. Find amount of full weeks between startDate and endDate (name it weeksBetween). In the above example weeksBetween = 1.
  2. Find amount of full days between startDate + 7 * weeksBetween and endDate (name it left, it could be 1,2,3,4,5 or 6). In the above example left = 3 (left days are Fri, Sat and Sun). Of interest are amount of weekends in those left days (name it left_weekends, it could be 0,1 or 2). In the above example left_weekends = 2.

Then, total amount of weekends is 2 * weeksBetween + left_weekends. In the above example it's 2 * 1 + 2 = 4.

To find left_weekends you can use $dayOfWeek date aggregation operator.

  1. for Sun dayOfWeek = 1 and left_weekends = 1 if left is 1,2,3,4,5 or left_weekends = 2 if left is 6 (including Sun and next Sat).
  2. for Mon dayOfWeek = 2 and left_weekends = 0 if left is 1,2,3,4 or left_weekends = 1 if left is 5 or left_weekends = 2 if left is 6 (including next Sat and Sun).

...

  1. for Sat dayOfWeek = 7 and left_weekends = 2 for any left.

Hence, there are next rules we can use:

  1. If remainder > 7, then left_weekends = 2.

  2. If remainder = 7, then left_weekends = 1 for Mon-Sat and left_weekends = 2 for Sun.

  3. If remainder < 7 then left_weekends = 0 for Mon-Sat and left_weekends = 1 for Sun.

Here, remainder = dayOfWeek + left.

Query

db.dates.aggregate(
  {
    $project:
      {
        "daysBetween":
          {
            $add: [
              {
                $floor:
                  {
                    $divide: [
                      { $subtract: [ "$endDate", "$startDate" ] },
                      1000 * 60 * 60 * 24
                    ]
                  }
              },
              1
            ]
          },
        "startDay": { $dayOfWeek: "$startDate" },
        "endDay": { $dayOfWeek: "$endDate" }
      }
  },
  {
    $project:
      {
        "daysBetween": "$daysBetween",
        "weeksBetween": { $floor: { $divide: [ "$daysBetween", 7 ] } },
        "startDay": "$startDay",
        "remainder":
          {
            $add: [
              { $abs: { $subtract: [ "$endDay", "$startDay" ] } },
              "$startDay"
            ]
          }
      }
  },
  {
    $project:
      {
        "weekendsBetween":
          {
            $add: [
              { $multiply: [ "$weeksBetween", 2 ] },
              {
                $cond:
                  {
                    if: { $gt: [ "$remainder", 7 ] },
                    then: 2,
                    else:
                      {
                        $cond:
                          {
                            if: { $eq: [ "$remainder", 7 ] },
                            then: 1,
                            else: 0
                          }
                      }
                  }
              },
              { $cond: { if: { $eq: [ "$startDay", 1 ] }, then: 1, else: 0 } }
            ]
          },
        "daysBetween": "$daysBetween"
      }
  },
  {
    $project:
      {
        "weekdaysBetween": { $subtract: [ "$daysBetween", "$weekendsBetween" ] }
      }
  }
);

Sample

{ "_id": 1, "startDate": ISODate("2016-12-23T00:00:00Z"), 
            "endDate":   ISODate("2017-01-01T00:00:00Z") }
{ "_id": 2, "startDate": ISODate("2016-12-23T00:00:00Z"), 
            "endDate":   ISODate("2017-01-09T00:00:00Z") }
{ "_id": 3, "startDate": ISODate("2016-12-23T00:00:00Z"), 
            "endDate":   ISODate("2017-01-18T00:00:00Z") }  

Results

{ "_id": 1, "weekdaysBetween" : 6 }
{ "_id": 2, "weekdaysBetween" : 12 }
{ "_id": 3, "weekdaysBetween" : 19 }

PS Extending last $project stage you can also get total amounts of days and weekends between 2 dates. Don't forget to change collection name before testing the query.

mr.tarsa
  • 6,386
  • 3
  • 25
  • 42
  • When I am executing the given code I am getting the following error... And Idea on this??? Error: assert: command failed: { "errmsg" : "exception: invalid operator '$sum'", "code" : 15999, "ok" : 0 } : aggregate failed : aggregate failed at Error () at doassert (src/mongo/shell/assert.js:11:14) at Function.assert.commandWorked (src/mongo/shell/assert.js:254:5) at DBCollection.aggregate (src/mongo/shell/collection.js:1278:12) at (shell):1:9 at src/mongo/shell/assert.js:13 – giridharan ck Sep 06 '16 at 06:57
  • If yours MongoDB version is prior to 3.2, than use `$add` *arithmetic aggregation operator* instead of `$sum`. Updated the answer. – mr.tarsa Sep 06 '16 at 07:12
  • i tired to do this in the Coding Ground provided by tutorialspoint – giridharan ck Sep 06 '16 at 07:29
  • It uses v3.0.9. You can check it with `db.version()` command. `$sum` operator does not work in `$project` stage for v.3.0.9. Replace it with `$add` operator as I said and did in the answer. – mr.tarsa Sep 06 '16 at 08:04
  • I have replaced the sum with the add and have removed the $floor and $abs as there both also was available only in 3.2. Then i am getting the output like { "_id" : ObjectId("57d011e6e6d6fa2c2c0998ae"), "weekdaysbetween" : null } Even when i gave a start date as month begenning and end date as month end date. Any idea on this? – giridharan ck Sep 07 '16 at 13:21
  • `$abs` is not required if `$startDate` is always smaller than `$endDate`, I added it to handle possible irregularities. If you want to leave it, than replace it with [`$cond`](https://docs.mongodb.com/manual/reference/operator/aggregation/cond/) operator (available since v2.6). According to `$floor` operator, you can replace it for older versions as described [here](http://stackoverflow.com/questions/32554156/how-to-aggregate-by-floor-in-mongodb). – mr.tarsa Sep 07 '16 at 17:24