How to use DateOperators in Spring Data MongoDB

Published on 09/20/2024

Let’s try to understand how to use DateOperators in a Spring Data MongoDB project using an example use case.

Scenario

We have a collection of sensors installed in various places around the world. Sensors communicate their presence and activity some times per day.

We want to count the number of activity of sensors located in Sydney aggregated by the day of month.

How can I implement this requirement in Spring Data MongoDB ?

Solution

First of all some context:

  • MongoDB saves dates in UTC timezone.
  • The timezone of Sydney is UTC+10/UTC+11.

I have to use the functions $month and $dayOfMonth for extracting the value of day and month passing the timezone of interest.

Below the MongoDB aggregation:

db.getCollection('presence').aggregate([
  {$group: {
      _id: {
            dayOfMonth: {
                $dayOfMonth :{
                    date: '$date', 
                    timezone: 'Australia/Sydney' 
                }
            },
            month: {
                $month :{
                    date: '$date', 
                    timezone: 'Australia/Sydney' 
                }
           }
      }, 
      howMany: {$sum: 1}}}
])

How can I translate the above query in Spring Data MongoDB?

Let’s introduce the structure of the MongoDB document class.

@Document(collection = "presence")
public class PresenceDocument {
	@Id
	private String id;
	private String sensorId;
	private ZonedDateTime date;

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getSensorId() {
		return sensorId;
	}

	public void setSensorId(String sensorId) {
		this.sensorId = sensorId;
	}

	public ZonedDateTime getDate() {
		return date;
	}

	public void setDate(ZonedDateTime date) {
		this.date = date;
	}

}

Then the Metrics class that represent the counter we want to calculate.

public class Metrics {
	private GroupId id;
	private int howMany;

	public static class GroupId {
		private int dayOfMonth;
		private int month;

		public int getDayOfMonth() {
			return dayOfMonth;
		}

		public void setDayOfMonth(int dayOfMonth) {
			this.dayOfMonth = dayOfMonth;
		}

		public int getMonth() {
			return month;
		}

		public void setMonth(int month) {
			this.month = month;
		}
	}

	public GroupId getId() {
		return id;
	}

	public void setId(GroupId id) {
		this.id = id;
	}

	public int getHowMany() {
		return howMany;
	}

	public void setHowMany(int howMany) {
		this.howMany = howMany;
	}

}

In the Spring Data code I have to use the DateOperators class that exposes a set of static functions to process dates in MongoDB accepting a timezone as parameter.

A possible solution is:

List<AggregationOperation> operations = new ArrayList<AggregationOperation>();
Timezone sydneyTimezone = DateOperators.Timezone.fromZone(ZoneId.of("Australia/Sydney"));
operations.add(
	Aggregation.project("sensorId")
		.and(DateOperators.DayOfMonth.dayOfMonth("date").withTimezone(sydneyTimezone)).as("dayOfMonth")
		.and(DateOperators.Month.monthOf("date").withTimezone(sydneyTimezone)).as("month")
);
operations.add(Aggregation.group("dayOfMonth","month").count().as("howMany"));
Aggregation aggregation = Aggregation.newAggregation(operations);
AggregationResults<Metrics> result = mongo.aggregate(aggregation, PresenceDocument.class, Metrics.class);
  • Article is published under CC-BY 4.0
  • If not explicitly declared code snippets are published under MIT license