1
1
package com .eternalcode .commons .time ;
2
2
3
+ import com .eternalcode .commons .Lazy ;
3
4
import java .math .BigInteger ;
5
+ import java .math .RoundingMode ;
4
6
import java .time .Duration ;
5
7
import java .time .LocalDate ;
6
8
import java .time .LocalDateTime ;
40
42
*/
41
43
public abstract class TemporalAmountParser <T extends TemporalAmount > {
42
44
43
- private static final Map <ChronoUnit , Long > UNIT_TO_NANO = new LinkedHashMap <>();
44
- private static final Map <ChronoUnit , Integer > PART_TIME_UNITS = new LinkedHashMap <>();
45
-
46
- private Set <ChronoUnit > roundedUnits = new HashSet <>();
47
-
48
- public TemporalAmountParser <T > roundOff (ChronoUnit unit ) {
49
- this .roundedUnits .add (unit );
50
- return this ;
51
- }
45
+ private static final Map <ChronoUnit , BigInteger > UNIT_TO_NANO = new LinkedHashMap <>();
52
46
53
47
static {
54
- UNIT_TO_NANO .put (ChronoUnit .NANOS , 1L );
55
- UNIT_TO_NANO .put (ChronoUnit .MICROS , 1_000L );
56
- UNIT_TO_NANO .put (ChronoUnit .MILLIS , 1_000_000L );
57
- UNIT_TO_NANO .put (ChronoUnit .SECONDS , 1_000_000_000L );
58
- UNIT_TO_NANO .put (ChronoUnit .MINUTES , 60 * 1_000_000_000L );
59
- UNIT_TO_NANO .put (ChronoUnit .HOURS , 60 * 60 * 1_000_000_000L );
60
- UNIT_TO_NANO .put (ChronoUnit .DAYS , 24 * 60 * 60 * 1_000_000_000L );
61
- UNIT_TO_NANO .put (ChronoUnit .WEEKS , 7 * 24 * 60 * 60 * 1_000_000_000L );
62
- UNIT_TO_NANO .put (ChronoUnit .MONTHS , 30 * 24 * 60 * 60 * 1_000_000_000L );
63
- UNIT_TO_NANO .put (ChronoUnit .YEARS , 365 * 24 * 60 * 60 * 1_000_000_000L );
64
- UNIT_TO_NANO .put (ChronoUnit .DECADES , 10 * 365 * 24 * 60 * 60 * 1_000_000_000L );
65
-
66
- PART_TIME_UNITS .put (ChronoUnit .NANOS , 1000 );
67
- PART_TIME_UNITS .put (ChronoUnit .MICROS , 1000 );
68
- PART_TIME_UNITS .put (ChronoUnit .MILLIS , 1000 );
69
- PART_TIME_UNITS .put (ChronoUnit .SECONDS , 60 );
70
- PART_TIME_UNITS .put (ChronoUnit .MINUTES , 60 );
71
- PART_TIME_UNITS .put (ChronoUnit .HOURS , 24 );
72
- PART_TIME_UNITS .put (ChronoUnit .DAYS , 7 );
73
- PART_TIME_UNITS .put (ChronoUnit .WEEKS , 4 );
74
- PART_TIME_UNITS .put (ChronoUnit .MONTHS , 12 );
75
- PART_TIME_UNITS .put (ChronoUnit .YEARS , Integer .MAX_VALUE );
48
+ UNIT_TO_NANO .put (ChronoUnit .NANOS , BigInteger .valueOf (1L ));
49
+ UNIT_TO_NANO .put (ChronoUnit .MICROS , BigInteger .valueOf (1_000L ));
50
+ UNIT_TO_NANO .put (ChronoUnit .MILLIS , BigInteger .valueOf (1_000_000L ));
51
+ UNIT_TO_NANO .put (ChronoUnit .SECONDS , BigInteger .valueOf (1_000_000_000L ));
52
+ UNIT_TO_NANO .put (ChronoUnit .MINUTES , BigInteger .valueOf (60 * 1_000_000_000L ));
53
+ UNIT_TO_NANO .put (ChronoUnit .HOURS , BigInteger .valueOf (60 * 60 * 1_000_000_000L ));
54
+ UNIT_TO_NANO .put (ChronoUnit .DAYS , BigInteger .valueOf (24 * 60 * 60 * 1_000_000_000L ));
55
+ UNIT_TO_NANO .put (ChronoUnit .WEEKS , BigInteger .valueOf (7 * 24 * 60 * 60 * 1_000_000_000L ));
56
+ UNIT_TO_NANO .put (ChronoUnit .MONTHS , BigInteger .valueOf (30 * 24 * 60 * 60 * 1_000_000_000L ));
57
+ UNIT_TO_NANO .put (ChronoUnit .YEARS , BigInteger .valueOf (365 * 24 * 60 * 60 * 1_000_000_000L ));
58
+ UNIT_TO_NANO .put (ChronoUnit .DECADES , BigInteger .valueOf (10 * 365 * 24 * 60 * 60 * 1_000_000_000L ));
76
59
}
77
60
61
+ private final ChronoUnit defaultZero ;
62
+ private final Lazy <String > defaultZeroSymbol ;
78
63
private final Map <String , ChronoUnit > units = new LinkedHashMap <>();
64
+ private final Set <TimeModifier > modifiers = new HashSet <>();
79
65
private final LocalDateTimeProvider baseForTimeEstimation ;
80
66
81
- protected TemporalAmountParser (LocalDateTimeProvider baseForTimeEstimation ) {
82
- this .baseForTimeEstimation = baseForTimeEstimation ;
67
+ protected TemporalAmountParser (ChronoUnit defaultZero , Map <String , ChronoUnit > units , Set <TimeModifier > modifiers , LocalDateTimeProvider baseForTimeEstimation ) {
68
+ this (defaultZero , baseForTimeEstimation );
69
+ this .units .putAll (units );
70
+ this .modifiers .addAll (modifiers );
83
71
}
84
72
85
- protected TemporalAmountParser (Map <String , ChronoUnit > units , LocalDateTimeProvider baseForTimeEstimation ) {
73
+ protected TemporalAmountParser (ChronoUnit defaultZero , LocalDateTimeProvider baseForTimeEstimation ) {
74
+ this .defaultZero = defaultZero ;
86
75
this .baseForTimeEstimation = baseForTimeEstimation ;
87
- this .units .putAll (units );
76
+ this .defaultZeroSymbol = new Lazy <>(() -> this .units .entrySet ()
77
+ .stream ()
78
+ .filter (entry -> entry .getValue () == defaultZero )
79
+ .map (entry -> entry .getKey ())
80
+ .findFirst ()
81
+ .orElseThrow (() -> new IllegalStateException ("Can not find default zero symbol for " + defaultZero ))
82
+ );
88
83
}
89
84
90
85
public TemporalAmountParser <T > withUnit (String symbol , ChronoUnit chronoUnit ) {
@@ -98,14 +93,61 @@ public TemporalAmountParser<T> withUnit(String symbol, ChronoUnit chronoUnit) {
98
93
99
94
Map <String , ChronoUnit > newUnits = new LinkedHashMap <>(this .units );
100
95
newUnits .put (symbol , chronoUnit );
101
- return clone (newUnits , this .baseForTimeEstimation );
96
+ return clone (this . defaultZero , newUnits , this . modifiers , this .baseForTimeEstimation );
102
97
}
103
98
104
99
public TemporalAmountParser <T > withLocalDateTimeProvider (LocalDateTimeProvider baseForTimeEstimation ) {
105
- return clone (this .units , baseForTimeEstimation );
100
+ return clone (this .defaultZero , this .units , this .modifiers , baseForTimeEstimation );
101
+ }
102
+
103
+ public TemporalAmountParser <T > withDefaultZero (ChronoUnit defaultZero ) {
104
+ return clone (defaultZero , this .units , this .modifiers , this .baseForTimeEstimation );
106
105
}
107
106
108
- protected abstract TemporalAmountParser <T > clone (Map <String , ChronoUnit > units , LocalDateTimeProvider baseForTimeEstimation );
107
+ public TemporalAmountParser <T > withRounded (ChronoUnit unit , RoundingMode roundingMode ) {
108
+ return withTimeModifier (duration -> {
109
+ BigInteger nanosInUnit = UNIT_TO_NANO .get (unit );
110
+ BigInteger nanos = durationToNano (duration );
111
+ BigInteger rounded = round (roundingMode , nanos , nanosInUnit );
112
+
113
+ return Duration .ofNanos (rounded .longValue ());
114
+ });
115
+ }
116
+
117
+ private static BigInteger round (RoundingMode roundingMode , BigInteger nanos , BigInteger nanosInUnit ) {
118
+ BigInteger remainder = nanos .remainder (nanosInUnit );
119
+ BigInteger subtract = nanos .subtract (remainder );
120
+ BigInteger add = subtract .add (nanosInUnit );
121
+
122
+ BigInteger roundedUp = remainder .equals (BigInteger .ZERO ) ? nanos : (nanos .signum () > 0 ? add : subtract .subtract (nanosInUnit ));
123
+ BigInteger roundedDown = remainder .equals (BigInteger .ZERO ) ? nanos : (nanos .signum () > 0 ? subtract : add .subtract (nanosInUnit ));
124
+
125
+ int compare = remainder .abs ().multiply (BigInteger .valueOf (2 )).compareTo (nanosInUnit );
126
+ switch (roundingMode ) {
127
+ case UP :
128
+ return roundedUp ;
129
+ case DOWN :
130
+ return roundedDown ;
131
+ case CEILING :
132
+ return nanos .signum () >= 0 ? roundedUp : roundedDown ;
133
+ case FLOOR :
134
+ return nanos .signum () >= 0 ? roundedDown : roundedUp ;
135
+ case HALF_UP :
136
+ return compare >= 0 ? roundedUp : roundedDown ;
137
+ case HALF_DOWN :
138
+ return (compare > 0 ) ? roundedUp : roundedDown ;
139
+ default : throw new IllegalArgumentException ("Unsupported rounding mode " + roundingMode );
140
+ }
141
+ }
142
+
143
+
144
+ private TemporalAmountParser <T > withTimeModifier (TimeModifier modifier ) {
145
+ Set <TimeModifier > newRoundedUnits = new HashSet <>(this .modifiers );
146
+ newRoundedUnits .add (modifier );
147
+ return clone (this .defaultZero , this .units , newRoundedUnits , this .baseForTimeEstimation );
148
+ }
149
+
150
+ protected abstract TemporalAmountParser <T > clone (ChronoUnit defaultZeroUnit , Map <String , ChronoUnit > units , Set <TimeModifier > modifiers , LocalDateTimeProvider baseForTimeEstimation );
109
151
110
152
private boolean validCharacters (String content , Predicate <Character > predicate ) {
111
153
for (int i = 0 ; i < content .length (); i ++) {
@@ -274,6 +316,9 @@ public ChronoUnit getUnit() {
274
316
public String format (T temporalAmount ) {
275
317
StringBuilder builder = new StringBuilder ();
276
318
Duration duration = this .toDuration (this .baseForTimeEstimation , temporalAmount );
319
+ for (TimeModifier modifier : this .modifiers ) {
320
+ duration = modifier .modify (duration );
321
+ }
277
322
278
323
if (duration .isNegative ()) {
279
324
builder .append ('-' );
@@ -284,31 +329,34 @@ public String format(T temporalAmount) {
284
329
Collections .reverse (keys );
285
330
286
331
for (String key : keys ) {
287
- ChronoUnit chronoUnit = this .units .get (key );
288
-
289
- if (roundedUnits .contains (chronoUnit )) {
290
- continue ;
291
- }
292
-
293
- Long part = UNIT_TO_NANO .get (chronoUnit );
332
+ ChronoUnit unit = this .units .get (key );
333
+ BigInteger nanosInOneUnit = UNIT_TO_NANO .get (unit );
294
334
295
- if (part == null ) {
296
- throw new IllegalArgumentException ("Unsupported unit " + chronoUnit );
335
+ if (nanosInOneUnit == null ) {
336
+ throw new IllegalArgumentException ("Unsupported unit " + unit );
297
337
}
298
338
299
- BigInteger currentCount = this .durationToNano (duration ).divide (BigInteger .valueOf (part ));
300
- BigInteger maxCount = BigInteger .valueOf (PART_TIME_UNITS .get (chronoUnit ));
301
- BigInteger count = currentCount .equals (maxCount ) ? BigInteger .ONE : currentCount .mod (maxCount );
339
+ BigInteger nanosCount = this .durationToNano (duration );
340
+ BigInteger count = nanosCount .divide (nanosInOneUnit );
302
341
303
342
if (count .equals (BigInteger .ZERO )) {
304
343
continue ;
305
344
}
306
345
346
+ BigInteger nanosCountCleared = count .multiply (nanosInOneUnit );
347
+
307
348
builder .append (count ).append (key );
308
- duration = duration .minusNanos (count .longValue () * part );
349
+ duration = duration .minusNanos (nanosCountCleared .longValue ());
350
+ }
351
+
352
+ String result = builder .toString ();
353
+
354
+ if (result .isEmpty ()) {
355
+ String defaultSymbol = this .defaultZeroSymbol .get ();
356
+ return "0" + defaultSymbol ;
309
357
}
310
358
311
- return builder . toString () ;
359
+ return result ;
312
360
}
313
361
314
362
protected abstract Duration toDuration (LocalDateTimeProvider baseForTimeEstimation , T temporalAmount );
@@ -340,11 +388,14 @@ static LocalDateTimeProvider of(LocalDate localDate) {
340
388
341
389
}
342
390
343
- BigInteger durationToNano (Duration duration ) {
391
+ protected interface TimeModifier {
392
+ Duration modify (Duration duration );
393
+ }
394
+
395
+ private BigInteger durationToNano (Duration duration ) {
344
396
return BigInteger .valueOf (duration .getSeconds ())
345
397
.multiply (BigInteger .valueOf (1_000_000_000 ))
346
398
.add (BigInteger .valueOf (duration .getNano ()));
347
399
}
348
400
349
401
}
350
-
0 commit comments