Skip to content

Commit afef243

Browse files
christophstroblmp911de
authored andcommitted
Add support for $dateAdd aggregation operator.
Closes: #3713 Original pull request: #3748.
1 parent 869b887 commit afef243

File tree

4 files changed

+189
-6
lines changed

4 files changed

+189
-6
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java

+138-6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.mongodb.core.aggregation;
1717

1818
import java.util.Collections;
19+
import java.util.HashMap;
1920
import java.util.LinkedHashMap;
2021
import java.util.Map;
2122

@@ -156,7 +157,7 @@ public static Timezone none() {
156157
* representing an Olson Timezone Identifier or UTC Offset.
157158
*
158159
* @param value the plain timezone {@link String}, a {@link Field} holding the timezone or an
159-
* {@link AggregationExpression} resulting in the timezone.
160+
* {@link AggregationExpression} resulting in the timezone.
160161
* @return new instance of {@link Timezone}.
161162
*/
162163
public static Timezone valueOf(Object value) {
@@ -274,6 +275,41 @@ public DateOperatorFactory withTimezone(Timezone timezone) {
274275
return new DateOperatorFactory(fieldReference, expression, dateValue, timezone);
275276
}
276277

278+
/**
279+
* Creates new {@link AggregationExpression} that adds the value of the given {@link AggregationExpression
280+
* expression} (in {@literal units). @param expression must not be {@literal null}.
281+
*
282+
* @param unit the unit of measure. Must not be {@literal null}.
283+
* @return new instance of {@link DateAdd}.
284+
* @since 3.3
285+
*/
286+
public DateAdd addValueOf(AggregationExpression expression, String unit) {
287+
return applyTimezone(DateAdd.addValueOf(expression, unit).toDate(dateReference()), timezone);
288+
}
289+
290+
/**
291+
* Creates new {@link AggregationExpression} that adds the value stored at the given {@literal field} (in
292+
* {@literal units). @param fieldReference must not be {@literal null}.
293+
*
294+
* @param unit the unit of measure. Must not be {@literal null}.
295+
* @return new instance of {@link DateAdd}.
296+
* @since 3.3
297+
*/
298+
public DateAdd addValueOf(String fieldReference, String unit) {
299+
return applyTimezone(DateAdd.addValueOf(fieldReference, unit).toDate(dateReference()), timezone);
300+
}
301+
302+
/**
303+
* Creates new {@link AggregationExpression} that adds the given value (in {@literal units). @param value must not
304+
* be {@literal null}. @param unit the unit of measure. Must not be {@literal null}.
305+
*
306+
* @return
307+
* @since 3.3 new instance of {@link DateAdd}.
308+
*/
309+
public DateAdd add(Object value, String unit) {
310+
return applyTimezone(DateAdd.addValue(value, unit).toDate(dateReference()), timezone);
311+
}
312+
277313
/**
278314
* Creates new {@link AggregationExpression} that returns the day of the year for a date as a number between 1 and
279315
* 366.
@@ -1480,7 +1516,6 @@ protected java.util.Map<String, Object> append(String key, Object value) {
14801516
} else {
14811517
clone.put("timezone", ((Timezone) value).value);
14821518
}
1483-
14841519
} else {
14851520
clone.put(key, value);
14861521
}
@@ -1911,7 +1946,7 @@ default T millisecondOf(AggregationExpression expression) {
19111946
* @author Matt Morrissette
19121947
* @author Christoph Strobl
19131948
* @see <a href=
1914-
* "https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromParts/">https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromParts/</a>
1949+
* "https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromParts/">https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromParts/</a>
19151950
* @since 2.1
19161951
*/
19171952
public static class DateFromParts extends TimezonedDateAggregationExpression implements DateParts<DateFromParts> {
@@ -2086,7 +2121,7 @@ default DateFromParts yearOf(AggregationExpression expression) {
20862121
* @author Matt Morrissette
20872122
* @author Christoph Strobl
20882123
* @see <a href=
2089-
* "https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromParts/">https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromParts/</a>
2124+
* "https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromParts/">https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromParts/</a>
20902125
* @since 2.1
20912126
*/
20922127
public static class IsoDateFromParts extends TimezonedDateAggregationExpression
@@ -2262,7 +2297,7 @@ default IsoDateFromParts isoWeekYearOf(AggregationExpression expression) {
22622297
* @author Matt Morrissette
22632298
* @author Christoph Strobl
22642299
* @see <a href=
2265-
* "https://docs.mongodb.com/manual/reference/operator/aggregation/dateToParts/">https://docs.mongodb.com/manual/reference/operator/aggregation/dateToParts/</a>
2300+
* "https://docs.mongodb.com/manual/reference/operator/aggregation/dateToParts/">https://docs.mongodb.com/manual/reference/operator/aggregation/dateToParts/</a>
22662301
* @since 2.1
22672302
*/
22682303
public static class DateToParts extends TimezonedDateAggregationExpression {
@@ -2343,7 +2378,7 @@ protected String getMongoMethod() {
23432378
* @author Matt Morrissette
23442379
* @author Christoph Strobl
23452380
* @see <a href=
2346-
* "https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromString/">https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromString/</a>
2381+
* "https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromString/">https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromString/</a>
23472382
* @since 2.1
23482383
*/
23492384
public static class DateFromString extends TimezonedDateAggregationExpression {
@@ -2418,6 +2453,103 @@ protected String getMongoMethod() {
24182453
}
24192454
}
24202455

2456+
/**
2457+
* {@link AggregationExpression} for {@code $dateAdd}.<br />
2458+
* <strong>NOTE:</strong> Requires MongoDB 5.0 or later.
2459+
*
2460+
* @author Christoph Strobl
2461+
* @since 3.3
2462+
*/
2463+
public static class DateAdd extends TimezonedDateAggregationExpression {
2464+
2465+
private DateAdd(Object value) {
2466+
super(value);
2467+
}
2468+
2469+
/**
2470+
* Add the number of {@literal units} of the result of the given {@link AggregationExpression expression} to a
2471+
* {@link #toDate(Object) start date}.
2472+
*
2473+
* @param expression must not be {@literal null}.
2474+
* @param unit must not be {@literal null}.
2475+
* @return new instance of {@link DateAdd}.
2476+
*/
2477+
public static DateAdd addValueOf(AggregationExpression expression, String unit) {
2478+
return addValue(expression, unit);
2479+
}
2480+
2481+
/**
2482+
* Add the number of {@literal units} from a {@literal field} to a {@link #toDate(Object) start date}.
2483+
*
2484+
* @param fieldReference must not be {@literal null}.
2485+
* @param unit must not be {@literal null}.
2486+
* @return new instance of {@link DateAdd}.
2487+
*/
2488+
public static DateAdd addValueOf(String fieldReference, String unit) {
2489+
return addValue(Fields.field(fieldReference), unit);
2490+
}
2491+
2492+
/**
2493+
* Add the number of {@literal units} to a {@link #toDate(Object) start date}.
2494+
*
2495+
* @param value must not be {@literal null}.
2496+
* @param unit must not be {@literal null}.
2497+
* @return new instance of {@link DateAdd}.
2498+
*/
2499+
public static DateAdd addValue(Object value, String unit) {
2500+
2501+
Map<String, Object> args = new HashMap<>();
2502+
args.put("unit", unit);
2503+
args.put("amount", value);
2504+
return new DateAdd(args);
2505+
}
2506+
2507+
/**
2508+
* Define the start date, in UTC, for the addition operation.
2509+
*
2510+
* @param expression must not be {@literal null}.
2511+
* @return new instance of {@link DateAdd}.
2512+
*/
2513+
public DateAdd toDateOf(AggregationExpression expression) {
2514+
return toDate(expression);
2515+
}
2516+
2517+
/**
2518+
* Define the start date, in UTC, for the addition operation.
2519+
*
2520+
* @param fieldReference must not be {@literal null}.
2521+
* @return new instance of {@link DateAdd}.
2522+
*/
2523+
public DateAdd toDateOf(String fieldReference) {
2524+
return toDate(Fields.field(fieldReference));
2525+
}
2526+
2527+
/**
2528+
* Define the start date, in UTC, for the addition operation.
2529+
*
2530+
* @param dateExpression anything that evaluates to a valid date. Must not be {@literal null}.
2531+
* @return new instance of {@link DateAdd}.
2532+
*/
2533+
public DateAdd toDate(Object dateExpression) {
2534+
return new DateAdd(append("startDate", dateExpression));
2535+
}
2536+
2537+
/**
2538+
* Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
2539+
*
2540+
* @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead.
2541+
* @return new instance of {@link DateAdd}.
2542+
*/
2543+
public DateAdd withTimezone(Timezone timezone) {
2544+
return new DateAdd(appendTimezone(argumentMap(), timezone));
2545+
}
2546+
2547+
@Override
2548+
protected String getMongoMethod() {
2549+
return "$dateAdd";
2550+
}
2551+
}
2552+
24212553
@SuppressWarnings("unchecked")
24222554
private static <T extends TimezonedDateAggregationExpression> T applyTimezone(T instance, Timezone timezone) {
24232555
return !ObjectUtils.nullSafeEquals(Timezone.none(), timezone) && !instance.hasTimezone()

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java

+1
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ public class MethodReferenceNode extends ExpressionNode {
144144
map.put("literal", singleArgRef().forOperator("$literal"));
145145

146146
// DATE OPERATORS
147+
map.put("dateAdd", mapArgRef().forOperator("$dateAdd").mappingParametersTo("startDate", "unit", "amount", "timezone"));
147148
map.put("dayOfYear", singleArgRef().forOperator("$dayOfYear"));
148149
map.put("dayOfMonth", singleArgRef().forOperator("$dayOfMonth"));
149150
map.put("dayOfWeek", singleArgRef().forOperator("$dayOfWeek"));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2021. the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core.aggregation;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
20+
import org.bson.Document;
21+
import org.junit.jupiter.api.Test;
22+
import org.springframework.data.mongodb.core.aggregation.DateOperators.Timezone;
23+
24+
/**
25+
* @author Christoph Strobl
26+
*/
27+
class DateOperatorsUnitTests {
28+
29+
@Test // GH-3713
30+
void rendersDateAdd() {
31+
32+
assertThat(DateOperators.dateOf("purchaseDate").add(3, "day").toDocument(Aggregation.DEFAULT_CONTEXT))
33+
.isEqualTo(Document.parse("{ $dateAdd: { startDate: \"$purchaseDate\", unit: \"day\", amount: 3 } }"));
34+
}
35+
36+
@Test // GH-3713
37+
void rendersDateAddWithTimezone() {
38+
39+
assertThat(DateOperators.dateOf("purchaseDate").withTimezone(Timezone.valueOf("America/Chicago")).add(3, "day")
40+
.toDocument(Aggregation.DEFAULT_CONTEXT)).isEqualTo(Document.parse(
41+
"{ $dateAdd: { startDate: \"$purchaseDate\", unit: \"day\", amount: 3, timezone : \"America/Chicago\" } }"));
42+
}
43+
}
44+
}

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java

+6
Original file line numberDiff line numberDiff line change
@@ -1039,6 +1039,12 @@ private Document transform(String expression, Object... params) {
10391039
}
10401040

10411041
private Object transformValue(String expression, Object... params) {
1042+
@Test // GH-3713
1043+
void shouldRenderDateAdd() {
1044+
assertThat(transform("dateAdd(purchaseDate, 'day', 3)")).isEqualTo(Document.parse("{ $dateAdd: { startDate: \"$purchaseDate\", unit: \"day\", amount: 3 } }"));
1045+
}
1046+
1047+
private Object transform(String expression, Object... params) {
10421048
Object result = transformer.transform(expression, Aggregation.DEFAULT_CONTEXT, params);
10431049
return result == null ? null : (!(result instanceof org.bson.Document) ? result.toString() : result);
10441050
}

0 commit comments

Comments
 (0)