How to add x minutes to a date in MongoDB aggregation?
Write MongoDB aggregations like Pro
So I needed to know occupied time for an staff from orders they've been assigned. A customer creates an order and one or more staffs are assigned to that order for services that customer has requested. To get the insights of orders and staff schedule time, I needed to write some aggregation.
The MongoDB schema for orders collection is like this -
{
orderNum: Number,
scheduledAt: Date,
services: [{
staffId: ObjectId,
averageTimeMinute: Number // time taken to complete the service
// ....
}],
// ...
}
scheduledAt
tells when is the service scheduled to be serviced to the customer. And averageTimeMinute
tells the average time takes for that service. The problem here is the scheduledAt
is a Date while averageTimeMinute
is minutes in Number. And I needed to come up with a solution that gives us a range of time when the staff is assigned to do the service.
That can be calculated using plain javascript by -
const fromTime = scheduledAt;
const endTime = new Date(scheduledAt);
endTime.setMinutes(endTime.getMinutes() + services[i].averageTimeMinute)
But the better way of doing anything like this, is with the help of MongoDB aggregations. It is very powerful to manipulate data in a way we want. If you don't know what an aggregation is, you can think of it like a pipeline where you input data from one end and it does something with data and outputs from another end.
The thing is aggregations are hard to understand and write. After hours of trial and error, I finally came up with an aggregation which will give me the desired output I need. Here it is
db.getCollection("orders").aggregate([
{ $unwind: { path: "$services" } },
{
$group: {
_id: "$service.staffId",
services: {
$push: {
start: "$scheduledAt",
end: {
$toDate: {
$add: [
{ $toLong: "$scheduledAt" },
{ $multiply: ["$services.averageMinuteTime", 60000.0] },
],
},
},
},
},
},
},
]);
So the trick here is, since scheduledAt
is a Date and averageMinuteTime
is a Number, we need both of these in same unit, Number. So the first step was to convert scheduledAt
to a unix timestamp and in mongo we can do like this
{ $toLong: "$scheduledAt" }
Now we have both in same type, Number, we also need to make both numbers in same type. Now scheduledAt
is in miliseconds, we also need to make our averageMinuteTime
into miliseconds. We can do it by multiplying by 60000
. And in MongoDB we can do like this -
{ $multiply: ["$services.averageMinuteTime", 60000.0] },
Now, both our scheduledAt
and averageMinuteTime
is in same type and unit. Let's add both and we will get our desired endTime
for the service.
$add: [
{ $toLong: "$scheduledAt" },
{ $multiply: ["$services.averageMinuteTime", 60000.0] },
],
And the last step is to convert it back to date, which in very easy to do -
$toDate: {
$add: [
{ $toLong: "$scheduledAt" },
{ $multiply: ["$services.averageMinuteTime", 60000.0] },
],
},
and here it is, we got out desired output from the pipeline. Now I can render beautiful charts to see when our staffs are scheduled to work.
I hope you found this interesting. Please let me know if you have any feedback.
Thanks