Skip to content

Commit

Permalink
Add support for $dateTrunc aggregation operator.
Browse files Browse the repository at this point in the history
See #4139
Original pull request: #4182.
  • Loading branch information
christophstrobl authored and mp911de committed Oct 12, 2022
1 parent db12c4b commit dec7c12
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 0 deletions.
Expand Up @@ -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.
Expand Down Expand Up @@ -3027,6 +3053,135 @@ protected String getMongoMethod() {
}
}

/**
* {@link AggregationExpression} for {@code $dateTrunc}.<br />
* <strong>NOTE:</strong> 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.
*
Expand Down
Expand Up @@ -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"));
Expand Down
Expand Up @@ -17,13 +17,15 @@

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;
import java.util.TimeZone;

import org.junit.jupiter.api.Test;

import org.springframework.data.mongodb.core.aggregation.DateOperators.TemporalUnit;
import org.springframework.data.mongodb.core.aggregation.DateOperators.Timezone;

/**
Expand Down Expand Up @@ -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\" } }");
}
}
Expand Up @@ -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);
}
Expand Down

0 comments on commit dec7c12

Please sign in to comment.