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')"))