Skip to content

Commit e44cb53

Browse files
author
Morten Haraldsen
committed
Handle extremely large/small values
1 parent bf10bf4 commit e44cb53

File tree

2 files changed

+37
-29
lines changed

2 files changed

+37
-29
lines changed

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

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ private static int readUntilNonDigit(final String text, final int offset, final
9595
int unit = 0;
9696
int index = offset;
9797
long value = 0;
98-
int startIndex = index;
9998
while (index < text.length())
10099
{
101100
final char c = text.charAt(index);
@@ -107,20 +106,22 @@ private static int readUntilNonDigit(final String text, final int offset, final
107106
}
108107
else
109108
{
110-
int digit = c - DIGIT_ZERO;
109+
final int digit = c - DIGIT_ZERO;
110+
111+
// Check for overflow before updating `value`
112+
if (value > (Long.MAX_VALUE - digit) / 10)
113+
{
114+
error("Expression is too large", text, index);
115+
}
116+
111117
value = (value << 3) + (value << 1) + digit;
112118
index++;
113119
}
114120
}
115121

116-
if (index - startIndex > MAX_DIGITS || value > Integer.MAX_VALUE)
117-
{
118-
error("Value too large for unit '" + (char) unit + "'", text, index);
119-
}
120-
121122
final int length = index - offset;
122123

123-
consumer.accept(text, index, length, (char) unit, (int) value);
124+
consumer.accept(text, index, length, (char) unit, value);
124125

125126
return index + 1;
126127
}
@@ -153,7 +154,7 @@ private DurationPartsConsumer(final int startOffset, boolean negative)
153154
this.negative = negative;
154155
}
155156

156-
public final void accept(final String text, final int index, final int length, final char unit, final int value)
157+
public final void accept(final String text, final int index, final int length, final char unit, final long value)
157158
{
158159
final int relIndex = index - startOffset;
159160

@@ -173,12 +174,6 @@ public final void accept(final String text, final int index, final int length, f
173174
error("Duration must start with 'P'", text, index);
174175
}
175176

176-
/*
177-
* An int can never overflow a long when used for seconds, minutes, hours, days, and weeks,
178-
* even at their maximum values, because the total number of seconds remains within the
179-
* 64-bit long's limits.
180-
*/
181-
182177
if (unit == 0)
183178
{
184179
if (!dotFound)
@@ -286,12 +281,7 @@ public final void accept(final String text, final int index, final int length, f
286281
error("Maximum allowed is 9 fraction digits", text, index);
287282
}
288283

289-
if (length == 0)
290-
{
291-
error("Must have at least one fraction digit after the '.''", text, index);
292-
}
293-
294-
nano = value;
284+
nano = Math.toIntExact(value);
295285
int remainingDigits = DIGITS_IN_NANO - length;
296286
if (remainingDigits > 0)
297287
{

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

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@
2020
* #L%
2121
*/
2222

23-
import static org.assertj.core.api.Assertions.assertThat;
24-
import static org.assertj.core.api.Assertions.assertThatThrownBy;
25-
import static org.junit.jupiter.api.Assertions.assertThrows;
26-
27-
import java.time.format.DateTimeParseException;
23+
import com.ethlo.time.internal.ItuDurationParser;
2824

2925
import org.junit.jupiter.api.Test;
3026

31-
import com.ethlo.time.internal.ItuDurationParser;
27+
import java.time.format.DateTimeParseException;
28+
29+
import static org.assertj.core.api.Assertions.assertThat;
30+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
31+
import static org.junit.jupiter.api.Assertions.assertThrows;
3232

3333
class ItuDurationParserTest
3434
{
@@ -39,6 +39,24 @@ void testMissingP()
3939
.hasMessage("Duration must start with 'P': 1D");
4040
}
4141

42+
@Test
43+
void testHighestValue()
44+
{
45+
final String input = "PT" + Long.MAX_VALUE + ".999999999S";
46+
final Duration result = ITU.parseDuration(input);
47+
assertThat(result.getSeconds()).isEqualTo(Long.MAX_VALUE);
48+
assertThat(result.getNanos()).isEqualTo(999_999_999);
49+
}
50+
51+
@Test
52+
void testLowestValue()
53+
{
54+
final String input = "-PT9223372036854775807S";
55+
final Duration result = ITU.parseDuration(input);
56+
assertThat(result.getSeconds()).isEqualTo(-Long.MAX_VALUE);
57+
assertThat(result.getNanos()).isEqualTo(0L);
58+
}
59+
4260
@Test
4361
void testValidMinimal()
4462
{
@@ -137,10 +155,10 @@ void shouldParseDurationWithNoTimeSection()
137155
@Test
138156
void shouldThrowDateTimeParseExceptionForOverflow()
139157
{
140-
final String input = "P20D9999999999999999H";
158+
final String input = "P20D999999999999999999999H";
141159
assertThatThrownBy(() -> ItuDurationParser.parse(input))
142160
.isInstanceOf(DateTimeParseException.class)
143-
.hasMessageContaining("Value too large for unit 'H': " + input);
161+
.hasMessageContaining("Expression is too large: " + input);
144162
}
145163

146164
@Test

0 commit comments

Comments
 (0)