11package com .eternalcode .commons .time ;
22
3+ import com .eternalcode .commons .Lazy ;
34import java .math .BigInteger ;
5+ import java .math .RoundingMode ;
46import java .time .Duration ;
57import java .time .LocalDate ;
68import java .time .LocalDateTime ;
4042 */
4143public abstract class TemporalAmountParser <T extends TemporalAmount > {
4244
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 <>();
5246
5347 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 ));
7659 }
7760
61+ private final ChronoUnit defaultZero ;
62+ private final Lazy <String > defaultZeroSymbol ;
7863 private final Map <String , ChronoUnit > units = new LinkedHashMap <>();
64+ private final Set <TimeModifier > modifiers = new HashSet <>();
7965 private final LocalDateTimeProvider baseForTimeEstimation ;
8066
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 );
8371 }
8472
85- protected TemporalAmountParser (Map <String , ChronoUnit > units , LocalDateTimeProvider baseForTimeEstimation ) {
73+ protected TemporalAmountParser (ChronoUnit defaultZero , LocalDateTimeProvider baseForTimeEstimation ) {
74+ this .defaultZero = defaultZero ;
8675 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+ );
8883 }
8984
9085 public TemporalAmountParser <T > withUnit (String symbol , ChronoUnit chronoUnit ) {
@@ -98,14 +93,61 @@ public TemporalAmountParser<T> withUnit(String symbol, ChronoUnit chronoUnit) {
9893
9994 Map <String , ChronoUnit > newUnits = new LinkedHashMap <>(this .units );
10095 newUnits .put (symbol , chronoUnit );
101- return clone (newUnits , this .baseForTimeEstimation );
96+ return clone (this . defaultZero , newUnits , this . modifiers , this .baseForTimeEstimation );
10297 }
10398
10499 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 );
106105 }
107106
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 );
109151
110152 private boolean validCharacters (String content , Predicate <Character > predicate ) {
111153 for (int i = 0 ; i < content .length (); i ++) {
@@ -274,6 +316,9 @@ public ChronoUnit getUnit() {
274316 public String format (T temporalAmount ) {
275317 StringBuilder builder = new StringBuilder ();
276318 Duration duration = this .toDuration (this .baseForTimeEstimation , temporalAmount );
319+ for (TimeModifier modifier : this .modifiers ) {
320+ duration = modifier .modify (duration );
321+ }
277322
278323 if (duration .isNegative ()) {
279324 builder .append ('-' );
@@ -284,31 +329,34 @@ public String format(T temporalAmount) {
284329 Collections .reverse (keys );
285330
286331 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 );
294334
295- if (part == null ) {
296- throw new IllegalArgumentException ("Unsupported unit " + chronoUnit );
335+ if (nanosInOneUnit == null ) {
336+ throw new IllegalArgumentException ("Unsupported unit " + unit );
297337 }
298338
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 );
302341
303342 if (count .equals (BigInteger .ZERO )) {
304343 continue ;
305344 }
306345
346+ BigInteger nanosCountCleared = count .multiply (nanosInOneUnit );
347+
307348 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 ;
309357 }
310358
311- return builder . toString () ;
359+ return result ;
312360 }
313361
314362 protected abstract Duration toDuration (LocalDateTimeProvider baseForTimeEstimation , T temporalAmount );
@@ -340,11 +388,14 @@ static LocalDateTimeProvider of(LocalDate localDate) {
340388
341389 }
342390
343- BigInteger durationToNano (Duration duration ) {
391+ protected interface TimeModifier {
392+ Duration modify (Duration duration );
393+ }
394+
395+ private BigInteger durationToNano (Duration duration ) {
344396 return BigInteger .valueOf (duration .getSeconds ())
345397 .multiply (BigInteger .valueOf (1_000_000_000 ))
346398 .add (BigInteger .valueOf (duration .getNano ()));
347399 }
348400
349401}
350-
0 commit comments