Skip to content

Commit 24171b3

Browse files
committed
Polishing.
Introduce factory methods to convert TimeZone/ZoneId/ZoneOffset into Mongo Timezone. Introduce TemporalUnit abstraction and converters to convert ChronoUnit and TimeUnit into TemporalUnit for date operators accepting a unit parameter. See #3713 Original pull request: #3748.
1 parent 456c1ad commit 24171b3

File tree

5 files changed

+376
-42
lines changed

5 files changed

+376
-42
lines changed

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

+268-26
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,16 @@
1515
*/
1616
package org.springframework.data.mongodb.core.aggregation;
1717

18+
import java.time.ZoneId;
19+
import java.time.ZoneOffset;
20+
import java.time.temporal.ChronoUnit;
1821
import java.util.Collections;
1922
import java.util.HashMap;
2023
import java.util.LinkedHashMap;
24+
import java.util.Locale;
2125
import java.util.Map;
26+
import java.util.TimeZone;
27+
import java.util.concurrent.TimeUnit;
2228

2329
import org.springframework.lang.Nullable;
2430
import org.springframework.util.Assert;
@@ -157,6 +163,7 @@ public static DateFromString dateFromString(String value) {
157163
* <strong>NOTE: </strong>Support for timezones in aggregations Requires MongoDB 3.6 or later.
158164
*
159165
* @author Christoph Strobl
166+
* @author Mark Paluch
160167
* @since 2.1
161168
*/
162169
public static class Timezone {
@@ -192,6 +199,61 @@ public static Timezone valueOf(Object value) {
192199
return new Timezone(value);
193200
}
194201

202+
/**
203+
* Create a {@link Timezone} for the given {@link TimeZone} rendering the offset as UTC offset.
204+
*
205+
* @param timeZone {@link TimeZone} rendering the offset as UTC offset.
206+
* @return new instance of {@link Timezone}.
207+
* @since 3.3
208+
*/
209+
public static Timezone fromOffset(TimeZone timeZone) {
210+
211+
Assert.notNull(timeZone, "TimeZone must not be null!");
212+
213+
return fromOffset(
214+
ZoneOffset.ofTotalSeconds(Math.toIntExact(TimeUnit.MILLISECONDS.toSeconds(timeZone.getRawOffset()))));
215+
}
216+
217+
/**
218+
* Create a {@link Timezone} for the given {@link ZoneOffset} rendering the offset as UTC offset.
219+
*
220+
* @param offset {@link ZoneOffset} rendering the offset as UTC offset.
221+
* @return new instance of {@link Timezone}.
222+
* @since 3.3
223+
*/
224+
public static Timezone fromOffset(ZoneOffset offset) {
225+
226+
Assert.notNull(offset, "ZoneOffset must not be null!");
227+
return new Timezone(offset.toString());
228+
}
229+
230+
/**
231+
* Create a {@link Timezone} for the given {@link TimeZone} rendering the offset as UTC offset.
232+
*
233+
* @param timeZone {@link Timezone} rendering the offset as zone identifier.
234+
* @return new instance of {@link Timezone}.
235+
* @since 3.3
236+
*/
237+
public static Timezone fromZone(TimeZone timeZone) {
238+
239+
Assert.notNull(timeZone, "TimeZone must not be null!");
240+
241+
return valueOf(timeZone.getID());
242+
}
243+
244+
/**
245+
* Create a {@link Timezone} for the given {@link java.time.ZoneId} rendering the offset as UTC offset.
246+
*
247+
* @param zoneId {@link ZoneId} rendering the offset as zone identifier.
248+
* @return new instance of {@link Timezone}.
249+
* @since 3.3
250+
*/
251+
public static Timezone fromZone(ZoneId zoneId) {
252+
253+
Assert.notNull(zoneId, "ZoneId must not be null!");
254+
return new Timezone(zoneId.toString());
255+
}
256+
195257
/**
196258
* Create a {@link Timezone} for the {@link Field} reference holding the Olson Timezone Identifier or UTC Offset.
197259
*
@@ -212,6 +274,11 @@ public static Timezone ofField(String fieldReference) {
212274
public static Timezone ofExpression(AggregationExpression expression) {
213275
return valueOf(expression);
214276
}
277+
278+
@Nullable
279+
Object getValue() {
280+
return value;
281+
}
215282
}
216283

217284
/**
@@ -303,39 +370,87 @@ public DateOperatorFactory withTimezone(Timezone timezone) {
303370

304371
/**
305372
* Creates new {@link AggregationExpression} that adds the value of the given {@link AggregationExpression
306-
* expression} (in {@literal units). @param expression must not be {@literal null}.
307-
*
373+
* expression} (in {@literal units}).
374+
*
375+
* @param expression must not be {@literal null}.
308376
* @param unit the unit of measure. Must not be {@literal null}.
309-
* @return new instance of {@link DateAdd}.
310-
* @since 3.3
377+
* @return new instance of {@link DateAdd}. @since 3.3
311378
*/
312379
public DateAdd addValueOf(AggregationExpression expression, String unit) {
313380
return applyTimezone(DateAdd.addValueOf(expression, unit).toDate(dateReference()), timezone);
314381
}
315382

383+
/**
384+
* Creates new {@link AggregationExpression} that adds the value of the given {@link AggregationExpression
385+
* expression} (in {@literal units}).
386+
*
387+
* @param expression must not be {@literal null}.
388+
* @param unit the unit of measure. Must not be {@literal null}.
389+
* @return new instance of {@link DateAdd}. @since 3.3
390+
*/
391+
public DateAdd addValueOf(AggregationExpression expression, TemporalUnit unit) {
392+
393+
Assert.notNull(unit, "TemporalUnit must not be null");
394+
return applyTimezone(DateAdd.addValueOf(expression, unit.name().toLowerCase(Locale.ROOT)).toDate(dateReference()),
395+
timezone);
396+
}
397+
316398
/**
317399
* Creates new {@link AggregationExpression} that adds the value stored at the given {@literal field} (in
318-
* {@literal units). @param fieldReference must not be {@literal null}.
319-
*
400+
* {@literal units}).
401+
*
402+
* @param fieldReference must not be {@literal null}.
320403
* @param unit the unit of measure. Must not be {@literal null}.
321-
* @return new instance of {@link DateAdd}.
322-
* @since 3.3
404+
* @return new instance of {@link DateAdd}. @since 3.3
323405
*/
324406
public DateAdd addValueOf(String fieldReference, String unit) {
325407
return applyTimezone(DateAdd.addValueOf(fieldReference, unit).toDate(dateReference()), timezone);
326408
}
327409

328410
/**
329-
* Creates new {@link AggregationExpression} that adds the given value (in {@literal units). @param value must not
330-
* be {@literal null}. @param unit the unit of measure. Must not be {@literal null}.
331-
*
411+
* Creates new {@link AggregationExpression} that adds the value stored at the given {@literal field} (in
412+
* {@literal units}).
413+
*
414+
* @param fieldReference must not be {@literal null}.
415+
* @param unit the unit of measure. Must not be {@literal null}.
416+
* @return new instance of {@link DateAdd}. @since 3.3
417+
*/
418+
public DateAdd addValueOf(String fieldReference, TemporalUnit unit) {
419+
420+
Assert.notNull(unit, "TemporalUnit must not be null");
421+
422+
return applyTimezone(
423+
DateAdd.addValueOf(fieldReference, unit.name().toLowerCase(Locale.ROOT)).toDate(dateReference()), timezone);
424+
}
425+
426+
/**
427+
* Creates new {@link AggregationExpression} that adds the given value (in {@literal units}).
428+
*
429+
* @param value must not be {@literal null}.
430+
* @param unit the unit of measure. Must not be {@literal null}.
332431
* @return
333432
* @since 3.3 new instance of {@link DateAdd}.
334433
*/
335434
public DateAdd add(Object value, String unit) {
336435
return applyTimezone(DateAdd.addValue(value, unit).toDate(dateReference()), timezone);
337436
}
338437

438+
/**
439+
* Creates new {@link AggregationExpression} that adds the given value (in {@literal units}).
440+
*
441+
* @param value must not be {@literal null}.
442+
* @param unit the unit of measure. Must not be {@literal null}.
443+
* @return
444+
* @since 3.3 new instance of {@link DateAdd}.
445+
*/
446+
public DateAdd add(Object value, TemporalUnit unit) {
447+
448+
Assert.notNull(unit, "TemporalUnit must not be null");
449+
450+
return applyTimezone(DateAdd.addValue(value, unit.name().toLowerCase(Locale.ROOT)).toDate(dateReference()),
451+
timezone);
452+
}
453+
339454
/**
340455
* Creates new {@link AggregationExpression} that returns the day of the year for a date as a number between 1 and
341456
* 366.
@@ -367,41 +482,89 @@ public DayOfWeek dayOfWeek() {
367482
}
368483

369484
/**
370-
* Creates new {@link AggregationExpression} that calculates the difference (in {@literal units) to the date
371-
* computed by the given {@link AggregationExpression expression}. @param expression must not be {@literal null}.
372-
*
485+
* Creates new {@link AggregationExpression} that calculates the difference (in {@literal units}) to the date
486+
* computed by the given {@link AggregationExpression expression}.
487+
*
488+
* @param expression must not be {@literal null}.
373489
* @param unit the unit of measure. Must not be {@literal null}.
374-
* @return new instance of {@link DateAdd}.
375-
* @since 3.3
490+
* @return new instance of {@link DateAdd}. @since 3.3
376491
*/
377492
public DateDiff diffValueOf(AggregationExpression expression, String unit) {
378493
return applyTimezone(DateDiff.diffValueOf(expression, unit).toDate(dateReference()), timezone);
379494
}
380495

381496
/**
382-
* Creates new {@link AggregationExpression} that calculates the difference (in {@literal units) to the date stored
383-
* at the given {@literal field}. @param expression must not be {@literal null}.
384-
*
497+
* Creates new {@link AggregationExpression} that calculates the difference (in {@literal units}) to the date
498+
* computed by the given {@link AggregationExpression expression}.
499+
*
500+
* @param expression must not be {@literal null}.
385501
* @param unit the unit of measure. Must not be {@literal null}.
386-
* @return new instance of {@link DateAdd}.
387-
* @since 3.3
502+
* @return new instance of {@link DateAdd}. @since 3.3
503+
*/
504+
public DateDiff diffValueOf(AggregationExpression expression, TemporalUnit unit) {
505+
506+
Assert.notNull(unit, "TemporalUnit must not be null");
507+
508+
return applyTimezone(
509+
DateDiff.diffValueOf(expression, unit.name().toLowerCase(Locale.ROOT)).toDate(dateReference()), timezone);
510+
}
511+
512+
/**
513+
* Creates new {@link AggregationExpression} that calculates the difference (in {@literal units}) to the date stored
514+
* at the given {@literal field}.
515+
*
516+
* @param fieldReference must not be {@literal null}.
517+
* @param unit the unit of measure. Must not be {@literal null}.
518+
* @return new instance of {@link DateAdd}. @since 3.3
388519
*/
389520
public DateDiff diffValueOf(String fieldReference, String unit) {
390521
return applyTimezone(DateDiff.diffValueOf(fieldReference, unit).toDate(dateReference()), timezone);
391522
}
392523

393524
/**
394-
* Creates new {@link AggregationExpression} that calculates the difference (in {@literal units) to the date given
395-
* {@literal value}. @param value anything the resolves to a valid date. Must not be {@literal null}.
396-
*
525+
* Creates new {@link AggregationExpression} that calculates the difference (in {@literal units}) to the date stored
526+
* at the given {@literal field}.
527+
*
528+
* @param fieldReference must not be {@literal null}.
397529
* @param unit the unit of measure. Must not be {@literal null}.
398-
* @return new instance of {@link DateAdd}.
399-
* @since 3.3
530+
* @return new instance of {@link DateAdd}. @since 3.3
531+
*/
532+
public DateDiff diffValueOf(String fieldReference, TemporalUnit unit) {
533+
534+
Assert.notNull(unit, "TemporalUnit must not be null");
535+
536+
return applyTimezone(
537+
DateDiff.diffValueOf(fieldReference, unit.name().toLowerCase(Locale.ROOT)).toDate(dateReference()), timezone);
538+
}
539+
540+
/**
541+
* Creates new {@link AggregationExpression} that calculates the difference (in {@literal units}) to the date given
542+
* {@literal value}.
543+
*
544+
* @param value anything the resolves to a valid date. Must not be {@literal null}.
545+
* @param unit the unit of measure. Must not be {@literal null}.
546+
* @return new instance of {@link DateAdd}. @since 3.3
400547
*/
401548
public DateDiff diff(Object value, String unit) {
402549
return applyTimezone(DateDiff.diffValue(value, unit).toDate(dateReference()), timezone);
403550
}
404551

552+
/**
553+
* Creates new {@link AggregationExpression} that calculates the difference (in {@literal units}) to the date given
554+
* {@literal value}.
555+
*
556+
* @param value anything the resolves to a valid date. Must not be {@literal null}.
557+
* @param unit the unit of measure. Must not be {@literal null}.
558+
* @return new instance of {@link DateAdd}. @since 3.3
559+
*/
560+
public DateDiff diff(Object value, TemporalUnit unit) {
561+
562+
Assert.notNull(unit, "TemporalUnit must not be null");
563+
564+
return applyTimezone(DateDiff.diffValue(value, unit.name().toLowerCase(Locale.ROOT)).toDate(dateReference()),
565+
timezone);
566+
}
567+
405568
/**
406569
* Creates new {@link AggregationExpression} that returns the year portion of a date.
407570
*
@@ -2720,6 +2883,85 @@ protected String getMongoMethod() {
27202883
}
27212884
}
27222885

2886+
/**
2887+
* Interface defining a temporal unit for date operators.
2888+
*
2889+
* @author Mark Paluch
2890+
* @since 3.3
2891+
*/
2892+
public interface TemporalUnit {
2893+
2894+
String name();
2895+
2896+
/**
2897+
* Converts the given time unit into a {@link TemporalUnit}. Supported units are: days, hours, minutes, seconds, and
2898+
* milliseconds.
2899+
*
2900+
* @param timeUnit the time unit to convert, must not be {@literal null}.
2901+
* @return
2902+
* @throws IllegalArgumentException if the {@link TimeUnit} is {@literal null} or not supported for conversion.
2903+
*/
2904+
static TemporalUnit from(TimeUnit timeUnit) {
2905+
2906+
Assert.notNull(timeUnit, "TimeUnit must not be null");
2907+
2908+
switch (timeUnit) {
2909+
case DAYS:
2910+
return TemporalUnits.DAY;
2911+
case HOURS:
2912+
return TemporalUnits.HOUR;
2913+
case MINUTES:
2914+
return TemporalUnits.MINUTE;
2915+
case SECONDS:
2916+
return TemporalUnits.SECOND;
2917+
case MILLISECONDS:
2918+
return TemporalUnits.MILLISECOND;
2919+
}
2920+
2921+
throw new IllegalArgumentException(String.format("Cannot create TemporalUnit from %s", timeUnit));
2922+
}
2923+
2924+
/**
2925+
* Converts the given chrono unit into a {@link TemporalUnit}. Supported units are: years, weeks, months, days,
2926+
* hours, minutes, seconds, and millis.
2927+
*
2928+
* @param chronoUnit the chrono unit to convert, must not be {@literal null}.
2929+
* @return
2930+
* @throws IllegalArgumentException if the {@link TimeUnit} is {@literal null} or not supported for conversion.
2931+
*/
2932+
static TemporalUnit from(ChronoUnit chronoUnit) {
2933+
2934+
switch (chronoUnit) {
2935+
case YEARS:
2936+
return TemporalUnits.YEAR;
2937+
case WEEKS:
2938+
return TemporalUnits.WEEK;
2939+
case MONTHS:
2940+
return TemporalUnits.MONTH;
2941+
case DAYS:
2942+
return TemporalUnits.DAY;
2943+
case HOURS:
2944+
return TemporalUnits.HOUR;
2945+
case MINUTES:
2946+
return TemporalUnits.MINUTE;
2947+
case SECONDS:
2948+
return TemporalUnits.SECOND;
2949+
case MILLIS:
2950+
return TemporalUnits.MILLISECOND;
2951+
}
2952+
2953+
throw new IllegalArgumentException(String.format("Cannot create TemporalUnit from %s", chronoUnit));
2954+
}
2955+
}
2956+
2957+
/**
2958+
* Supported temporal units.
2959+
*/
2960+
enum TemporalUnits implements TemporalUnit {
2961+
YEAR, QUARTER, WEEK, MONTH, DAY, HOUR, MINUTE, SECOND, MILLISECOND
2962+
2963+
}
2964+
27232965
@SuppressWarnings("unchecked")
27242966
private static <T extends TimezonedDateAggregationExpression> T applyTimezone(T instance, Timezone timezone) {
27252967
return !ObjectUtils.nullSafeEquals(Timezone.none(), timezone) && !instance.hasTimezone()

0 commit comments

Comments
 (0)