Search code examples
javascriptnode.jsmongodbdatetimeaggregate

new Date doesn"t work fine with $match aggregation?


I'm trying to filtering some date data by starting using $unwind for destructing the date array:

"startDates": [
     "2024-05-15T09:30:00.000Z",
     "2024-11-13T10:00:00.000Z",
     "2024-02-10T00:00:00.000Z"
 ] 

using:

{
  $unwind: "$startDates",
},

and then use $match for filtering date data with years:

{
  $match: {
    startDates: {
      $gte: new Date(`${year}-01-01`),
      $lte: new Date(`${year}-12-31`),
    },
  },
},

but the response is return an empty array

{
  "status": "Success",
  "result": 0,
  "data": {
    "plan": []
  }
}

but when i remove new Date and write the date manually the response return the data i want like that:

{
  $match: {
    startDates: {
      $gte: `${year}-01-01`,
      $lte: `${year}-12-31`,
    },
  },
},

but then when i try to group the data with $group and using $month pupline it show me this error:

{
  "status": "failed",
  "message": "PlanExecutor error during aggregation :: caused by :: can't convert from BSON type string to Date"
}

this is the full function code:

exports.getMonthlyPlan = async (req, res) => {
  try {
    const year = req.params.year * 1;
    const plan = await Tour.aggregate([{
        $unwind: "$startDates",
      },
      {
        $match: {
          startDates: {
            $gte: `${year}-01-01`,
            $lte: `${year}-12-31`,
          },
        },
      },
      {
        $group: {
          _id: {
            $month: "$startDates"
          },
          numTourStarts: {
            $sum: 1
          },
        },
      },
    ]);

    res.status(200).json({
      status: "Success",
      result: plan.length,
      data: {
        plan,
      },
    });
  } catch (error) {
    res.status(400).json({
      status: "failed",
      message: error.message,
    });
  }
};

I hope I have explained the problems in a clear way

i'm trying to play with new Date function but i don"t understand the issue


Solution

  • Storing date values a string is a design flaw, you should never do that. Store always proper Date objects.

    First you can convert the data type with an update like this:

    db.collection.updateMany(
       { startDates: { $type: "string" } },
       [
          {
             $set: {
                startDates: {
                   $map: {
                      input: "$startDates",
                      in: { $dateFromString: { dateString: "$$this" } }
                   }
                }
             }
          }
       ]
    )
    

    Then your query will work. This one may give better performance:

    db.gcollection.aggregate([
       {
          $match: {
             startDates: {
                $gte: new Date(`${year}-01-01`),
                $lte: new Date(`${year}-12-31`)
             }
          }
       },
       {
          $set: {
             startDates: {
                $filter: {
                   input: "$startDates",
                   cond: { $eq: [{ $year: "$$this" }, year] }
                }
             }
          }
       },
       { $unwind: "$startDates" },
       {
          $group: {
             _id: { $month: "$startDates" },
             numTourStarts: { $count: {} }
          }
       }
    ])