Name: jl125535 Date: 01/27/2003
FULL PRODUCT VERSION :
java version "1.4.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0-b92)
Java HotSpot(TM) Client VM (build 1.4.0-b92, mixed mode)
A DESCRIPTION OF THE PROBLEM :
java.sql.Timestamp valueOf() is coded very inefficently and
also doesn't do very much validation of the strings it is
being fed for correctness. This suggested enhancement greatly
reduces object churn by total elimination of
String.substring() and String.indexing operations. Also is
not dependent on Interger.parseInt() which was forcing much
of the substringing. Code also significantly upgrades
correctness checking. For example, you can feed the
existing code "2002-01-23 -12:-13:-45.-196" and it will
happily chew it down and give back an interesting result.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
Current source code:
public static Timestamp valueOf(String s) {
String date_s;
String time_s;
String nanos_s;
int year;
int month;
int day;
int hour;
int minute;
int second;
int a_nanos = 0;
int firstDash;
int secondDash;
int dividingSpace;
int firstColon = 0;
int secondColon = 0;
int period = 0;
String formatError = "Timestamp format must be yyyy-mm-dd
hh:mm:ss.fffffffff";
String zeros = "000000000";
if (s == null) throw new java.lang.IllegalArgumentException("null
string");
// Split the string into date and time components
s = s.trim();
dividingSpace = s.indexOf(' ');
if (dividingSpace > 0) {
date_s = s.substring(0,dividingSpace);
time_s = s.substring(dividingSpace+1);
} else {
throw new java.lang.IllegalArgumentException(formatError);
}
// Parse the date
firstDash = date_s.indexOf('-');
secondDash = date_s.indexOf('-', firstDash+1);
// Parse the time
if (time_s == null)
throw new java.lang.IllegalArgumentException(formatError);
firstColon = time_s.indexOf(':');
secondColon = time_s.indexOf(':', firstColon+1);
period = time_s.indexOf('.', secondColon+1);
// Convert the date
if ((firstDash > 0) & (secondDash > 0) &
(secondDash < date_s.length()-1)) {
year = Integer.parseInt(date_s.substring(0, firstDash)) - 1900;
month =
Integer.parseInt(date_s.substring
(firstDash+1, secondDash)) - 1;
day = Integer.parseInt(date_s.substring(secondDash+1));
} else {
throw new java.lang.IllegalArgumentException(formatError);
}
// Convert the time; default missing nanos
if ((firstColon > 0) & (secondColon > 0) &
(secondColon < time_s.length()-1)) {
hour = Integer.parseInt(time_s.substring(0, firstColon));
minute =
Integer.parseInt(time_s.substring(firstColon+1, secondColon));
if ((period > 0) & (period < time_s.length()-1)) {
second =
Integer.parseInt(time_s.substring(secondColon+1, period));
nanos_s = time_s.substring(period+1);
if (nanos_s.length() > 9)
throw new java.lang.IllegalArgumentException(formatError);
if (!Character.isDigit(nanos_s.charAt(0)))
throw new java.lang.IllegalArgumentException(formatError);
nanos_s = nanos_s + zeros.substring(0,9-nanos_s.length());
a_nanos = Integer.parseInt(nanos_s);
} else if (period > 0) {
throw new java.lang.IllegalArgumentException(formatError);
} else {
second = Integer.parseInt(time_s.substring(secondColon+1));
}
} else {
throw new java.lang.IllegalArgumentException();
}
return new Timestamp(year, month, day, hour, minute, second, a_nanos);
}
Suggested enhancement:
public static java.sql.Timestamp valueOf(String s) {
if (s == null) {
throw new java.lang.IllegalArgumentException(s);
}
String formatException = "Timestamp format must be yyyy-mm-dd
hh:mm:ss.fffffffff: ";
char buf[] = s.toCharArray();
// 0-1-2 3:4:5.6
int results[] = {0,0,0,0,0,0,0};
int r = 0; // results index to 0
int i=0; // buf index to 0
while (i < buf.length) {
char c = buf[i++];
if (c == '-') {
if (r++ > 1) {
// '-' only valid at 0 and 1
throw new java.lang.IllegalArgumentException
(formatException + s);
}
} else if (c == ' ') {
if (r++ != 2) {
// ' ' can only be used at 2
throw new java.lang.IllegalArgumentException
(formatException + s);
}
} else if (c == ':') {
if ((r != 3) && (r != 4)) {
// ':' only valid at 3 and 4
throw new java.lang.IllegalArgumentException
(formatException + s);
}
r++;
} else if (c == '.') {
if (r++ != 5) {
// '.' only valid at 5
throw new java.lang.IllegalArgumentException
(formatException + s);
}
} else {
int digit = Character.digit(c, 10);
if (digit < 0) {
throw new java.lang.IllegalArgumentException
(formatException + s);
}
if (results[r] >= 100000000) {
// None of the fields should exceed 999999999
// Checked here to catch before it hits MAX_INTEGER
throw new java.lang.IllegalArgumentException
(formatException + s);
}
results[r] = (results[r] * 10) + digit;
}
}
// Validate results
if (
// Diddn't reach end of buffer
(i != buf.length) ||
// Didn't parse all of our int's, but we do allow for missing nanos
(r < (results.length - 2)) ||
// Year is too large
(results[0] > 9999) ||
// Month is too large
((results[1] == 0) || (results[1] > 12)) ||
// Day out of range
((results[2] == 0) || (results[2] > 31)) ||
// Hours too large
(results[3] > 23) ||
// Minutes too large
(results[4] > 59) ||
// Seconds too large
(results[5] > 59)
// No need to check nanos, done above
) {
throw new java.lang.IllegalArgumentException(formatException + s);
}
// Now scale the nanos up, if they aren't 0
if (results[6] != 0) {
while (results[6] <= 99999999) {
results[6] *= 10;
}
}
// Create and return new EITimestamp
return new Timestamp(results[0] - 1900, results[1] - 1, results[2],
results[3], results[4], results[5],
results[6]);
}
---------- END SOURCE ----------
(Review ID: 153596)
======================================================================
FULL PRODUCT VERSION :
java version "1.4.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0-b92)
Java HotSpot(TM) Client VM (build 1.4.0-b92, mixed mode)
A DESCRIPTION OF THE PROBLEM :
java.sql.Timestamp valueOf() is coded very inefficently and
also doesn't do very much validation of the strings it is
being fed for correctness. This suggested enhancement greatly
reduces object churn by total elimination of
String.substring() and String.indexing operations. Also is
not dependent on Interger.parseInt() which was forcing much
of the substringing. Code also significantly upgrades
correctness checking. For example, you can feed the
existing code "2002-01-23 -12:-13:-45.-196" and it will
happily chew it down and give back an interesting result.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
Current source code:
public static Timestamp valueOf(String s) {
String date_s;
String time_s;
String nanos_s;
int year;
int month;
int day;
int hour;
int minute;
int second;
int a_nanos = 0;
int firstDash;
int secondDash;
int dividingSpace;
int firstColon = 0;
int secondColon = 0;
int period = 0;
String formatError = "Timestamp format must be yyyy-mm-dd
hh:mm:ss.fffffffff";
String zeros = "000000000";
if (s == null) throw new java.lang.IllegalArgumentException("null
string");
// Split the string into date and time components
s = s.trim();
dividingSpace = s.indexOf(' ');
if (dividingSpace > 0) {
date_s = s.substring(0,dividingSpace);
time_s = s.substring(dividingSpace+1);
} else {
throw new java.lang.IllegalArgumentException(formatError);
}
// Parse the date
firstDash = date_s.indexOf('-');
secondDash = date_s.indexOf('-', firstDash+1);
// Parse the time
if (time_s == null)
throw new java.lang.IllegalArgumentException(formatError);
firstColon = time_s.indexOf(':');
secondColon = time_s.indexOf(':', firstColon+1);
period = time_s.indexOf('.', secondColon+1);
// Convert the date
if ((firstDash > 0) & (secondDash > 0) &
(secondDash < date_s.length()-1)) {
year = Integer.parseInt(date_s.substring(0, firstDash)) - 1900;
month =
Integer.parseInt(date_s.substring
(firstDash+1, secondDash)) - 1;
day = Integer.parseInt(date_s.substring(secondDash+1));
} else {
throw new java.lang.IllegalArgumentException(formatError);
}
// Convert the time; default missing nanos
if ((firstColon > 0) & (secondColon > 0) &
(secondColon < time_s.length()-1)) {
hour = Integer.parseInt(time_s.substring(0, firstColon));
minute =
Integer.parseInt(time_s.substring(firstColon+1, secondColon));
if ((period > 0) & (period < time_s.length()-1)) {
second =
Integer.parseInt(time_s.substring(secondColon+1, period));
nanos_s = time_s.substring(period+1);
if (nanos_s.length() > 9)
throw new java.lang.IllegalArgumentException(formatError);
if (!Character.isDigit(nanos_s.charAt(0)))
throw new java.lang.IllegalArgumentException(formatError);
nanos_s = nanos_s + zeros.substring(0,9-nanos_s.length());
a_nanos = Integer.parseInt(nanos_s);
} else if (period > 0) {
throw new java.lang.IllegalArgumentException(formatError);
} else {
second = Integer.parseInt(time_s.substring(secondColon+1));
}
} else {
throw new java.lang.IllegalArgumentException();
}
return new Timestamp(year, month, day, hour, minute, second, a_nanos);
}
Suggested enhancement:
public static java.sql.Timestamp valueOf(String s) {
if (s == null) {
throw new java.lang.IllegalArgumentException(s);
}
String formatException = "Timestamp format must be yyyy-mm-dd
hh:mm:ss.fffffffff: ";
char buf[] = s.toCharArray();
// 0-1-2 3:4:5.6
int results[] = {0,0,0,0,0,0,0};
int r = 0; // results index to 0
int i=0; // buf index to 0
while (i < buf.length) {
char c = buf[i++];
if (c == '-') {
if (r++ > 1) {
// '-' only valid at 0 and 1
throw new java.lang.IllegalArgumentException
(formatException + s);
}
} else if (c == ' ') {
if (r++ != 2) {
// ' ' can only be used at 2
throw new java.lang.IllegalArgumentException
(formatException + s);
}
} else if (c == ':') {
if ((r != 3) && (r != 4)) {
// ':' only valid at 3 and 4
throw new java.lang.IllegalArgumentException
(formatException + s);
}
r++;
} else if (c == '.') {
if (r++ != 5) {
// '.' only valid at 5
throw new java.lang.IllegalArgumentException
(formatException + s);
}
} else {
int digit = Character.digit(c, 10);
if (digit < 0) {
throw new java.lang.IllegalArgumentException
(formatException + s);
}
if (results[r] >= 100000000) {
// None of the fields should exceed 999999999
// Checked here to catch before it hits MAX_INTEGER
throw new java.lang.IllegalArgumentException
(formatException + s);
}
results[r] = (results[r] * 10) + digit;
}
}
// Validate results
if (
// Diddn't reach end of buffer
(i != buf.length) ||
// Didn't parse all of our int's, but we do allow for missing nanos
(r < (results.length - 2)) ||
// Year is too large
(results[0] > 9999) ||
// Month is too large
((results[1] == 0) || (results[1] > 12)) ||
// Day out of range
((results[2] == 0) || (results[2] > 31)) ||
// Hours too large
(results[3] > 23) ||
// Minutes too large
(results[4] > 59) ||
// Seconds too large
(results[5] > 59)
// No need to check nanos, done above
) {
throw new java.lang.IllegalArgumentException(formatException + s);
}
// Now scale the nanos up, if they aren't 0
if (results[6] != 0) {
while (results[6] <= 99999999) {
results[6] *= 10;
}
}
// Create and return new EITimestamp
return new Timestamp(results[0] - 1900, results[1] - 1, results[2],
results[3], results[4], results[5],
results[6]);
}
---------- END SOURCE ----------
(Review ID: 153596)
======================================================================
- relates to
-
JDK-8055055 Improve numeric parsing in java.sql
-
- Resolved
-
-
JDK-5006540 java.sql.Timestamp.valueOf(String) fails to throw an IllegalArgumentException
-
- Closed
-