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 f7abf88d7..a9b1d411c 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 @@ -340,6 +340,42 @@ public class DateOperators { return applyTimezone(DayOfWeek.dayOfWeek(dateReference()), timezone); } + /** + * Creates new {@link AggregationExpression} that calculates the difference (in {@literal units) to the date + * computed by the given {@link AggregationExpression expression}. @param expression must not be {@literal null}. + * + * @param unit the unit of measure. Must not be {@literal null}. + * @return new instance of {@link DateAdd}. + * @since 3.3 + */ + public DateDiff diffValueOf(AggregationExpression expression, String unit) { + return applyTimezone(DateDiff.diffValueOf(expression, unit).toDate(dateReference()), timezone); + } + + /** + * Creates new {@link AggregationExpression} that calculates the difference (in {@literal units) to the date stored + * at the given {@literal field}. @param expression must not be {@literal null}. + * + * @param unit the unit of measure. Must not be {@literal null}. + * @return new instance of {@link DateAdd}. + * @since 3.3 + */ + public DateDiff diffValueOf(String fieldReference, String unit) { + return applyTimezone(DateDiff.diffValueOf(fieldReference, unit).toDate(dateReference()), timezone); + } + + /** + * Creates new {@link AggregationExpression} that calculates the difference (in {@literal units) to the date given + * {@literal value}. @param value anything the resolves to a valid date. Must not be {@literal null}. + * + * @param unit the unit of measure. Must not be {@literal null}. + * @return new instance of {@link DateAdd}. + * @since 3.3 + */ + public DateDiff diff(Object value, String unit) { + return applyTimezone(DateDiff.diffValue(value, unit).toDate(dateReference()), timezone); + } + /** * Creates new {@link AggregationExpression} that returns the year portion of a date. * @@ -2550,6 +2586,114 @@ public class DateOperators { } } + /** + * {@link AggregationExpression} for {@code $dateDiff}.
+ * NOTE: Requires MongoDB 5.0 or later. + * + * @author Christoph Strobl + * @since 3.3 + */ + public static class DateDiff extends TimezonedDateAggregationExpression { + + private DateDiff(Object value) { + super(value); + } + + /** + * Add the number of {@literal units} of the result of the given {@link AggregationExpression expression} to a + * {@link #toDate(Object) start date}. + * + * @param expression must not be {@literal null}. + * @param unit must not be {@literal null}. + * @return new instance of {@link DateAdd}. + */ + public static DateDiff diffValueOf(AggregationExpression expression, String unit) { + return diffValue(expression, unit); + } + + /** + * Add the number of {@literal units} from a {@literal field} to a {@link #toDate(Object) start date}. + * + * @param fieldReference must not be {@literal null}. + * @param unit must not be {@literal null}. + * @return new instance of {@link DateAdd}. + */ + public static DateDiff diffValueOf(String fieldReference, String unit) { + return diffValue(Fields.field(fieldReference), unit); + } + + /** + * Add the number of {@literal units} to a {@link #toDate(Object) start date}. + * + * @param value must not be {@literal null}. + * @param unit must not be {@literal null}. + * @return new instance of {@link DateAdd}. + */ + public static DateDiff diffValue(Object value, String unit) { + + Map args = new HashMap<>(); + args.put("unit", unit); + args.put("endDate", value); + return new DateDiff(args); + } + + /** + * Define the start date, in UTC, for the addition operation. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link DateAdd}. + */ + public DateDiff toDateOf(AggregationExpression expression) { + return toDate(expression); + } + + /** + * Define the start date, in UTC, for the addition operation. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link DateAdd}. + */ + public DateDiff toDateOf(String fieldReference) { + return toDate(Fields.field(fieldReference)); + } + + /** + * Define the start date, in UTC, for the addition operation. + * + * @param dateExpression anything that evaluates to a valid date. Must not be {@literal null}. + * @return new instance of {@link DateAdd}. + */ + public DateDiff toDate(Object dateExpression) { + return new DateDiff(append("startDate", dateExpression)); + } + + /** + * 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 DateAdd}. + */ + public DateDiff withTimezone(Timezone timezone) { + return new DateDiff(appendTimezone(argumentMap(), timezone)); + } + + /** + * Set the start day of the week if the unit if measure is set to {@literal week}. Uses {@literal Sunday} by + * default. + * + * @param day must not be {@literal null}. + * @return new instance of {@link DateDiff}. + */ + public DateDiff startOfWeek(Object day) { + return new DateDiff(append("startOfWeek", day)); + } + + @Override + protected String getMongoMethod() { + return "$dateDiff"; + } + } + @SuppressWarnings("unchecked") private static T applyTimezone(T instance, Timezone timezone) { return !ObjectUtils.nullSafeEquals(Timezone.none(), timezone) && !instance.hasTimezone() 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 6b4daa15b..6a60a7df1 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 @@ -145,6 +145,7 @@ public class MethodReferenceNode extends ExpressionNode { // DATE OPERATORS map.put("dateAdd", mapArgRef().forOperator("$dateAdd").mappingParametersTo("startDate", "unit", "amount", "timezone")); + map.put("dateDiff", mapArgRef().forOperator("$dateDiff").mappingParametersTo("startDate", "endDate", "unit","timezone", "startOfWeek")); 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 036edfdce..ab975b852 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 @@ -40,5 +40,21 @@ class DateOperatorsUnitTests { .toDocument(Aggregation.DEFAULT_CONTEXT)).isEqualTo(Document.parse( "{ $dateAdd: { startDate: \"$purchaseDate\", unit: \"day\", amount: 3, timezone : \"America/Chicago\" } }")); } + + @Test // GH-3713 + void rendersDateDiff() { + + assertThat( + DateOperators.dateOf("purchaseDate").diffValueOf("delivered", "day").toDocument(Aggregation.DEFAULT_CONTEXT)) + .isEqualTo(Document + .parse("{ $dateDiff: { startDate: \"$purchaseDate\", endDate: \"$delivered\", unit: \"day\" } }")); + } + + @Test // GH-3713 + void rendersDateDiffWithTimezone() { + + assertThat(DateOperators.dateOf("purchaseDate").withTimezone(Timezone.valueOf("America/Chicago")) + .diffValueOf("delivered", "day").toDocument(Aggregation.DEFAULT_CONTEXT)).isEqualTo(Document.parse( + "{ $dateDiff: { startDate: \"$purchaseDate\", endDate: \"$delivered\", unit: \"day\", 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 337d61f98..33edab1d5 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 @@ -1044,6 +1044,11 @@ public class SpelExpressionTransformerUnitTests { assertThat(transform("dateAdd(purchaseDate, 'day', 3)")).isEqualTo(Document.parse("{ $dateAdd: { startDate: \"$purchaseDate\", unit: \"day\", amount: 3 } }")); } + @Test // GH-3713 + void shouldRenderDateDiff() { + assertThat(transform("dateDiff(purchaseDate, delivered, 'day')")).isEqualTo(Document.parse("{ $dateDiff: { startDate: \"$purchaseDate\", endDate: \"$delivered\", unit: \"day\" } }")); + } + private Object transform(String expression, Object... params) { Object result = transformer.transform(expression, Aggregation.DEFAULT_CONTEXT, params); return result == null ? null : (!(result instanceof org.bson.Document) ? result.toString() : result);