From: Barry Lind Date: Tue, 11 Dec 2001 04:48:05 +0000 (+0000) Subject: Applied patch from Thomas O'Dowd that fixes timestamp parsing. The jdbc code X-Git-Tag: REL7_2_BETA4~2 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=3dd85bcb08440bae6210114124aa0002a83c957c;p=postgresql Applied patch from Thomas O'Dowd that fixes timestamp parsing. The jdbc code wasn't updated to handle more than two decimal digits for fractional seconds that now are possible in 7.2. This patch fixes the timestamp parsing logic. I have built and tested on both jdbc1 and jdbc2. --- diff --git a/src/interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java b/src/interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java index 7fea1dab6b..b20d2f8a88 100644 --- a/src/interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java +++ b/src/interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java @@ -491,6 +491,14 @@ public class ResultSet extends org.postgresql.ResultSet implements java.sql.Resu /* * Get the value of a column in the current row as a * java.sql.Timestamp object + * + * The driver is set to return ISO date formated strings. We modify this + * string from the ISO format to a format that Java can understand. Java + * expects timezone info as 'GMT+09:00' where as ISO gives '+09'. + * Java also expects fractional seconds to 3 places where postgres + * will give, none, 2 or 6 depending on the time and postgres version. + * From version 7.2 postgres returns fractional seconds to 6 places. + * If available, we drop the last 3 digits. * * @param columnIndex the first column is 1, the second is 2... * @return the column value; null if SQL NULL @@ -499,102 +507,80 @@ public class ResultSet extends org.postgresql.ResultSet implements java.sql.Resu public Timestamp getTimestamp(int columnIndex) throws SQLException { String s = getString(columnIndex); + if (s == null) return null; - boolean subsecond; - //if string contains a '.' we have fractional seconds - if (s.indexOf('.') == -1) - { - subsecond = false; - } - else - { - subsecond = true; - } + StringBuffer sbuf = new StringBuffer(s); + SimpleDateFormat df = null; - //here we are modifying the string from ISO format to a format java can understand - //java expects timezone info as 'GMT-08:00' instead of '-08' in postgres ISO format - //and java expects three digits if fractional seconds are present instead of two for postgres - //so this code strips off timezone info and adds on the GMT+/-... - //as well as adds a third digit for partial seconds if necessary - StringBuffer strBuf = new StringBuffer(s); - - //we are looking to see if the backend has appended on a timezone. - //currently postgresql will return +/-HH:MM or +/-HH for timezone offset - //(i.e. -06, or +06:30, note the expectation of the leading zero for the - //hours, and the use of the : for delimiter between hours and minutes) - //if the backend ISO format changes in the future this code will - //need to be changed as well - char sub = strBuf.charAt(strBuf.length() - 3); - if (sub == '+' || sub == '-') + if (s.length() > 19) { - strBuf.setLength(strBuf.length() - 3); - if (subsecond) - { - strBuf.append('0').append("GMT").append(s.substring(s.length() - 3, s.length())).append(":00"); - } - else - { - strBuf.append("GMT").append(s.substring(s.length() - 3, s.length())).append(":00"); - } - } - else if (sub == ':') - { - //we may have found timezone info of format +/-HH:MM, or there is no - //timezone info at all and this is the : preceding the seconds - char sub2 = strBuf.charAt(strBuf.length() - 5); - if (sub2 == '+' || sub2 == '-') + // The len of the ISO string to the second value is 19 chars. If + // greater then 19, there should be tz info and perhaps fractional + // second info which we need to change to java to read it. + + // cut the copy to second value "2001-12-07 16:29:22" + int i = 19; + sbuf.setLength(i); + + char c = s.charAt(i++); + if (c == '.') { - //we have found timezone info of format +/-HH:MM - strBuf.setLength(strBuf.length() - 5); - if (subsecond) - { - strBuf.append('0').append("GMT").append(s.substring(s.length() - 5)); - } - else + // Found a fractional value. Append up to 3 digits including + // the leading '.' + do { - strBuf.append("GMT").append(s.substring(s.length() - 5)); - } + if (i < 24) + sbuf.append(c); + c = s.charAt(i++); + } while (Character.isDigit(c)); + + // If there wasn't at least 3 digits we should add some zeros + // to make up the 3 digits we tell java to expect. + for (int j = i; j < 24; j++) + sbuf.append('0'); } - else if (subsecond) + else { - strBuf.append('0'); + // No fractional seconds, lets add some. + sbuf.append(".000"); } - } - else if (subsecond) - { - strBuf = strBuf.append('0'); - } - s = strBuf.toString(); + // prepend the GMT part and then add the remaining bit of + // the string. + sbuf.append(" GMT"); + sbuf.append(c); + sbuf.append(s.substring(i, s.length())); - SimpleDateFormat df = null; + // Lastly, if the tz part doesn't specify the :MM part then + // we add ":00" for java. + if (s.length() - i < 5) + sbuf.append(":00"); - if (s.length() > 23 && subsecond) - { - df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSzzzzzzzzz"); - } - else if (s.length() > 23 && !subsecond) - { - df = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzzzzzzzz"); - } - else if (s.length() > 10 && subsecond) - { - df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + // we'll use this dateformat string to parse the result. + df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS z"); } - else if (s.length() > 10 && !subsecond) + else if (s.length() == 19) { + // No tz or fractional second info. + // I'm not sure if it is + // possible to have a string in this format, as pg + // should give us tz qualified timestamps back, but it was + // in the old code, so I'm handling it for now. df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } else { + // We must just have a date. This case is + // needed if this method is called on a date column df = new SimpleDateFormat("yyyy-MM-dd"); } try { - return new Timestamp(df.parse(s).getTime()); + // All that's left is to parse the string and return the ts. + return new Timestamp(df.parse(sbuf.toString()).getTime()); } catch (ParseException e) { diff --git a/src/interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java b/src/interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java index 66e7b4d3fa..565847db9f 100644 --- a/src/interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java +++ b/src/interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java @@ -1591,115 +1591,113 @@ public class ResultSet extends org.postgresql.ResultSet implements java.sql.Resu } } - public static Timestamp toTimestamp(String s, ResultSet resultSet) throws SQLException + /** + * Parse a string and return a timestamp representing its value. + * + * The driver is set to return ISO date formated strings. We modify this + * string from the ISO format to a format that Java can understand. Java + * expects timezone info as 'GMT+09:00' where as ISO gives '+09'. + * Java also expects fractional seconds to 3 places where postgres + * will give, none, 2 or 6 depending on the time and postgres version. + * From version 7.2 postgres returns fractional seconds to 6 places. + * If available, we drop the last 3 digits. + * + * @param s The ISO formated date string to parse. + * @param resultSet The ResultSet this date is part of. + * + * @return null if s is null or a timestamp of the parsed string s. + * + * @throws SQLException if there is a problem parsing s. + **/ + public static Timestamp toTimestamp(String s, ResultSet resultSet) + throws SQLException { if (s == null) return null; - boolean subsecond; - //if string contains a '.' we have fractional seconds - if (s.indexOf('.') == -1) - { - subsecond = false; - } - else - { - subsecond = true; - } - - //here we are modifying the string from ISO format to a format java can understand - //java expects timezone info as 'GMT-08:00' instead of '-08' in postgres ISO format - //and java expects three digits if fractional seconds are present instead of two for postgres - //so this code strips off timezone info and adds on the GMT+/-... - //as well as adds a third digit for partial seconds if necessary + // We must be synchronized here incase more theads access the ResultSet + // bad practice but possible. Anyhow this is to protect sbuf and + // SimpleDateFormat objects synchronized (resultSet) { - // We must be synchronized here incase more theads access the ResultSet - // bad practice but possible. Anyhow this is to protect sbuf and - // SimpleDateFormat objects + SimpleDateFormat df = null; - // First time? + // If first time, create the buffer, otherwise clear it. if (resultSet.sbuf == null) resultSet.sbuf = new StringBuffer(); + else + resultSet.sbuf.setLength(0); - resultSet.sbuf.setLength(0); + // Copy s into sbuf for parsing. resultSet.sbuf.append(s); - //we are looking to see if the backend has appended on a timezone. - //currently postgresql will return +/-HH:MM or +/-HH for timezone offset - //(i.e. -06, or +06:30, note the expectation of the leading zero for the - //hours, and the use of the : for delimiter between hours and minutes) - //if the backend ISO format changes in the future this code will - //need to be changed as well - char sub = resultSet.sbuf.charAt(resultSet.sbuf.length() - 3); - if (sub == '+' || sub == '-') + if (s.length() > 19) { - //we have found timezone info of format +/-HH + // The len of the ISO string to the second value is 19 chars. If + // greater then 19, there should be tz info and perhaps fractional + // second info which we need to change to java to read it. - resultSet.sbuf.setLength(resultSet.sbuf.length() - 3); - if (subsecond) - { - resultSet.sbuf.append('0').append("GMT").append(s.substring(s.length() - 3)).append(":00"); - } - else - { - resultSet.sbuf.append("GMT").append(s.substring(s.length() - 3)).append(":00"); - } - } - else if (sub == ':') - { - //we may have found timezone info of format +/-HH:MM, or there is no - //timezone info at all and this is the : preceding the seconds - char sub2 = resultSet.sbuf.charAt(resultSet.sbuf.length() - 5); - if (sub2 == '+' || sub2 == '-') + // cut the copy to second value "2001-12-07 16:29:22" + int i = 19; + resultSet.sbuf.setLength(i); + + char c = s.charAt(i++); + if (c == '.') { - //we have found timezone info of format +/-HH:MM - resultSet.sbuf.setLength(resultSet.sbuf.length() - 5); - if (subsecond) - { - resultSet.sbuf.append('0').append("GMT").append(s.substring(s.length() - 5)); - } - else + // Found a fractional value. Append up to 3 digits including + // the leading '.' + do { - resultSet.sbuf.append("GMT").append(s.substring(s.length() - 5)); - } + if (i < 24) + resultSet.sbuf.append(c); + c = s.charAt(i++); + } while (Character.isDigit(c)); + + // If there wasn't at least 3 digits we should add some zeros + // to make up the 3 digits we tell java to expect. + for (int j = i; j < 24; j++) + resultSet.sbuf.append('0'); } - else if (subsecond) + else { - resultSet.sbuf.append('0'); + // No fractional seconds, lets add some. + resultSet.sbuf.append(".000"); } - } - else if (subsecond) - { - resultSet.sbuf.append('0'); - } - // could optimize this a tad to remove too many object creations... - SimpleDateFormat df = null; + // prepend the GMT part and then add the remaining bit of + // the string. + resultSet.sbuf.append(" GMT"); + resultSet.sbuf.append(c); + resultSet.sbuf.append(s.substring(i, s.length())); - if (resultSet.sbuf.length() > 23 && subsecond) - { - df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSzzzzzzzzz"); - } - else if (resultSet.sbuf.length() > 23 && !subsecond) - { - df = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzzzzzzzz"); - } - else if (resultSet.sbuf.length() > 10 && subsecond) - { - df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + // Lastly, if the tz part doesn't specify the :MM part then + // we add ":00" for java. + if (s.length() - i < 5) + resultSet.sbuf.append(":00"); + + // we'll use this dateformat string to parse the result. + df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS z"); } - else if (resultSet.sbuf.length() > 10 && !subsecond) + else if (s.length() == 19) { + // No tz or fractional second info. + // I'm not sure if it is + // possible to have a string in this format, as pg + // should give us tz qualified timestamps back, but it was + // in the old code, so I'm handling it for now. df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } else { + // We must just have a date. This case is + // needed if this method is called on a date + // column df = new SimpleDateFormat("yyyy-MM-dd"); } try { + // All that's left is to parse the string and return the ts. return new Timestamp(df.parse(resultSet.sbuf.toString()).getTime()); } catch (ParseException e) @@ -1708,7 +1706,5 @@ public class ResultSet extends org.postgresql.ResultSet implements java.sql.Resu } } } - - }