Search code examples
mongodbspring-data-mongodb

Use DateOperators.Month.withTimezone correctly in a spring data mongo aggregation


I have a set of mongodb documents like these

    * 1 */
{
    "_id" : ObjectId("613b21fe2e57a07bbacc18da"),
    "date" : ISODate("2021-07-31T22:00:00.000Z"),
    "initiativeId" : "613b17332e57a07bbacc164d",
    "studentClass" : "3A",
    "instituteId" : "60c89b7c1442725ff813733c",
    "values" : [ 
        {
            "mode" : "piedi",
            "distanceKm" : 12.2,
            "studentNumber" : 4
        }, 
        {
            "mode" : "pedibus",
            "distanceKm" : 4.2,
            "studentNumber" : 2
        }
    ]
}

/* 2 */
{
    "_id" : ObjectId("613b21fe2e57a07bbacc18dc"),
    "date" : ISODate("2021-09-06T22:00:00.000Z"),
    "initiativeId" : "613b17332e57a07bbacc164d",
    "studentClass" : "5C",
    "instituteId" : "60c89b7c1442725ff813733c",
    "values" : [ 
        {
            "mode" : "moto",
            "distanceKm" : 10.0,
            "studentNumber" : 1
        }
    ]
}

I'm using MongoDB 4.4.

I want to reproduce following working mongodb query using Spring Data Mongo (v2.2.5)

db.initiativeRecord.aggregate([
{ $addFields: { month: { year: {$year : "$date"}, 
                         month: {$month : { date: "$date", timezone: "Europe/Rome"}} 
                       } } },
{ $unwind : "$values" },
{ $group : { _id: {d: "$month", mode: "$values.mode"}, distance: {$sum: "$values.distanceKm" } } }
])

This is the Spring data mongo code

  List<AggregationOperation> operations = new ArrayList<>();
    operations.add(Aggregation.unwind("values"));
    
       
    ProjectionOperation subProject = Aggregation.project("values")
        .and(DateOperators.Month.monthOf("date").withTimezone(Timezone.valueOf("Europe/Rome"))).as("month")
        .and(DateOperators.Year.year("$date")).as("year");
    operations.add(subProject);

    GroupOperation group = Aggregation.group("distance","year", "month","values.mode").sum("values.distanceKm").as("distance");
    project = project.andExpression("_id.mode").as("mode")
        .andExpression("_id.month").as("month")
        .andExpression("_id.year").as("year");

    operations.add(group);
    operations.add(project);

The problem is the use of timezone attribute in $month function

when I try to add this

DateOperators.Month.monthOf("date").withTimezone(Timezone.valueOf("Europe/Rome"))

execution throws following exception

Command failed with error 16006 (Location16006): 'can't convert from BSON type object to Date' on server localhost:40393. The full response is {"ok": 0.0, "errmsg": "can't convert from BSON type object to Date", "code": 16006, "codeName": "Location16006"}

When I remove withTimezone part

DateOperators.Month.monthOf("date")

execution doesn't throw the exception

I don't understand how to use withTimezone correctly in my code


Solution

  • After a lot of tests I can answer the question myself. I decided to do it as future documentation for everybody.

    The code is correct and works well with MongoDb 4.4. Spring data mongodb 2.2.5 supports DateOperators.Month.month.withTimeZone correctly.

    The exception was thrown by de.flapdoodle.embed.mongo (version 2.2.0 in my case) (an embedded mongo db) I use for tests.