diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java
index 41968b441f..0a0ac44ba0 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java
@@ -538,6 +538,32 @@ public DateSubtract subtract(Object value, TemporalUnit unit) {
DateSubtract.subtractValue(value, unit.name().toLowerCase(Locale.ROOT)).fromDate(dateReference()), timezone);
}
+ /**
+ * Creates new {@link AggregationExpression} that truncates a date to the given {@literal unit}.
+ *
+ * @param unit the unit of measure. Must not be {@literal null}.
+ * @return new instance of {@link DateTrunc}.
+ * @since 4.0
+ */
+ public DateTrunc truncate(String unit) {
+
+ Assert.notNull(unit, "TemporalUnit must not be null");
+ return applyTimezone(DateTrunc.truncateValue(dateReference()).to(unit), timezone);
+ }
+
+ /**
+ * Creates new {@link AggregationExpression} that truncates a date to the given {@literal unit}.
+ *
+ * @param unit the unit of measure. Must not be {@literal null}.
+ * @return new instance of {@link DateTrunc}.
+ * @since 4.0
+ */
+ public DateTrunc truncate(TemporalUnit unit) {
+
+ Assert.notNull(unit, "TemporalUnit must not be null");
+ return truncate(unit.name().toLowerCase(Locale.ROOT));
+ }
+
/**
* Creates new {@link AggregationExpression} that returns the day of the year for a date as a number between 1 and
* 366.
@@ -3027,6 +3053,135 @@ protected String getMongoMethod() {
}
}
+ /**
+ * {@link AggregationExpression} for {@code $dateTrunc}.
+ * NOTE: Requires MongoDB 5.0 or later.
+ *
+ * @author Christoph Strobl
+ * @since 4.0
+ */
+ public static class DateTrunc extends TimezonedDateAggregationExpression {
+
+ private DateTrunc(Object value) {
+ super(value);
+ }
+
+ /**
+ * Truncates the date value of computed by the given {@link AggregationExpression}.
+ *
+ * @param expression must not be {@literal null}.
+ * @return new instance of {@link DateTrunc}.
+ */
+ public static DateTrunc truncateValueOf(AggregationExpression expression) {
+ return truncateValue(expression);
+ }
+
+ /**
+ * Truncates the date value of the referenced {@literal field}.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return new instance of {@link DateTrunc}.
+ */
+ public static DateTrunc truncateValueOf(String fieldReference) {
+ return truncateValue(Fields.field(fieldReference));
+ }
+
+ /**
+ * Truncates the date value.
+ *
+ * @param value must not be {@literal null}.
+ * @return new instance of {@link DateTrunc}.
+ */
+ public static DateTrunc truncateValue(Object value) {
+ return new DateTrunc(Collections.singletonMap("date", value));
+ }
+
+ /**
+ * Define the unit of time.
+ *
+ * @param unit must not be {@literal null}.
+ * @return new instance of {@link DateTrunc}.
+ */
+ public DateTrunc to(String unit) {
+ return new DateTrunc(append("unit", unit));
+ }
+
+ /**
+ * Define the unit of time via an {@link AggregationExpression}.
+ *
+ * @param unit must not be {@literal null}.
+ * @return new instance of {@link DateTrunc}.
+ */
+ public DateTrunc to(AggregationExpression unit) {
+ return new DateTrunc(append("unit", unit));
+ }
+
+ /**
+ * Define the weeks starting day if {@link #to(String)} resolves to {@literal week}.
+ *
+ * @param day must not be {@literal null}.
+ * @return new instance of {@link DateTrunc}.
+ */
+ public DateTrunc startOfWeek(java.time.DayOfWeek day) {
+ return startOfWeek(day.name().toLowerCase(Locale.US));
+ }
+
+ /**
+ * Define the weeks starting day if {@link #to(String)} resolves to {@literal week}.
+ *
+ * @param day must not be {@literal null}.
+ * @return new instance of {@link DateTrunc}.
+ */
+ public DateTrunc startOfWeek(String day) {
+ return new DateTrunc(append("startOfWeek", day));
+ }
+
+ /**
+ * Define the numeric time value.
+ *
+ * @param binSize must not be {@literal null}.
+ * @return new instance of {@link DateTrunc}.
+ */
+ public DateTrunc binSize(int binSize) {
+ return binSize((Object) binSize);
+ }
+
+ /**
+ * Define the numeric time value via an {@link AggregationExpression}.
+ *
+ * @param expression must not be {@literal null}.
+ * @return new instance of {@link DateTrunc}.
+ */
+ public DateTrunc binSize(AggregationExpression expression) {
+ return binSize((Object) expression);
+ }
+
+ /**
+ * Define the numeric time value.
+ *
+ * @param binSize must not be {@literal null}.
+ * @return new instance of {@link DateTrunc}.
+ */
+ public DateTrunc binSize(Object binSize) {
+ return new DateTrunc(append("binSize", binSize));
+ }
+
+ /**
+ * Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
+ *
+ * @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead.
+ * @return new instance of {@link DateTrunc}.
+ */
+ public DateTrunc withTimezone(Timezone timezone) {
+ return new DateTrunc(appendTimezone(argumentMap(), timezone));
+ }
+
+ @Override
+ protected String getMongoMethod() {
+ return "$dateTrunc";
+ }
+ }
+
/**
* Interface defining a temporal unit for date operators.
*
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java
index 60f9fba00e..23e558a1cf 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java
@@ -165,6 +165,8 @@ public class MethodReferenceNode extends ExpressionNode {
mapArgRef().forOperator("$dateSubtract").mappingParametersTo("startDate", "unit", "amount", "timezone"));
map.put("dateDiff", mapArgRef().forOperator("$dateDiff").mappingParametersTo("startDate", "endDate", "unit",
"timezone", "startOfWeek"));
+ map.put("dateTrunc",
+ mapArgRef().forOperator("$dateTrunc").mappingParametersTo("date", "unit", "binSize", "startOfWeek", "timezone"));
map.put("dayOfYear", singleArgRef().forOperator("$dayOfYear"));
map.put("dayOfMonth", singleArgRef().forOperator("$dayOfMonth"));
map.put("dayOfWeek", singleArgRef().forOperator("$dayOfWeek"));
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/DateOperatorsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/DateOperatorsUnitTests.java
index 4f4c2f36e3..f0830acd1c 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/DateOperatorsUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/DateOperatorsUnitTests.java
@@ -17,6 +17,7 @@
import static org.springframework.data.mongodb.test.util.Assertions.*;
+import java.time.DayOfWeek;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
@@ -24,6 +25,7 @@
import org.junit.jupiter.api.Test;
+import org.springframework.data.mongodb.core.aggregation.DateOperators.TemporalUnit;
import org.springframework.data.mongodb.core.aggregation.DateOperators.Timezone;
/**
@@ -102,4 +104,18 @@ void rendersTimezoneFromTimeZoneId() {
void rendersTimezoneFromZoneId() {
assertThat(DateOperators.Timezone.fromZone(ZoneId.of("America/Chicago")).getValue()).isEqualTo("America/Chicago");
}
+
+ @Test // GH-4139
+ void rendersDateTrunc() {
+
+ assertThat(DateOperators.dateOf("purchaseDate").truncate("week").binSize(2).startOfWeek(DayOfWeek.MONDAY).toDocument(Aggregation.DEFAULT_CONTEXT))
+ .isEqualTo("{ $dateTrunc: { date: \"$purchaseDate\", unit: \"week\", binSize: 2, startOfWeek : \"monday\" } }");
+ }
+
+ @Test // GH-4139
+ void rendersDateTruncWithTimezone() {
+
+ assertThat(DateOperators.zonedDateOf("purchaseDate", Timezone.valueOf("America/Chicago")).truncate("week").binSize(2).startOfWeek(DayOfWeek.MONDAY).toDocument(Aggregation.DEFAULT_CONTEXT))
+ .isEqualTo("{ $dateTrunc: { date: \"$purchaseDate\", unit: \"week\", binSize: 2, startOfWeek : \"monday\", timezone : \"America/Chicago\" } }");
+ }
}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java
index 3c0456f63c..b80963828c 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java
@@ -1220,6 +1220,11 @@ void shouldRenderMinN() {
assertThat(transform("minN(3, \"$score\")")).isEqualTo("{ $minN : { n : 3, input : \"$score\" }}");
}
+ @Test // GH-4139
+ void shouldRenderDateTrunc() {
+ assertThat(transform("dateTrunc(purchaseDate, \"week\", 2, \"monday\")")).isEqualTo("{ $dateTrunc : { date : \"$purchaseDate\", unit : \"week\", binSize : 2, startOfWeek : \"monday\" }}");
+ }
+
private Document transform(String expression, Object... params) {
return (Document) transformer.transform(expression, Aggregation.DEFAULT_CONTEXT, params);
}