15
15
*/
16
16
package org .springframework .data .mongodb .core .aggregation ;
17
17
18
+ import java .time .ZoneId ;
19
+ import java .time .ZoneOffset ;
20
+ import java .time .temporal .ChronoUnit ;
18
21
import java .util .Collections ;
19
22
import java .util .HashMap ;
20
23
import java .util .LinkedHashMap ;
24
+ import java .util .Locale ;
21
25
import java .util .Map ;
26
+ import java .util .TimeZone ;
27
+ import java .util .concurrent .TimeUnit ;
22
28
23
29
import org .springframework .lang .Nullable ;
24
30
import org .springframework .util .Assert ;
@@ -157,6 +163,7 @@ public static DateFromString dateFromString(String value) {
157
163
* <strong>NOTE: </strong>Support for timezones in aggregations Requires MongoDB 3.6 or later.
158
164
*
159
165
* @author Christoph Strobl
166
+ * @author Mark Paluch
160
167
* @since 2.1
161
168
*/
162
169
public static class Timezone {
@@ -192,6 +199,61 @@ public static Timezone valueOf(Object value) {
192
199
return new Timezone (value );
193
200
}
194
201
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
+
195
257
/**
196
258
* Create a {@link Timezone} for the {@link Field} reference holding the Olson Timezone Identifier or UTC Offset.
197
259
*
@@ -212,6 +274,11 @@ public static Timezone ofField(String fieldReference) {
212
274
public static Timezone ofExpression (AggregationExpression expression ) {
213
275
return valueOf (expression );
214
276
}
277
+
278
+ @ Nullable
279
+ Object getValue () {
280
+ return value ;
281
+ }
215
282
}
216
283
217
284
/**
@@ -303,39 +370,87 @@ public DateOperatorFactory withTimezone(Timezone timezone) {
303
370
304
371
/**
305
372
* 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}.
308
376
* @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
311
378
*/
312
379
public DateAdd addValueOf (AggregationExpression expression , String unit ) {
313
380
return applyTimezone (DateAdd .addValueOf (expression , unit ).toDate (dateReference ()), timezone );
314
381
}
315
382
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
+
316
398
/**
317
399
* 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}.
320
403
* @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
323
405
*/
324
406
public DateAdd addValueOf (String fieldReference , String unit ) {
325
407
return applyTimezone (DateAdd .addValueOf (fieldReference , unit ).toDate (dateReference ()), timezone );
326
408
}
327
409
328
410
/**
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}.
332
431
* @return
333
432
* @since 3.3 new instance of {@link DateAdd}.
334
433
*/
335
434
public DateAdd add (Object value , String unit ) {
336
435
return applyTimezone (DateAdd .addValue (value , unit ).toDate (dateReference ()), timezone );
337
436
}
338
437
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
+
339
454
/**
340
455
* Creates new {@link AggregationExpression} that returns the day of the year for a date as a number between 1 and
341
456
* 366.
@@ -367,41 +482,89 @@ public DayOfWeek dayOfWeek() {
367
482
}
368
483
369
484
/**
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}.
373
489
* @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
376
491
*/
377
492
public DateDiff diffValueOf (AggregationExpression expression , String unit ) {
378
493
return applyTimezone (DateDiff .diffValueOf (expression , unit ).toDate (dateReference ()), timezone );
379
494
}
380
495
381
496
/**
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}.
385
501
* @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
388
519
*/
389
520
public DateDiff diffValueOf (String fieldReference , String unit ) {
390
521
return applyTimezone (DateDiff .diffValueOf (fieldReference , unit ).toDate (dateReference ()), timezone );
391
522
}
392
523
393
524
/**
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}.
397
529
* @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
400
547
*/
401
548
public DateDiff diff (Object value , String unit ) {
402
549
return applyTimezone (DateDiff .diffValue (value , unit ).toDate (dateReference ()), timezone );
403
550
}
404
551
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
+
405
568
/**
406
569
* Creates new {@link AggregationExpression} that returns the year portion of a date.
407
570
*
@@ -2720,6 +2883,85 @@ protected String getMongoMethod() {
2720
2883
}
2721
2884
}
2722
2885
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
+
2723
2965
@ SuppressWarnings ("unchecked" )
2724
2966
private static <T extends TimezonedDateAggregationExpression > T applyTimezone (T instance , Timezone timezone ) {
2725
2967
return !ObjectUtils .nullSafeEquals (Timezone .none (), timezone ) && !instance .hasTimezone ()
0 commit comments