Skip to content

Commit 3fb0412

Browse files
author
Morten Haraldsen
committed
Minor performance improvements, more tests.
1 parent abcebc1 commit 3fb0412

File tree

3 files changed

+123
-38
lines changed

3 files changed

+123
-38
lines changed

src/main/java/com/ethlo/time/Duration.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
*/
3333
public class Duration implements Comparable<Duration>
3434
{
35-
public static final Duration ZERO = new Duration(0,0);
35+
public static final Duration ZERO = new Duration(0, 0);
3636

3737
public static final int NANOS_PER_SECOND = 1_000_000_000;
3838
public static final long SECONDS_PER_MINUTE = 60;
@@ -315,4 +315,27 @@ public int compareTo(final Duration o)
315315
.thenComparingInt(Duration::getNanos)
316316
.compare(this, o);
317317
}
318+
319+
public Duration plusHours(final long hours)
320+
{
321+
return plusSeconds(Math.multiplyExact(hours, SECONDS_PER_HOUR));
322+
}
323+
324+
public Duration plusMinutes(final long minutes)
325+
{
326+
return plusSeconds(Math.multiplyExact(minutes, SECONDS_PER_MINUTE));
327+
}
328+
329+
public Duration plusSeconds(long seconds)
330+
{
331+
return new Duration(Math.addExact(this.seconds, seconds), nanos);
332+
}
333+
334+
public Duration plusNanos(long nanos)
335+
{
336+
final long nanosTotal = Math.addExact(this.nanos, nanos);
337+
final int nanosRemainder = Math.toIntExact(nanosTotal % NANOS_PER_SECOND);
338+
final long extraSecs = nanosTotal / NANOS_PER_SECOND;
339+
return new Duration(Math.addExact(seconds, extraSecs), nanosRemainder);
340+
}
318341
}

src/main/java/com/ethlo/time/ItuDurationParser.java

Lines changed: 64 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@
2121
*/
2222

2323
import static com.ethlo.time.internal.fixed.ITUParser.DIGITS_IN_NANO;
24-
import static com.ethlo.time.internal.fixed.ITUParser.RADIX;
2524
import static com.ethlo.time.internal.fixed.ITUParser.sanityCheckInputParams;
26-
import static com.ethlo.time.internal.util.LimitedCharArrayIntegerUtil.DIGIT_9;
2725
import static com.ethlo.time.internal.util.LimitedCharArrayIntegerUtil.ZERO;
2826

2927
import java.time.format.DateTimeParseException;
@@ -90,7 +88,8 @@ private static int readUntilNonDigit(final String chars, final int offset, final
9088
while (idx < len)
9189
{
9290
final char c = chars.charAt(idx);
93-
if (c < ZERO || c > DIGIT_9)
91+
int isDigit = (c - '0') >>> 31 | ('9' - c) >>> 31;
92+
if (isDigit != 0)
9493
{
9594
unit = c;
9695
break;
@@ -105,7 +104,8 @@ private static int readUntilNonDigit(final String chars, final int offset, final
105104
throw new DateTimeParseException("Numeric overflow while parsing value", chars, idx);
106105
}
107106

108-
value = value * RADIX + digit;
107+
value = (value << 3) + (value << 1) + (c - '0');
108+
//value = value * RADIX + digit;
109109
idx++;
110110
}
111111
}
@@ -135,11 +135,11 @@ private static class DurationPartsConsumer
135135
private boolean readingFractionalPart;
136136
private boolean afterT;
137137
private boolean pFound;
138-
private boolean wFound;
139-
private boolean dFound;
140-
private boolean hFound;
141-
private boolean mFound;
142-
private boolean sFound;
138+
private int wFound;
139+
private int dFound;
140+
private int hFound;
141+
private int mFound;
142+
private int sFound;
143143
private boolean dotFound;
144144
private boolean fractionsFound;
145145

@@ -149,6 +149,11 @@ private DurationPartsConsumer(final int startOffset, boolean negative)
149149
this.negative = negative;
150150
}
151151

152+
private static DateTimeParseException error(String chars, int index)
153+
{
154+
return new DateTimeParseException("Units must be in order from largest to smallest", chars, index);
155+
}
156+
152157
public final void accept(final String chars, final int index, final int length, final char unit, final int value)
153158
{
154159
final int relIndex = index - startOffset;
@@ -195,7 +200,7 @@ public final void accept(final String chars, final int index, final int length,
195200

196201
case 'W':
197202
assertNonFractional('W', chars, index);
198-
if (wFound)
203+
if (wFound > 0)
199204
{
200205
throw new DateTimeParseException("'W' (week) can only appear once", chars, index);
201206
}
@@ -205,12 +210,12 @@ public final void accept(final String chars, final int index, final int length,
205210
throw new DateTimeParseException("'W' (week) must appear before 'T' in the duration", chars, index);
206211
}
207212
seconds += value * 604800L; // 7 * 86400
208-
wFound = true;
213+
wFound = index;
209214
break;
210215

211216
case 'D':
212217
assertNonFractional('D', chars, index);
213-
if (dFound)
218+
if (dFound > 0)
214219
{
215220
throw new DateTimeParseException("'D' (days) can only appear once", chars, index);
216221
}
@@ -219,12 +224,12 @@ public final void accept(final String chars, final int index, final int length,
219224
throw new DateTimeParseException("'D' (days) must appear before 'T' in the duration", chars, index);
220225
}
221226
seconds += value * 86400L;
222-
dFound = true;
227+
dFound = index;
223228
break;
224229

225230
case 'H':
226231
assertNonFractional('H', chars, index);
227-
if (hFound)
232+
if (hFound > 0)
228233
{
229234
throw new DateTimeParseException("'H' (hours) can only appear once", chars, index);
230235
}
@@ -233,12 +238,12 @@ public final void accept(final String chars, final int index, final int length,
233238
throw new DateTimeParseException("'H' (hours) must appear after 'T' in the duration", chars, index);
234239
}
235240
seconds += value * 3600L;
236-
hFound = true;
241+
hFound = index;
237242
break;
238243

239244
case 'M':
240245
assertNonFractional('M', chars, index);
241-
if (mFound)
246+
if (mFound > 0)
242247
{
243248
throw new DateTimeParseException("'M' (minutes) can only appear once", chars, index);
244249
}
@@ -247,19 +252,19 @@ public final void accept(final String chars, final int index, final int length,
247252
throw new DateTimeParseException("'M' (minutes) must appear after 'T' in the duration", chars, index);
248253
}
249254
seconds += value * 60L;
250-
mFound = true;
255+
mFound = index;
251256
break;
252257

253258
case 'S':
254-
if (sFound)
259+
if (sFound > 0)
255260
{
256261
throw new DateTimeParseException("'S' (seconds) can only appear once", chars, index);
257262
}
258263
if (!afterT)
259264
{
260265
throw new DateTimeParseException("'S' (seconds) must appear after 'T' in the duration", chars, index);
261266
}
262-
sFound = true;
267+
sFound = index;
263268

264269
if (readingFractionalPart)
265270
{
@@ -323,7 +328,7 @@ private void assertNonFractional(final char unit, final String chars, final int
323328

324329
public void validate(String chars, int index)
325330
{
326-
if (afterT && !hFound && !mFound && !sFound)
331+
if (afterT && hFound == 0 && mFound == 0 && sFound == 0)
327332
{
328333
throw new DateTimeParseException("Expected at least value and unit after the 'T'", chars, index);
329334
}
@@ -333,15 +338,52 @@ public void validate(String chars, int index)
333338
throw new DateTimeParseException("Expected at least one fractional digit after the dot", chars, index);
334339
}
335340

336-
if (fractionsFound && !sFound)
341+
if (fractionsFound && sFound == 0)
337342
{
338343
throw new DateTimeParseException("Expected 'S' after fractional number", chars, index);
339344
}
340345

341-
if (!wFound && !dFound && !hFound && !mFound && !sFound)
346+
if (wFound == 0 && dFound == 0 && hFound == 0 && mFound == 0 && sFound == 0)
342347
{
343348
throw new DateTimeParseException("Expected at least one value and unit", chars, index);
344349
}
350+
351+
valdateUnitOrder(chars);
352+
}
353+
354+
private void valdateUnitOrder(String chars)
355+
{
356+
int lastIndex = -1;
357+
358+
if (wFound > 0)
359+
{
360+
if (wFound < lastIndex) throw error(chars, wFound);
361+
lastIndex = wFound;
362+
}
363+
364+
if (dFound > 0)
365+
{
366+
if (dFound < lastIndex) throw error(chars, dFound);
367+
lastIndex = dFound;
368+
}
369+
370+
if (hFound > 0)
371+
{
372+
if (hFound < lastIndex) throw error(chars, hFound);
373+
lastIndex = hFound;
374+
}
375+
376+
if (mFound > 0)
377+
{
378+
if (mFound < lastIndex) throw error(chars, mFound);
379+
lastIndex = mFound;
380+
}
381+
382+
if (sFound > 0)
383+
{
384+
if (sFound < lastIndex) throw error(chars, sFound);
385+
lastIndex = sFound;
386+
}
345387
}
346388

347389
public Duration getResult()

src/test/java/com/ethlo/time/ItuDurationParserTest.java

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,31 @@ void testValidFullNotNormalizedToNormalizedNegative()
5858
assertThat(ItuDurationParser.parse("-P4W10DT28H122M1.123456S").normalized()).isEqualTo("-P5W4DT6H2M1.123456S");
5959
}
6060

61+
@Test
62+
void testEmpty()
63+
{
64+
assertThrows(DateTimeParseException.class, () -> ItuDurationParser.parse(""));
65+
}
66+
67+
@Test
68+
void testNull()
69+
{
70+
assertThrows(NullPointerException.class, () -> ItuDurationParser.parse(null));
71+
}
72+
73+
@Test
74+
void testNothingAfterDot()
75+
{
76+
assertThrows(DateTimeParseException.class, () -> ItuDurationParser.parse("PT1."));
77+
}
78+
79+
@Test
80+
void testNoUnitAfterFractions()
81+
{
82+
assertThrows(DateTimeParseException.class, () -> ItuDurationParser.parse("PT1.5"));
83+
}
84+
85+
6186
@Test
6287
void shouldParseZeroDuration()
6388
{
@@ -103,17 +128,15 @@ void shouldThrowDateTimeParseExceptionForInvalidUnits()
103128
@Test
104129
void shouldParseFractionalSecondsWithoutTrailingZeros()
105130
{
106-
// Input: PT1.123000S (1 second, 123 milliseconds)
107-
java.time.Duration duration = ItuDurationParser.parse("PT1.123000S").toDuration();
108-
assertThat(duration).isEqualTo(java.time.Duration.ofSeconds(1).plusNanos(123000000));
131+
final Duration duration = ItuDurationParser.parse("PT1.123000S");
132+
assertThat(duration).isEqualTo(Duration.ofSeconds(1).plusNanos(123000000));
109133
}
110134

111135
@Test
112136
void shouldHandleMultipleFractionalDigits()
113137
{
114-
// Input: PT1.123456789S (1 second, 123456789 nanoseconds)
115-
java.time.Duration duration = ItuDurationParser.parse("PT1.123456789S").toDuration();
116-
assertThat(duration).isEqualTo(java.time.Duration.ofSeconds(1).plusNanos(123456789));
138+
final Duration duration = ItuDurationParser.parse("PT1.123456789S");
139+
assertThat(duration).isEqualTo(Duration.ofSeconds(1).plusNanos(123456789));
117140
}
118141

119142
@Test
@@ -146,24 +169,21 @@ void shouldThrowDateTimeParseExceptionForMalformedDuration()
146169
@Test
147170
void shouldParseDurationWithWeeks()
148171
{
149-
// Input: P1W2D (1 week, 2 days)
150-
java.time.Duration duration = ItuDurationParser.parse("P1W2D").toDuration();
151-
assertThat(duration).isEqualTo(java.time.Duration.ofDays(7 + 2)); // 7 days + 2 days
172+
final Duration duration = ItuDurationParser.parse("P1W2D");
173+
assertThat(duration).isEqualTo(Duration.ofDays(7 + 2));
152174
}
153175

154176
@Test
155177
void shouldParseDurationWithTimeOnly()
156178
{
157-
// Input: PT10H30M45S (10 hours, 30 minutes, 45 seconds)
158-
java.time.Duration duration = ItuDurationParser.parse("PT10H30M45S").toDuration();
159-
160-
assertThat(duration).isEqualTo(java.time.Duration.ofHours(10).plusMinutes(30).plusSeconds(45));
179+
final Duration duration = ItuDurationParser.parse("PT10H30M45S");
180+
assertThat(duration).isEqualTo(Duration.ofHours(10).plusMinutes(30).plusSeconds(45));
161181
}
162182

163183
@Test
164184
void parse0DDurationAsZero()
165185
{
166-
java.time.Duration duration = ItuDurationParser.parse("P0D").toDuration();
167-
assertThat(duration).isEqualTo(java.time.Duration.ZERO);
186+
final Duration duration = ItuDurationParser.parse("P0D");
187+
assertThat(duration).isEqualTo(Duration.ZERO);
168188
}
169189
}

0 commit comments

Comments
 (0)