From feefc329bd64ee552d12c8e40bed76e3573c3fa4 Mon Sep 17 00:00:00 2001 From: Barry Lind Date: Tue, 14 Jan 2003 09:13:51 +0000 Subject: [PATCH] Patch from Florian Wunderlich to correctly support java Timestamps. Previously the code would only capture milliseconds where as both postgres and the java Timestamp object support greater resolution. Also fixed a bug reported by Rhett Sutphin where the last digit of the fractional seconds was lost when using timestamp without time zone Modified Files: jdbc/org/postgresql/jdbc1/AbstractJdbc1ResultSet.java jdbc/org/postgresql/test/jdbc2/TimestampTest.java --- .../jdbc1/AbstractJdbc1ResultSet.java | 73 +++++++++++++------ .../postgresql/test/jdbc2/TimestampTest.java | 45 ++++++++---- 2 files changed, 83 insertions(+), 35 deletions(-) diff --git a/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1ResultSet.java b/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1ResultSet.java index f75ab70d16..48e8b8f70b 100644 --- a/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1ResultSet.java +++ b/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1ResultSet.java @@ -13,7 +13,7 @@ import org.postgresql.largeobject.*; import org.postgresql.util.PGbytea; import org.postgresql.util.PSQLException; -/* $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc1/Attic/AbstractJdbc1ResultSet.java,v 1.7 2002/10/19 22:10:36 barry Exp $ +/* $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc1/Attic/AbstractJdbc1ResultSet.java,v 1.8 2003/01/14 09:13:51 barry Exp $ * This class defines methods of the jdbc1 specification. This class is * extended by org.postgresql.jdbc2.AbstractJdbc2ResultSet which adds the jdbc2 * methods. The real ResultSet class (for jdbc1) is org.postgresql.jdbc1.Jdbc1ResultSet @@ -844,7 +844,14 @@ public abstract class AbstractJdbc1ResultSet * 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. + * + * According to the Timestamp documentation, fractional digits are kept + * in the nanos field of Timestamp and not in the milliseconds of Date. + * Thus, parsing for fractional digits is entirely separated from the + * rest. + * + * The method assumes that there are no more than 9 fractional + * digits given. Undefined behavior if this is not the case. * * @param s The ISO formated date string to parse. * @param resultSet The ResultSet this date is part of. @@ -881,6 +888,13 @@ public abstract class AbstractJdbc1ResultSet rs.sbuf.append(s); int slen = s.length(); + // For a Timestamp, the fractional seconds are stored in the + // nanos field. As a DateFormat is used for parsing which can + // only parse to millisecond precision and which returns a + // Date object, the fractional second parsing is completely + // separate. + int nanos = 0; + if (slen > 19) { // The len of the ISO string to the second value is 19 chars. If @@ -894,25 +908,36 @@ public abstract class AbstractJdbc1ResultSet char c = s.charAt(i++); if (c == '.') { - // Found a fractional value. Append up to 3 digits including - // the leading '.' - do + // Found a fractional value. + final int start = i; + while (true) { - if (i < 24) - rs.sbuf.append(c); c = s.charAt(i++); + if (!Character.isDigit(c)) + break; + if (i == slen) + { + i++; + break; + } } - while (i < slen && 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++) - rs.sbuf.append('0'); - } - else - { - // No fractional seconds, lets add some. - rs.sbuf.append(".000"); + // The range [start, i - 1) contains all fractional digits. + final int end = i - 1; + try + { + nanos = Integer.parseInt(s.substring(start, end)); + } + catch (NumberFormatException e) + { + throw new PSQLException("postgresql.unusual", e); + } + + // The nanos field stores nanoseconds. Adjust the parsed + // value to the correct magnitude. + for (int digitsToNano = 9 - (end - start); + digitsToNano > 0; --digitsToNano) + nanos *= 10; } if (i < slen) @@ -929,7 +954,7 @@ public abstract class AbstractJdbc1ResultSet rs.sbuf.append(":00"); // we'll use this dateformat string to parse the result. - df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS z"); + df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); } else { @@ -938,11 +963,11 @@ public abstract class AbstractJdbc1ResultSet if (pgDataType.equals("timestamptz")) { rs.sbuf.append(" GMT"); - df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS z"); + df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); } else { - df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } } } @@ -981,9 +1006,13 @@ public abstract class AbstractJdbc1ResultSet { // All that's left is to parse the string and return the ts. if ( org.postgresql.Driver.logDebug ) - org.postgresql.Driver.debug( "" + df.parse(rs.sbuf.toString()).getTime() ); + org.postgresql.Driver.debug("the data after parsing is " + + rs.sbuf.toString() + " with " + nanos + " nanos"); - return new Timestamp(df.parse(rs.sbuf.toString()).getTime()); + Timestamp result = + new Timestamp(df.parse(rs.sbuf.toString()).getTime()); + result.setNanos(nanos); + return result; } catch (ParseException e) { diff --git a/src/interfaces/jdbc/org/postgresql/test/jdbc2/TimestampTest.java b/src/interfaces/jdbc/org/postgresql/test/jdbc2/TimestampTest.java index 2472a3d3d8..a5307e3b42 100644 --- a/src/interfaces/jdbc/org/postgresql/test/jdbc2/TimestampTest.java +++ b/src/interfaces/jdbc/org/postgresql/test/jdbc2/TimestampTest.java @@ -5,7 +5,7 @@ import junit.framework.TestCase; import java.sql.*; /* - * $Id: TimestampTest.java,v 1.9 2002/09/06 21:23:06 momjian Exp $ + * $Id: TimestampTest.java,v 1.10 2003/01/14 09:13:51 barry Exp $ * * Test get/setTimestamp for both timestamp with time zone and * timestamp without time zone datatypes @@ -52,11 +52,12 @@ public class TimestampTest extends TestCase assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL(TSWTZ_TABLE, "'" + TS1WTZ_PGFORMAT + "'"))); assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL(TSWTZ_TABLE, "'" + TS2WTZ_PGFORMAT + "'"))); assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL(TSWTZ_TABLE, "'" + TS3WTZ_PGFORMAT + "'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL(TSWTZ_TABLE, "'" + TS4WTZ_PGFORMAT + "'"))); // Fall through helper timestampTestWTZ(); - assertEquals(3, stmt.executeUpdate("DELETE FROM " + TSWTZ_TABLE)); + assertEquals(4, stmt.executeUpdate("DELETE FROM " + TSWTZ_TABLE)); stmt.close(); } @@ -88,10 +89,13 @@ public class TimestampTest extends TestCase pstmt.setTimestamp(1, TS3WTZ); assertEquals(1, pstmt.executeUpdate()); + pstmt.setTimestamp(1, TS4WTZ); + assertEquals(1, pstmt.executeUpdate()); + // Fall through helper timestampTestWTZ(); - assertEquals(3, stmt.executeUpdate("DELETE FROM " + TSWTZ_TABLE)); + assertEquals(4, stmt.executeUpdate("DELETE FROM " + TSWTZ_TABLE)); pstmt.close(); stmt.close(); @@ -117,11 +121,12 @@ public class TimestampTest extends TestCase assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL(TSWOTZ_TABLE, "'" + TS1WOTZ_PGFORMAT + "'"))); assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL(TSWOTZ_TABLE, "'" + TS2WOTZ_PGFORMAT + "'"))); assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL(TSWOTZ_TABLE, "'" + TS3WOTZ_PGFORMAT + "'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL(TSWOTZ_TABLE, "'" + TS4WOTZ_PGFORMAT + "'"))); // Fall through helper timestampTestWOTZ(); - assertEquals(3, stmt.executeUpdate("DELETE FROM " + TSWOTZ_TABLE)); + assertEquals(4, stmt.executeUpdate("DELETE FROM " + TSWOTZ_TABLE)); stmt.close(); } @@ -154,10 +159,13 @@ public class TimestampTest extends TestCase pstmt.setTimestamp(1, TS3WOTZ); assertEquals(1, pstmt.executeUpdate()); + pstmt.setTimestamp(1, TS4WOTZ); + assertEquals(1, pstmt.executeUpdate()); + // Fall through helper timestampTestWOTZ(); - assertEquals(3, stmt.executeUpdate("DELETE FROM " + TSWOTZ_TABLE)); + assertEquals(4, stmt.executeUpdate("DELETE FROM " + TSWOTZ_TABLE)); pstmt.close(); stmt.close(); @@ -195,6 +203,11 @@ public class TimestampTest extends TestCase assertNotNull(t); assertTrue(t.equals(TS3WTZ)); + assertTrue(rs.next()); + t = rs.getTimestamp(1); + assertNotNull(t); + assertTrue(t.equals(TS4WTZ)); + assertTrue(! rs.next()); // end of table. Fail if more entries exist. rs.close(); @@ -216,17 +229,22 @@ public class TimestampTest extends TestCase assertTrue(rs.next()); t = rs.getTimestamp(1); assertNotNull(t); - assertTrue(t.toString().equals(TS1WOTZ_JAVAFORMAT)); + assertTrue(t.equals(TS1WOTZ)); assertTrue(rs.next()); t = rs.getTimestamp(1); assertNotNull(t); - assertTrue(t.toString().equals(TS2WOTZ_JAVAFORMAT)); + assertTrue(t.equals(TS2WOTZ)); assertTrue(rs.next()); t = rs.getTimestamp(1); assertNotNull(t); - assertTrue(t.toString().equals(TS3WOTZ_JAVAFORMAT)); + assertTrue(t.equals(TS3WOTZ)); + + assertTrue(rs.next()); + t = rs.getTimestamp(1); + assertNotNull(t); + assertTrue(t.equals(TS4WOTZ)); assertTrue(! rs.next()); // end of table. Fail if more entries exist. @@ -277,20 +295,21 @@ public class TimestampTest extends TestCase private static final java.sql.Timestamp TS3WTZ = getTimestamp(2000, 7, 7, 15, 0, 0, 123000000, "GMT"); private static final String TS3WTZ_PGFORMAT = "2000-07-07 15:00:00.123+00"; + private static final java.sql.Timestamp TS4WTZ = getTimestamp(2000, 7, 7, 15, 0, 0, 123456000, "GMT"); + private static final String TS4WTZ_PGFORMAT = "2000-07-07 15:00:00.123456+00"; + private static final java.sql.Timestamp TS1WOTZ = getTimestamp(1950, 2, 7, 15, 0, 0, 100000000, null); private static final String TS1WOTZ_PGFORMAT = "1950-02-07 15:00:00.1"; - private static final String TS1WOTZ_JAVAFORMAT = "1950-02-07 15:00:00.1"; private static final java.sql.Timestamp TS2WOTZ = getTimestamp(2000, 2, 7, 15, 0, 0, 120000000, null); private static final String TS2WOTZ_PGFORMAT = "2000-02-07 15:00:00.12"; - //there is probably a bug here in that this needs to be .1 instead of .12, but I couldn't find it now - private static final String TS2WOTZ_JAVAFORMAT = "2000-02-07 15:00:00.1"; private static final java.sql.Timestamp TS3WOTZ = getTimestamp(2000, 7, 7, 15, 0, 0, 123000000, null); private static final String TS3WOTZ_PGFORMAT = "2000-07-07 15:00:00.123"; - //there is probably a bug here in that this needs to be .12 instead of .123, but I couldn't find it now - private static final String TS3WOTZ_JAVAFORMAT = "2000-07-07 15:00:00.12"; + + private static final java.sql.Timestamp TS4WOTZ = getTimestamp(2000, 7, 7, 15, 0, 0, 123456000, null); + private static final String TS4WOTZ_PGFORMAT = "2000-07-07 15:00:00.123456"; private static final String TSWTZ_TABLE = "testtimestampwtz"; private static final String TSWOTZ_TABLE = "testtimestampwotz";