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); }