JDK-4808845 : Enahnce java.sql.Timestamp#valueOf() efficiency and robustness
  • Type: Enhancement
  • Component: core-libs
  • Sub-Component: java.sql
  • Affected Version: 1.4.0
  • Priority: P4
  • Status: Resolved
  • Resolution: Duplicate
  • OS: windows_2000
  • CPU: x86
  • Submitted: 2003-01-27
  • Updated: 2014-09-25
  • Resolved: 2014-09-25
Related Reports
Relates :  
Relates :  
Description
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) 
======================================================================

Comments
This is a duplicate of 8055055
25-09-2014

EVALUATION The method needs to be corrected to do appropriate checks for Strings and minimize inclusion of operations that lead to inefficient code.
12-09-2006

EVALUATION fip Target for consideration in Tiger platform. ###@###.### 2003-01-28 Actively working on this for JDBC 4.0 platform. ###@###.### 2004-02-06
28-01-2003