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 b39c9725f..41968b441 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 @@ -449,6 +449,95 @@ public class DateOperators { timezone); } + /** + * Creates new {@link AggregationExpression} that subtracts the value of the given {@link AggregationExpression + * expression} (in {@literal units}). + * + * @param expression must not be {@literal null}. + * @param unit the unit of measure. Must not be {@literal null}. + * @return new instance of {@link DateSubtract}. + * @since 4.0 + */ + public DateSubtract subtractValueOf(AggregationExpression expression, String unit) { + return applyTimezone(DateSubtract.subtractValueOf(expression, unit).fromDate(dateReference()), timezone); + } + + /** + * Creates new {@link AggregationExpression} that subtracts the value of the given {@link AggregationExpression + * expression} (in {@literal units}). + * + * @param expression must not be {@literal null}. + * @param unit the unit of measure. Must not be {@literal null}. + * @return new instance of {@link DateSubtract}. + * @since 4.0 + */ + public DateSubtract subtractValueOf(AggregationExpression expression, TemporalUnit unit) { + + Assert.notNull(unit, "TemporalUnit must not be null"); + return applyTimezone( + DateSubtract.subtractValueOf(expression, unit.name().toLowerCase(Locale.ROOT)).fromDate(dateReference()), + timezone); + } + + /** + * Creates new {@link AggregationExpression} that subtracts the value stored at the given {@literal field} (in + * {@literal units}). + * + * @param fieldReference must not be {@literal null}. + * @param unit the unit of measure. Must not be {@literal null}. + * @return new instance of {@link DateSubtract}. + * @since 4.0 + */ + public DateSubtract subtractValueOf(String fieldReference, String unit) { + return applyTimezone(DateSubtract.subtractValueOf(fieldReference, unit).fromDate(dateReference()), timezone); + } + + /** + * Creates new {@link AggregationExpression} that subtracts the value stored at the given {@literal field} (in + * {@literal units}). + * + * @param fieldReference must not be {@literal null}. + * @param unit the unit of measure. Must not be {@literal null}. + * @return new instance of {@link DateSubtract}. + * @since 4.0 + */ + public DateSubtract subtractValueOf(String fieldReference, TemporalUnit unit) { + + Assert.notNull(unit, "TemporalUnit must not be null"); + + return applyTimezone( + DateSubtract.subtractValueOf(fieldReference, unit.name().toLowerCase(Locale.ROOT)).fromDate(dateReference()), + timezone); + } + + /** + * Creates new {@link AggregationExpression} that subtracts the given value (in {@literal units}). + * + * @param value must not be {@literal null}. + * @param unit the unit of measure. Must not be {@literal null}. + * @return new instance of {@link DateSubtract}. + * @since 4.0 + */ + public DateSubtract subtract(Object value, String unit) { + return applyTimezone(DateSubtract.subtractValue(value, unit).fromDate(dateReference()), timezone); + } + + /** + * Creates new {@link AggregationExpression} that subtracts the given value (in {@literal units}). + * + * @param value must not be {@literal null}. + * @param unit the unit of measure. Must not be {@literal null}. + * @return new instance of {@link DateSubtract}. + * @since 4.0 + */ + public DateSubtract subtract(Object value, TemporalUnit unit) { + + Assert.notNull(unit, "TemporalUnit must not be null"); + + return applyTimezone( + DateSubtract.subtractValue(value, unit.name().toLowerCase(Locale.ROOT)).fromDate(dateReference()), timezone); + } + /** * Creates new {@link AggregationExpression} that returns the day of the year for a date as a number between 1 and * 366. @@ -2733,6 +2822,103 @@ public class DateOperators { } } + /** + * {@link AggregationExpression} for {@code $dateSubtract}.
+ * NOTE: Requires MongoDB 5.0 or later. + * + * @author Christoph Strobl + * @since 4.0 + */ + public static class DateSubtract extends TimezonedDateAggregationExpression { + + private DateSubtract(Object value) { + super(value); + } + + /** + * Subtract the number of {@literal units} of the result of the given {@link AggregationExpression expression} from + * a {@link #fromDate(Object) start date}. + * + * @param expression must not be {@literal null}. + * @param unit must not be {@literal null}. + * @return new instance of {@link DateSubtract}. + */ + public static DateSubtract subtractValueOf(AggregationExpression expression, String unit) { + return subtractValue(expression, unit); + } + + /** + * Subtract the number of {@literal units} from a {@literal field} from a {@link #fromDate(Object) start date}. + * + * @param fieldReference must not be {@literal null}. + * @param unit must not be {@literal null}. + * @return new instance of {@link DateSubtract}. + */ + public static DateSubtract subtractValueOf(String fieldReference, String unit) { + return subtractValue(Fields.field(fieldReference), unit); + } + + /** + * Subtract the number of {@literal units} from a {@link #fromDate(Object) start date}. + * + * @param value must not be {@literal null}. + * @param unit must not be {@literal null}. + * @return new instance of {@link DateSubtract}. + */ + public static DateSubtract subtractValue(Object value, String unit) { + + Map args = new HashMap<>(); + args.put("unit", unit); + args.put("amount", value); + return new DateSubtract(args); + } + + /** + * Define the start date, in UTC, for the subtraction operation. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link DateSubtract}. + */ + public DateSubtract fromDateOf(AggregationExpression expression) { + return fromDate(expression); + } + + /** + * Define the start date, in UTC, for the subtraction operation. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link DateSubtract}. + */ + public DateSubtract fromDateOf(String fieldReference) { + return fromDate(Fields.field(fieldReference)); + } + + /** + * Define the start date, in UTC, for the subtraction operation. + * + * @param dateExpression anything that evaluates to a valid date. Must not be {@literal null}. + * @return new instance of {@link DateSubtract}. + */ + public DateSubtract fromDate(Object dateExpression) { + return new DateSubtract(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 DateSubtract}. + */ + public DateSubtract withTimezone(Timezone timezone) { + return new DateSubtract(appendTimezone(argumentMap(), timezone)); + } + + @Override + protected String getMongoMethod() { + return "$dateSubtract"; + } + } + /** * {@link AggregationExpression} for {@code $dateDiff}.
* NOTE: Requires MongoDB 5.0 or later. 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 2925e87e0..60f9fba00 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 @@ -161,6 +161,8 @@ public class MethodReferenceNode extends ExpressionNode { // DATE OPERATORS map.put("dateAdd", mapArgRef().forOperator("$dateAdd").mappingParametersTo("startDate", "unit", "amount", "timezone")); + map.put("dateSubtract", + mapArgRef().forOperator("$dateSubtract").mappingParametersTo("startDate", "unit", "amount", "timezone")); map.put("dateDiff", mapArgRef().forOperator("$dateDiff").mappingParametersTo("startDate", "endDate", "unit", "timezone", "startOfWeek")); map.put("dayOfYear", singleArgRef().forOperator("$dayOfYear")); 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 c9e73b5a9..4f4c2f36e 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 @@ -49,6 +49,21 @@ class DateOperatorsUnitTests { "{ $dateAdd: { startDate: \"$purchaseDate\", unit: \"day\", amount: 3, timezone : \"America/Chicago\" } }"); } + @Test // GH-4139 + void rendersDateSubtract() { + + assertThat(DateOperators.dateOf("purchaseDate").subtract(3, "day").toDocument(Aggregation.DEFAULT_CONTEXT)) + .isEqualTo("{ $dateSubtract: { startDate: \"$purchaseDate\", unit: \"day\", amount: 3 } }"); + } + + @Test // GH-4139 + void rendersDateSubtractWithTimezone() { + + assertThat(DateOperators.zonedDateOf("purchaseDate", Timezone.valueOf("America/Chicago")).subtract(3, "day") + .toDocument(Aggregation.DEFAULT_CONTEXT)).isEqualTo( + "{ $dateSubtract: { startDate: \"$purchaseDate\", unit: \"day\", amount: 3, timezone : \"America/Chicago\" } }"); + } + @Test // GH-3713 void rendersDateDiff() { 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 06f31b1c1..3c0456f63 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 @@ -1163,6 +1163,12 @@ public class SpelExpressionTransformerUnitTests { .isEqualTo("{ $dateAdd: { startDate: \"$purchaseDate\", unit: \"day\", amount: 3 } }"); } + @Test // GH-4139 + void shouldRenderDateSubtract() { + assertThat(transform("dateSubtract(purchaseDate, 'day', 3)")) + .isEqualTo("{ $dateSubtract: { startDate: \"$purchaseDate\", unit: \"day\", amount: 3 } }"); + } + @Test // GH-3713 void shouldRenderDateDiff() { assertThat(transform("dateDiff(purchaseDate, delivered, 'day')"))