Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

$week function and first day of week in aggregation

I use $project operator to extract week part from Date, then do grouping by weeks in aggregation pipeline:

{ $project: { 
    year: { $year: [ "$datetime" ] }, 
    week: { $week: [ "$datetime" ] }, 
    ....
    }
},
....
{ $group: { 
    _id: { 
        year: "$year", 
        week: "$week", 
        },
        .....
    }
}

But the $week operator I use, always counts Sunday as a first day of week, and we use Monday as first day of week in our location. Therefore, the results grouped by week are always incorrect for me.

The existing request in mongo tracking system seems to be unresolved for more than a year (is it really so rarely needed option?).

Any possible options are welcome. Maybe there is possibility to create some custom function in javascript and add/replace it somewhere?

like image 706
Volodymyr Metlyakov Avatar asked Feb 03 '14 09:02

Volodymyr Metlyakov


4 Answers

Starting in Mongo 5, it's a perfect use case for the new $dateTrunc aggregation operator:

// { date: ISODate("2021-12-02") } // Thursday
// { date: ISODate("2021-12-05") } // Sunday
// { date: ISODate("2021-12-05") } // Sunday
// { date: ISODate("2021-12-06") } // Monday
// { date: ISODate("2022-12-06") } // Following year
db.collection.aggregate([
  { $group: {
    _id: { $dateTrunc: { date: "$date", unit: "week", startOfWeek: "monday" } },
    total: { $count: {} }
  }}
])
// { _id: ISODate("2021-12-06"), total: 1 }
// { _id: ISODate("2021-11-29"), total: 3 }
// { _id: ISODate("2022-12-05"), total: 1 }

$dateTrunc truncates your dates at the beginning of their week (the truncation unit). It's kind of a modulo on dates per week.

And you can specify what day is considered the start of the week using the $startOfWeek parameter which here again would otherwise default to Sunday.

Weeks in the output will be defined by their first day (mondays' date).

like image 96
Xavier Guihot Avatar answered Nov 15 '22 00:11

Xavier Guihot


MongoDB has finally added $isoWeek and $isoWeekYear in 3.4 which will start the week on a Monday.

More information: https://docs.mongodb.com/manual/reference/operator/aggregation/isoWeek/

like image 38
Michiel De Mey Avatar answered Nov 15 '22 01:11

Michiel De Mey


You can use the below pipeline to modify the $week operation as per your requirement

[{ 
  $project: 
  {
    week: { $week: [ "$datetime" ] },
    dayOfWeek:{$dayOfWeek:["$datetime"]}
  }
},
{
  $project:
    {
      week:{$cond:[{$eq:["$dayOfWeek",1]},{$subtract:["$week",1]},'$week']}
    }
}]

What it does is that in the first stage it projects the weekOfYear and dayOfWeek. In the second stage it checks whether the dayOfWeek is sunday, if thats the case then it modifies the week to week -1. This will then function as if the week is starting on monday.

like image 35
Sarath Nair Avatar answered Nov 15 '22 00:11

Sarath Nair


Trick: just change one line by subtracting one day from datetime

$project: { 
    year: { $year: [ "$datetime" ] }, 
    week: { $week: {$subtract: ["$datetime", 86400000] } }, // <--- minus 1 day (in ms).
    ....
    }

This works on new and old mongo versions.

like image 1
Oleg Matei Avatar answered Nov 15 '22 01:11

Oleg Matei