Thread: Patch for JDBC timestamp problems

Patch for JDBC timestamp problems

From
Barry Lind
Date:
Attached is a set of patches for a couple of bugs dealing with
timestamps in JDBC.

Bug#1) Incorrect timestamp stored in DB if client timezone different
than DB.

The buggy implementation of setTimestamp() in PreparedStatement simply
used the toString() method of the java.sql.Timestamp object to convert
to a string to send to the database.  The format of this is yyyy-MM-dd
hh:mm:ss.SSS which doesn't include any timezone information.  Therefore
the DB assumes its timezone since none is specified.  That is OK if the
timezone of the client and server are the same, however if they are
different the wrong timestamp is received by the server.  For example if
the client is running in timezone GMT and wants to send the timestamp
for noon to a server running in PST (GMT-8 hours), then the server will
receive 2000-01-12 12:00:00.0 and interprete it as 2000-01-12
12:00:00-08 which is 2000-01-12 04:00:00 in GMT.  The fix is to send a
format to the server that includes the timezone offset.  For simplicity
sake the fix uses a SimpleDateFormat object with its timezone set to GMT
so that '+00' can be used as the timezone for postgresql.  This is done
as SimpleDateFormat doesn't support formating timezones in the way
postgresql expects.

Bug#2) Incorrect handling of partial seconds in getting timestamps from
the DB

When the SimpleDateFormat object parses a string with a format like
yyyy-MM-dd hh:mm:ss.SS it expects the fractional seconds to be three
decimal places (time precision in java is miliseconds = three decimal
places).  This seems like a bug in java to me, but it is unlikely to be
fixed anytime soon, so the postgresql code needed modification to
support the java behaviour.  So for example a string of '2000-01-12
12:00:00.12-08' coming from the database was being converted to a
timestamp object with a value of 2000-01-12 12:00:00.012GMT-08:00.  The
fix was to check for a '.' in the string and if one is found append on
an extra zero to the fractional seconds part.

Bug#3) Performance problems

In fixing the above two bugs, I noticed some things that could be
improved.  In PreparedStatement.setTimestamp(),
PreparedStatement.setDate(), ResultSet.getTimestamp(), and
ResultSet.getDate() these methods were creating a new SimpleDateFormat
object everytime they were called.  To avoid this unnecessary object
creation overhead, I changed the code to use static variables for
keeping a single instance of the needed formating objects.
Also the code used the + operator for string concatenation.  As everyone
should know this is very inefficient and the use of StringBuffers is
prefered.

I also did some cleanup in ResultSet.getTimestamp().  This method has
had multiple patches applied some of which resulted in code that was no
longer needed.  For example the ISO timestamp format that postgresql
uses specifies the timezone as an offset like '-08'.  Code was added at
one point to convert the postgresql format to the java one which is
GMT-08:00, however the old code was left around which did nothing.  So
there was code that looked for yyyy-MM-dd hh:mm:sszzzzzzzzz and
yyyy-MM-dd hh:mm:sszzz.  This second format would never be encountered
because zzz (i.e. -08) would be converted into the former (also note
that the SimpleDateFormat object treats zzzzzzzzz and zzz the same, the
number of z's does not matter).


There was another problem/fix mentioned on the email lists today by
mcannon@internet.com which is also fixed by this patch:

Bug#4) Fractional seconds lost when getting timestamp from the DB
A patch by Jan Thomea handled the case of yyyy-MM-dd hh:mm:sszzzzzzzzz
but not the fractional seconds version yyyy-MM-dd hh:mm:ss.SSzzzzzzzzz.
The code is fixed to handle this case as well.

thanks,
--Barry*** interfaces/jdbc/org/postgresql/jdbc1/PreparedStatement.java.orig    Fri Jan 12 17:17:48 2001
--- interfaces/jdbc/org/postgresql/jdbc1/PreparedStatement.java    Fri Jan 12 17:27:28 2001
***************
*** 310,321 ****
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
      public void setDate(int parameterIndex, java.sql.Date x) throws SQLException
      {
!       SimpleDateFormat df = new SimpleDateFormat("''yyyy-MM-dd''");
!
!       set(parameterIndex, df.format(x));
!
        // The above is how the date should be handled.
        //
        // However, in JDK's prior to 1.1.6 (confirmed with the
--- 310,320 ----
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
+         private static final SimpleDateFormat DF1 = new SimpleDateFormat("yyyy-MM-dd");
      public void setDate(int parameterIndex, java.sql.Date x) throws SQLException
      {
!       set(parameterIndex, DF1.format(x));
!
        // The above is how the date should be handled.
        //
        // However, in JDK's prior to 1.1.6 (confirmed with the
***************
*** 349,357 ****
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
      public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException
!     {
!         set(parameterIndex, "'" + x.toString() + "'");
      }

      /**
--- 348,364 ----
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
+         private static SimpleDateFormat DF2 = getDF2();
+         private static SimpleDateFormat getDF2() {
+           SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+           sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
+           return sdf;
+         }
      public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException
!         {
!             StringBuffer strBuf = new StringBuffer("'");
!             strBuf.append(DF2.format(x)).append('.').append(x.getNanos()/10000000).append("+00'");
!         set(parameterIndex, strBuf.toString());
      }

      /**
*** interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java.orig    Fri Jan 12 17:18:45 2001
--- interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java    Fri Jan 12 17:25:02 2001
***************
*** 412,420 ****
      String s = getString(columnIndex);
      if(s==null)
        return null;
-     SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
      try {
!       return new java.sql.Date(df.parse(s).getTime());
      } catch (ParseException e) {
        throw new PSQLException("postgresql.res.baddate",new Integer(e.getErrorOffset()),s);
      }
--- 412,419 ----
      String s = getString(columnIndex);
      if(s==null)
        return null;
      try {
!       return new java.sql.Date(DF5.parse(s).getTime());
      } catch (ParseException e) {
        throw new PSQLException("postgresql.res.baddate",new Integer(e.getErrorOffset()),s);
      }
***************
*** 457,486 ****
     * @return the column value; null if SQL NULL
     * @exception SQLException if a database access error occurs
     */
    public Timestamp getTimestamp(int columnIndex) throws SQLException
    {
      String s = getString(columnIndex);
      if(s==null)
      return null;
!
!     // This works, but it's commented out because Michael Stephenson's
!     // solution is better still:
!     //SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
!
!     // Michael Stephenson's solution:
      SimpleDateFormat df = null;
!     if (s.length()>21 && s.indexOf('.') != -1) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSzzz");
!     } else if (s.length()>19 && s.indexOf('.') == -1) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:sszzz");
!     } else if (s.length()>19 && s.indexOf('.') != -1) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss.SS");
!     } else if (s.length()>10 && s.length()<=18) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss");
      } else {
!     df = new SimpleDateFormat("yyyy-MM-dd");
      }
!
      try {
      return new Timestamp(df.parse(s).getTime());
      } catch(ParseException e) {
--- 456,514 ----
     * @return the column value; null if SQL NULL
     * @exception SQLException if a database access error occurs
     */
+   private static final SimpleDateFormat DF1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSzzzzzzzzz");
+   private static final SimpleDateFormat DF2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzzzzzzzz");
+   private static final SimpleDateFormat DF3 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+   private static final SimpleDateFormat DF4 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+   private static final SimpleDateFormat DF5 = new SimpleDateFormat("yyyy-MM-dd");
    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;
!     }
!
!     //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);
!     char sub = strBuf.charAt(strBuf.length()-3);
!     if (sub == '+' || sub == '-') {
!       strBuf.setLength(strBuf.length()-3);
!       if (subsecond)  {
!         strBuf = strBuf.append('0').append("GMT").append(s.substring(s.length()-3, s.length())).append(":00");
!       } else {
!         strBuf = strBuf.append("GMT").append(s.substring(s.length()-3, s.length())).append(":00");
!       }
!     } else if (subsecond) {
!       strBuf = strBuf.append('0');
!     }
!
!     s = strBuf.toString();
!
      SimpleDateFormat df = null;
!
!     if (s.length()>23 && subsecond) {
!       df = DF1;
!     } else if (s.length()>23 && !subsecond) {
!       df = DF2;
!     } else if (s.length()>10 && subsecond) {
!       df = DF3;
!     } else if (s.length()>10 && !subsecond) {
!       df = DF4;
      } else {
!       df = DF5;
      }
!
      try {
      return new Timestamp(df.parse(s).getTime());
      } catch(ParseException e) {
*** interfaces/jdbc/org/postgresql/jdbc2/PreparedStatement.java.orig    Fri Jan 12 17:40:55 2001
--- interfaces/jdbc/org/postgresql/jdbc2/PreparedStatement.java    Fri Jan 12 17:47:42 2001
***************
*** 310,321 ****
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
      public void setDate(int parameterIndex, java.sql.Date x) throws SQLException
      {
!       SimpleDateFormat df = new SimpleDateFormat("''yyyy-MM-dd''");
!
!       set(parameterIndex, df.format(x));
!
        // The above is how the date should be handled.
        //
        // However, in JDK's prior to 1.1.6 (confirmed with the
--- 310,320 ----
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
+         private static final SimpleDateFormat DF1 = new SimpleDateFormat("yyyy-MM-dd");
      public void setDate(int parameterIndex, java.sql.Date x) throws SQLException
      {
!       set(parameterIndex, DF1.format(x));
!
        // The above is how the date should be handled.
        //
        // However, in JDK's prior to 1.1.6 (confirmed with the
***************
*** 349,357 ****
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
      public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException
!     {
!         set(parameterIndex, "'" + x.toString() + "'");
      }

      /**
--- 348,364 ----
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
+         private static SimpleDateFormat DF2 = getDF2();
+         private static SimpleDateFormat getDF2() {
+           SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+           sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
+           return sdf;
+         }
      public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException
!         {
!             StringBuffer strBuf = new StringBuffer("'");
!             strBuf.append(DF2.format(x)).append('.').append(x.getNanos()/10000000).append("+00'");
!         set(parameterIndex, strBuf.toString());
      }

      /**
*** interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java.orig    Fri Jan 12 16:47:28 2001
--- interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java    Fri Jan 12 17:03:03 2001
***************
*** 415,423 ****
      String s = getString(columnIndex);
      if(s==null)
        return null;
-     SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
      try {
!       return new java.sql.Date(df.parse(s).getTime());
      } catch (ParseException e) {
        throw new PSQLException("postgresql.res.baddate",new Integer(e.getErrorOffset()),s);
      }
--- 415,422 ----
      String s = getString(columnIndex);
      if(s==null)
        return null;
      try {
!       return new java.sql.Date(DF5.parse(s).getTime());
      } catch (ParseException e) {
        throw new PSQLException("postgresql.res.baddate",new Integer(e.getErrorOffset()),s);
      }
***************
*** 460,506 ****
     * @return the column value; null if SQL NULL
     * @exception SQLException if a database access error occurs
     */
    public Timestamp getTimestamp(int columnIndex) throws SQLException
    {
      String s = getString(columnIndex);
      if(s==null)
      return null;
!
!     // This works, but it's commented out because Michael Stephenson's
!     // solution is better still:
!     //SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
! // Modification by Jan Thomae
!     String sub = s.substring(s.length() - 3, s.length()-2);
!     if (sub.equals("+") || sub.equals("-")) {
!             s = s.substring(0, s.length()-3) + "GMT"+ s.substring(s.length()-3, s.length())+":00";
      }
! // -------
!        // Michael Stephenson's solution:
      SimpleDateFormat df = null;

! // Modification by Jan Thomae
!     if (s.length()>27) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzzzzzzzz");
!     } else
! // -------
!     if (s.length()>21 && s.indexOf('.') != -1) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSzzz");
!     } else if (s.length()>19 && s.indexOf('.') == -1) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:sszzz");
!     } else if (s.length()>19 && s.indexOf('.') != -1) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss.SS");
!     } else if (s.length()>10 && s.length()<=18) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss");
      } else {
!     df = new SimpleDateFormat("yyyy-MM-dd");
      }
!
      try {
      return new Timestamp(df.parse(s).getTime());
      } catch(ParseException e) {
      throw new PSQLException("postgresql.res.badtimestamp",new Integer(e.getErrorOffset()),s);
      }
    }

    /**
     * A column value can be retrieved as a stream of ASCII characters
--- 459,524 ----
     * @return the column value; null if SQL NULL
     * @exception SQLException if a database access error occurs
     */
+   private static final SimpleDateFormat DF1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSzzzzzzzzz");
+   private static final SimpleDateFormat DF2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzzzzzzzz");
+   private static final SimpleDateFormat DF3 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+   private static final SimpleDateFormat DF4 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+   private static final SimpleDateFormat DF5 = new SimpleDateFormat("yyyy-MM-dd");
    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;
!     }
!
!     //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);
!     char sub = strBuf.charAt(strBuf.length()-3);
!     if (sub == '+' || sub == '-') {
!       strBuf.setLength(strBuf.length()-3);
!       if (subsecond)  {
!         strBuf = strBuf.append('0').append("GMT").append(s.substring(s.length()-3, s.length())).append(":00");
!       } else {
!         strBuf = strBuf.append("GMT").append(s.substring(s.length()-3, s.length())).append(":00");
!       }
!     } else if (subsecond) {
!       strBuf = strBuf.append('0');
      }
!
!     s = strBuf.toString();
!
      SimpleDateFormat df = null;

!     if (s.length()>23 && subsecond) {
!       df = DF1;
!     } else if (s.length()>23 && !subsecond) {
!       df = DF2;
!     } else if (s.length()>10 && subsecond) {
!       df = DF3;
!     } else if (s.length()>10 && !subsecond) {
!       df = DF4;
      } else {
!       df = DF5;
      }
!
      try {
      return new Timestamp(df.parse(s).getTime());
      } catch(ParseException e) {
      throw new PSQLException("postgresql.res.badtimestamp",new Integer(e.getErrorOffset()),s);
      }
    }
+

    /**
     * A column value can be retrieved as a stream of ASCII characters

Re: Patch for JDBC timestamp problems

From
Bruce Momjian
Date:
> There was another problem/fix mentioned on the email lists today by
> mcannon@internet.com which is also fixed by this patch:

Oh, great, I see this patch fixes Mike Cannon's problem too.  I will
apply it to the main tree now, and it will appear in 7.1.  If you have
any more improvements, please send them over.  Thanks.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026

Re: Patch for JDBC timestamp problems

From
Bruce Momjian
Date:
Thanks.  Applied.

---------------------------------------------------------------------------

*** interfaces/jdbc/org/postgresql/jdbc1/PreparedStatement.java.orig    Fri Jan 12 17:17:48 2001
--- interfaces/jdbc/org/postgresql/jdbc1/PreparedStatement.java    Fri Jan 12 17:27:28 2001
***************
*** 310,321 ****
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
      public void setDate(int parameterIndex, java.sql.Date x) throws SQLException
      {
!       SimpleDateFormat df = new SimpleDateFormat("''yyyy-MM-dd''");
!
!       set(parameterIndex, df.format(x));
!
        // The above is how the date should be handled.
        //
        // However, in JDK's prior to 1.1.6 (confirmed with the
--- 310,320 ----
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
+         private static final SimpleDateFormat DF1 = new SimpleDateFormat("yyyy-MM-dd");
      public void setDate(int parameterIndex, java.sql.Date x) throws SQLException
      {
!       set(parameterIndex, DF1.format(x));
!
        // The above is how the date should be handled.
        //
        // However, in JDK's prior to 1.1.6 (confirmed with the
***************
*** 349,357 ****
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
      public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException
!     {
!         set(parameterIndex, "'" + x.toString() + "'");
      }

      /**
--- 348,364 ----
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
+         private static SimpleDateFormat DF2 = getDF2();
+         private static SimpleDateFormat getDF2() {
+           SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+           sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
+           return sdf;
+         }
      public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException
!         {
!             StringBuffer strBuf = new StringBuffer("'");
!             strBuf.append(DF2.format(x)).append('.').append(x.getNanos()/10000000).append("+00'");
!         set(parameterIndex, strBuf.toString());
      }

      /**
*** interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java.orig    Fri Jan 12 17:18:45 2001
--- interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java    Fri Jan 12 17:25:02 2001
***************
*** 412,420 ****
      String s = getString(columnIndex);
      if(s==null)
        return null;
-     SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
      try {
!       return new java.sql.Date(df.parse(s).getTime());
      } catch (ParseException e) {
        throw new PSQLException("postgresql.res.baddate",new Integer(e.getErrorOffset()),s);
      }
--- 412,419 ----
      String s = getString(columnIndex);
      if(s==null)
        return null;
      try {
!       return new java.sql.Date(DF5.parse(s).getTime());
      } catch (ParseException e) {
        throw new PSQLException("postgresql.res.baddate",new Integer(e.getErrorOffset()),s);
      }
***************
*** 457,486 ****
     * @return the column value; null if SQL NULL
     * @exception SQLException if a database access error occurs
     */
    public Timestamp getTimestamp(int columnIndex) throws SQLException
    {
      String s = getString(columnIndex);
      if(s==null)
      return null;
!
!     // This works, but it's commented out because Michael Stephenson's
!     // solution is better still:
!     //SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
!
!     // Michael Stephenson's solution:
      SimpleDateFormat df = null;
!     if (s.length()>21 && s.indexOf('.') != -1) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSzzz");
!     } else if (s.length()>19 && s.indexOf('.') == -1) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:sszzz");
!     } else if (s.length()>19 && s.indexOf('.') != -1) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss.SS");
!     } else if (s.length()>10 && s.length()<=18) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss");
      } else {
!     df = new SimpleDateFormat("yyyy-MM-dd");
      }
!
      try {
      return new Timestamp(df.parse(s).getTime());
      } catch(ParseException e) {
--- 456,514 ----
     * @return the column value; null if SQL NULL
     * @exception SQLException if a database access error occurs
     */
+   private static final SimpleDateFormat DF1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSzzzzzzzzz");
+   private static final SimpleDateFormat DF2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzzzzzzzz");
+   private static final SimpleDateFormat DF3 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+   private static final SimpleDateFormat DF4 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+   private static final SimpleDateFormat DF5 = new SimpleDateFormat("yyyy-MM-dd");
    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;
!     }
!
!     //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);
!     char sub = strBuf.charAt(strBuf.length()-3);
!     if (sub == '+' || sub == '-') {
!       strBuf.setLength(strBuf.length()-3);
!       if (subsecond)  {
!         strBuf = strBuf.append('0').append("GMT").append(s.substring(s.length()-3, s.length())).append(":00");
!       } else {
!         strBuf = strBuf.append("GMT").append(s.substring(s.length()-3, s.length())).append(":00");
!       }
!     } else if (subsecond) {
!       strBuf = strBuf.append('0');
!     }
!
!     s = strBuf.toString();
!
      SimpleDateFormat df = null;
!
!     if (s.length()>23 && subsecond) {
!       df = DF1;
!     } else if (s.length()>23 && !subsecond) {
!       df = DF2;
!     } else if (s.length()>10 && subsecond) {
!       df = DF3;
!     } else if (s.length()>10 && !subsecond) {
!       df = DF4;
      } else {
!       df = DF5;
      }
!
      try {
      return new Timestamp(df.parse(s).getTime());
      } catch(ParseException e) {
*** interfaces/jdbc/org/postgresql/jdbc2/PreparedStatement.java.orig    Fri Jan 12 17:40:55 2001
--- interfaces/jdbc/org/postgresql/jdbc2/PreparedStatement.java    Fri Jan 12 17:47:42 2001
***************
*** 310,321 ****
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
      public void setDate(int parameterIndex, java.sql.Date x) throws SQLException
      {
!       SimpleDateFormat df = new SimpleDateFormat("''yyyy-MM-dd''");
!
!       set(parameterIndex, df.format(x));
!
        // The above is how the date should be handled.
        //
        // However, in JDK's prior to 1.1.6 (confirmed with the
--- 310,320 ----
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
+         private static final SimpleDateFormat DF1 = new SimpleDateFormat("yyyy-MM-dd");
      public void setDate(int parameterIndex, java.sql.Date x) throws SQLException
      {
!       set(parameterIndex, DF1.format(x));
!
        // The above is how the date should be handled.
        //
        // However, in JDK's prior to 1.1.6 (confirmed with the
***************
*** 349,357 ****
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
      public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException
!     {
!         set(parameterIndex, "'" + x.toString() + "'");
      }

      /**
--- 348,364 ----
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
+         private static SimpleDateFormat DF2 = getDF2();
+         private static SimpleDateFormat getDF2() {
+           SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+           sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
+           return sdf;
+         }
      public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException
!         {
!             StringBuffer strBuf = new StringBuffer("'");
!             strBuf.append(DF2.format(x)).append('.').append(x.getNanos()/10000000).append("+00'");
!         set(parameterIndex, strBuf.toString());
      }

      /**
*** interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java.orig    Fri Jan 12 16:47:28 2001
--- interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java    Fri Jan 12 17:03:03 2001
***************
*** 415,423 ****
      String s = getString(columnIndex);
      if(s==null)
        return null;
-     SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
      try {
!       return new java.sql.Date(df.parse(s).getTime());
      } catch (ParseException e) {
        throw new PSQLException("postgresql.res.baddate",new Integer(e.getErrorOffset()),s);
      }
--- 415,422 ----
      String s = getString(columnIndex);
      if(s==null)
        return null;
      try {
!       return new java.sql.Date(DF5.parse(s).getTime());
      } catch (ParseException e) {
        throw new PSQLException("postgresql.res.baddate",new Integer(e.getErrorOffset()),s);
      }
***************
*** 460,506 ****
     * @return the column value; null if SQL NULL
     * @exception SQLException if a database access error occurs
     */
    public Timestamp getTimestamp(int columnIndex) throws SQLException
    {
      String s = getString(columnIndex);
      if(s==null)
      return null;
!
!     // This works, but it's commented out because Michael Stephenson's
!     // solution is better still:
!     //SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
! // Modification by Jan Thomae
!     String sub = s.substring(s.length() - 3, s.length()-2);
!     if (sub.equals("+") || sub.equals("-")) {
!             s = s.substring(0, s.length()-3) + "GMT"+ s.substring(s.length()-3, s.length())+":00";
      }
! // -------
!        // Michael Stephenson's solution:
      SimpleDateFormat df = null;

! // Modification by Jan Thomae
!     if (s.length()>27) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzzzzzzzz");
!     } else
! // -------
!     if (s.length()>21 && s.indexOf('.') != -1) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSzzz");
!     } else if (s.length()>19 && s.indexOf('.') == -1) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:sszzz");
!     } else if (s.length()>19 && s.indexOf('.') != -1) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss.SS");
!     } else if (s.length()>10 && s.length()<=18) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss");
      } else {
!     df = new SimpleDateFormat("yyyy-MM-dd");
      }
!
      try {
      return new Timestamp(df.parse(s).getTime());
      } catch(ParseException e) {
      throw new PSQLException("postgresql.res.badtimestamp",new Integer(e.getErrorOffset()),s);
      }
    }

    /**
     * A column value can be retrieved as a stream of ASCII characters
--- 459,524 ----
     * @return the column value; null if SQL NULL
     * @exception SQLException if a database access error occurs
     */
+   private static final SimpleDateFormat DF1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSzzzzzzzzz");
+   private static final SimpleDateFormat DF2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzzzzzzzz");
+   private static final SimpleDateFormat DF3 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+   private static final SimpleDateFormat DF4 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+   private static final SimpleDateFormat DF5 = new SimpleDateFormat("yyyy-MM-dd");
    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;
!     }
!
!     //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);
!     char sub = strBuf.charAt(strBuf.length()-3);
!     if (sub == '+' || sub == '-') {
!       strBuf.setLength(strBuf.length()-3);
!       if (subsecond)  {
!         strBuf = strBuf.append('0').append("GMT").append(s.substring(s.length()-3, s.length())).append(":00");
!       } else {
!         strBuf = strBuf.append("GMT").append(s.substring(s.length()-3, s.length())).append(":00");
!       }
!     } else if (subsecond) {
!       strBuf = strBuf.append('0');
      }
!
!     s = strBuf.toString();
!
      SimpleDateFormat df = null;

!     if (s.length()>23 && subsecond) {
!       df = DF1;
!     } else if (s.length()>23 && !subsecond) {
!       df = DF2;
!     } else if (s.length()>10 && subsecond) {
!       df = DF3;
!     } else if (s.length()>10 && !subsecond) {
!       df = DF4;
      } else {
!       df = DF5;
      }
!
      try {
      return new Timestamp(df.parse(s).getTime());
      } catch(ParseException e) {
      throw new PSQLException("postgresql.res.badtimestamp",new Integer(e.getErrorOffset()),s);
      }
    }
+

    /**
     * A column value can be retrieved as a stream of ASCII characters


--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026

Re: Patch for JDBC timestamp problems

From
Bruce Momjian
Date:
I have applied this patch.  It fixes a number of bugs, even one that was
just reported a few day ago.  Thanks.


---------------------------------------------------------------------------


Attached is a set of patches for a couple of bugs dealing with
timestamps in JDBC.

Bug#1) Incorrect timestamp stored in DB if client timezone different
than DB.

The buggy implementation of setTimestamp() in PreparedStatement simply
used the toString() method of the java.sql.Timestamp object to convert
to a string to send to the database.  The format of this is yyyy-MM-dd
hh:mm:ss.SSS which doesn't include any timezone information.  Therefore
the DB assumes its timezone since none is specified.  That is OK if the
timezone of the client and server are the same, however if they are
different the wrong timestamp is received by the server.  For example if
the client is running in timezone GMT and wants to send the timestamp
for noon to a server running in PST (GMT-8 hours), then the server will
receive 2000-01-12 12:00:00.0 and interprete it as 2000-01-12
12:00:00-08 which is 2000-01-12 04:00:00 in GMT.  The fix is to send a
format to the server that includes the timezone offset.  For simplicity
sake the fix uses a SimpleDateFormat object with its timezone set to GMT
so that '+00' can be used as the timezone for postgresql.  This is done
as SimpleDateFormat doesn't support formating timezones in the way
postgresql expects.

Bug#2) Incorrect handling of partial seconds in getting timestamps from
the DB

When the SimpleDateFormat object parses a string with a format like
yyyy-MM-dd hh:mm:ss.SS it expects the fractional seconds to be three
decimal places (time precision in java is miliseconds = three decimal
places).  This seems like a bug in java to me, but it is unlikely to be
fixed anytime soon, so the postgresql code needed modification to
support the java behaviour.  So for example a string of '2000-01-12
12:00:00.12-08' coming from the database was being converted to a
timestamp object with a value of 2000-01-12 12:00:00.012GMT-08:00.  The
fix was to check for a '.' in the string and if one is found append on
an extra zero to the fractional seconds part.

Bug#3) Performance problems

In fixing the above two bugs, I noticed some things that could be
improved.  In PreparedStatement.setTimestamp(),
PreparedStatement.setDate(), ResultSet.getTimestamp(), and
ResultSet.getDate() these methods were creating a new SimpleDateFormat
object everytime they were called.  To avoid this unnecessary object
creation overhead, I changed the code to use static variables for
keeping a single instance of the needed formating objects.
Also the code used the + operator for string concatenation.  As everyone
should know this is very inefficient and the use of StringBuffers is
prefered.

I also did some cleanup in ResultSet.getTimestamp().  This method has
had multiple patches applied some of which resulted in code that was no
longer needed.  For example the ISO timestamp format that postgresql
uses specifies the timezone as an offset like '-08'.  Code was added at
one point to convert the postgresql format to the java one which is
GMT-08:00, however the old code was left around which did nothing.  So
there was code that looked for yyyy-MM-dd hh:mm:sszzzzzzzzz and
yyyy-MM-dd hh:mm:sszzz.  This second format would never be encountered
because zzz (i.e. -08) would be converted into the former (also note
that the SimpleDateFormat object treats zzzzzzzzz and zzz the same, the
number of z's does not matter).


There was another problem/fix mentioned on the email lists today by
mcannon@internet.com which is also fixed by this patch:

Bug#4) Fractional seconds lost when getting timestamp from the DB
A patch by Jan Thomea handled the case of yyyy-MM-dd hh:mm:sszzzzzzzzz
but not the fractional seconds version yyyy-MM-dd hh:mm:ss.SSzzzzzzzzz.
The code is fixed to handle this case as well.

thanks,
--Barry
--------------F902A91CC78EA5B0218A576D
Content-Type: text/plain; charset=UTF-8; name="patch.diff"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline; filename="patch.diff"

*** interfaces/jdbc/org/postgresql/jdbc1/PreparedStatement.java.orig    Fri Jan 12 17:17:48 2001
--- interfaces/jdbc/org/postgresql/jdbc1/PreparedStatement.java    Fri Jan 12 17:27:28 2001
***************
*** 310,321 ****
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
      public void setDate(int parameterIndex, java.sql.Date x) throws SQLException
      {
!       SimpleDateFormat df = new SimpleDateFormat("''yyyy-MM-dd''");
!
!       set(parameterIndex, df.format(x));
!
        // The above is how the date should be handled.
        //
        // However, in JDK's prior to 1.1.6 (confirmed with the
--- 310,320 ----
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
+         private static final SimpleDateFormat DF1 = new SimpleDateFormat("yyyy-MM-dd");
      public void setDate(int parameterIndex, java.sql.Date x) throws SQLException
      {
!       set(parameterIndex, DF1.format(x));
!
        // The above is how the date should be handled.
        //
        // However, in JDK's prior to 1.1.6 (confirmed with the
***************
*** 349,357 ****
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
      public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException
!     {
!         set(parameterIndex, "'" + x.toString() + "'");
      }

      /**
--- 348,364 ----
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
+         private static SimpleDateFormat DF2 = getDF2();
+         private static SimpleDateFormat getDF2() {
+           SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+           sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
+           return sdf;
+         }
      public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException
!         {
!             StringBuffer strBuf = new StringBuffer("'");
!             strBuf.append(DF2.format(x)).append('.').append(x.getNanos()/10000000).append("+00'");
!         set(parameterIndex, strBuf.toString());
      }

      /**
*** interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java.orig    Fri Jan 12 17:18:45 2001
--- interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java    Fri Jan 12 17:25:02 2001
***************
*** 412,420 ****
      String s = getString(columnIndex);
      if(s==null)
        return null;
-     SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
      try {
!       return new java.sql.Date(df.parse(s).getTime());
      } catch (ParseException e) {
        throw new PSQLException("postgresql.res.baddate",new Integer(e.getErrorOffset()),s);
      }
--- 412,419 ----
      String s = getString(columnIndex);
      if(s==null)
        return null;
      try {
!       return new java.sql.Date(DF5.parse(s).getTime());
      } catch (ParseException e) {
        throw new PSQLException("postgresql.res.baddate",new Integer(e.getErrorOffset()),s);
      }
***************
*** 457,486 ****
     * @return the column value; null if SQL NULL
     * @exception SQLException if a database access error occurs
     */
    public Timestamp getTimestamp(int columnIndex) throws SQLException
    {
      String s = getString(columnIndex);
      if(s==null)
      return null;
!
!     // This works, but it's commented out because Michael Stephenson's
!     // solution is better still:
!     //SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
!
!     // Michael Stephenson's solution:
      SimpleDateFormat df = null;
!     if (s.length()>21 && s.indexOf('.') != -1) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSzzz");
!     } else if (s.length()>19 && s.indexOf('.') == -1) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:sszzz");
!     } else if (s.length()>19 && s.indexOf('.') != -1) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss.SS");
!     } else if (s.length()>10 && s.length()<=18) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss");
      } else {
!     df = new SimpleDateFormat("yyyy-MM-dd");
      }
!
      try {
      return new Timestamp(df.parse(s).getTime());
      } catch(ParseException e) {
--- 456,514 ----
     * @return the column value; null if SQL NULL
     * @exception SQLException if a database access error occurs
     */
+   private static final SimpleDateFormat DF1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSzzzzzzzzz");
+   private static final SimpleDateFormat DF2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzzzzzzzz");
+   private static final SimpleDateFormat DF3 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+   private static final SimpleDateFormat DF4 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+   private static final SimpleDateFormat DF5 = new SimpleDateFormat("yyyy-MM-dd");
    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;
!     }
!
!     //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);
!     char sub = strBuf.charAt(strBuf.length()-3);
!     if (sub == '+' || sub == '-') {
!       strBuf.setLength(strBuf.length()-3);
!       if (subsecond)  {
!         strBuf = strBuf.append('0').append("GMT").append(s.substring(s.length()-3, s.length())).append(":00");
!       } else {
!         strBuf = strBuf.append("GMT").append(s.substring(s.length()-3, s.length())).append(":00");
!       }
!     } else if (subsecond) {
!       strBuf = strBuf.append('0');
!     }
!
!     s = strBuf.toString();
!
      SimpleDateFormat df = null;
!
!     if (s.length()>23 && subsecond) {
!       df = DF1;
!     } else if (s.length()>23 && !subsecond) {
!       df = DF2;
!     } else if (s.length()>10 && subsecond) {
!       df = DF3;
!     } else if (s.length()>10 && !subsecond) {
!       df = DF4;
      } else {
!       df = DF5;
      }
!
      try {
      return new Timestamp(df.parse(s).getTime());
      } catch(ParseException e) {
*** interfaces/jdbc/org/postgresql/jdbc2/PreparedStatement.java.orig    Fri Jan 12 17:40:55 2001
--- interfaces/jdbc/org/postgresql/jdbc2/PreparedStatement.java    Fri Jan 12 17:47:42 2001
***************
*** 310,321 ****
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
      public void setDate(int parameterIndex, java.sql.Date x) throws SQLException
      {
!       SimpleDateFormat df = new SimpleDateFormat("''yyyy-MM-dd''");
!
!       set(parameterIndex, df.format(x));
!
        // The above is how the date should be handled.
        //
        // However, in JDK's prior to 1.1.6 (confirmed with the
--- 310,320 ----
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
+         private static final SimpleDateFormat DF1 = new SimpleDateFormat("yyyy-MM-dd");
      public void setDate(int parameterIndex, java.sql.Date x) throws SQLException
      {
!       set(parameterIndex, DF1.format(x));
!
        // The above is how the date should be handled.
        //
        // However, in JDK's prior to 1.1.6 (confirmed with the
***************
*** 349,357 ****
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
      public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException
!     {
!         set(parameterIndex, "'" + x.toString() + "'");
      }

      /**
--- 348,364 ----
       * @param x the parameter value
       * @exception SQLException if a database access error occurs
       */
+         private static SimpleDateFormat DF2 = getDF2();
+         private static SimpleDateFormat getDF2() {
+           SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+           sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
+           return sdf;
+         }
      public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException
!         {
!             StringBuffer strBuf = new StringBuffer("'");
!             strBuf.append(DF2.format(x)).append('.').append(x.getNanos()/10000000).append("+00'");
!         set(parameterIndex, strBuf.toString());
      }

      /**
*** interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java.orig    Fri Jan 12 16:47:28 2001
--- interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java    Fri Jan 12 17:03:03 2001
***************
*** 415,423 ****
      String s = getString(columnIndex);
      if(s==null)
        return null;
-     SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
      try {
!       return new java.sql.Date(df.parse(s).getTime());
      } catch (ParseException e) {
        throw new PSQLException("postgresql.res.baddate",new Integer(e.getErrorOffset()),s);
      }
--- 415,422 ----
      String s = getString(columnIndex);
      if(s==null)
        return null;
      try {
!       return new java.sql.Date(DF5.parse(s).getTime());
      } catch (ParseException e) {
        throw new PSQLException("postgresql.res.baddate",new Integer(e.getErrorOffset()),s);
      }
***************
*** 460,506 ****
     * @return the column value; null if SQL NULL
     * @exception SQLException if a database access error occurs
     */
    public Timestamp getTimestamp(int columnIndex) throws SQLException
    {
      String s = getString(columnIndex);
      if(s==null)
      return null;
!
!     // This works, but it's commented out because Michael Stephenson's
!     // solution is better still:
!     //SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
! // Modification by Jan Thomae
!     String sub = s.substring(s.length() - 3, s.length()-2);
!     if (sub.equals("+") || sub.equals("-")) {
!             s = s.substring(0, s.length()-3) + "GMT"+ s.substring(s.length()-3, s.length())+":00";
      }
! // -------
!        // Michael Stephenson's solution:
      SimpleDateFormat df = null;

! // Modification by Jan Thomae
!     if (s.length()>27) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzzzzzzzz");
!     } else
! // -------
!     if (s.length()>21 && s.indexOf('.') != -1) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSzzz");
!     } else if (s.length()>19 && s.indexOf('.') == -1) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:sszzz");
!     } else if (s.length()>19 && s.indexOf('.') != -1) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss.SS");
!     } else if (s.length()>10 && s.length()<=18) {
!     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss");
      } else {
!     df = new SimpleDateFormat("yyyy-MM-dd");
      }
!
      try {
      return new Timestamp(df.parse(s).getTime());
      } catch(ParseException e) {
      throw new PSQLException("postgresql.res.badtimestamp",new Integer(e.getErrorOffset()),s);
      }
    }

    /**
     * A column value can be retrieved as a stream of ASCII characters
--- 459,524 ----
     * @return the column value; null if SQL NULL
     * @exception SQLException if a database access error occurs
     */
+   private static final SimpleDateFormat DF1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSzzzzzzzzz");
+   private static final SimpleDateFormat DF2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzzzzzzzz");
+   private static final SimpleDateFormat DF3 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+   private static final SimpleDateFormat DF4 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+   private static final SimpleDateFormat DF5 = new SimpleDateFormat("yyyy-MM-dd");
    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;
!     }
!
!     //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);
!     char sub = strBuf.charAt(strBuf.length()-3);
!     if (sub == '+' || sub == '-') {
!       strBuf.setLength(strBuf.length()-3);
!       if (subsecond)  {
!         strBuf = strBuf.append('0').append("GMT").append(s.substring(s.length()-3, s.length())).append(":00");
!       } else {
!         strBuf = strBuf.append("GMT").append(s.substring(s.length()-3, s.length())).append(":00");
!       }
!     } else if (subsecond) {
!       strBuf = strBuf.append('0');
      }
!
!     s = strBuf.toString();
!
      SimpleDateFormat df = null;

!     if (s.length()>23 && subsecond) {
!       df = DF1;
!     } else if (s.length()>23 && !subsecond) {
!       df = DF2;
!     } else if (s.length()>10 && subsecond) {
!       df = DF3;
!     } else if (s.length()>10 && !subsecond) {
!       df = DF4;
      } else {
!       df = DF5;
      }
!
      try {
      return new Timestamp(df.parse(s).getTime());
      } catch(ParseException e) {
      throw new PSQLException("postgresql.res.badtimestamp",new Integer(e.getErrorOffset()),s);
      }
    }
+

    /**
     * A column value can be retrieved as a stream of ASCII characters

--------------F902A91CC78EA5B0218A576D--




--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026

Re: Patch for JDBC timestamp problems

From
The Hermit Hacker
Date:
On Sat, 13 Jan 2001, Bruce Momjian wrote:

> Thanks.  Applied.
>

Can we start refraining from *reposting* the patch to the list?  We saw it
the first time, and the commit messages tell us it was applied ... *shrug*


Re: [INTERFACES] Re: Patch for JDBC timestamp problems

From
Bruce Momjian
Date:
> On Sat, 13 Jan 2001, Bruce Momjian wrote:
>
> > Thanks.  Applied.
> >
>
> Can we start refraining from *reposting* the patch to the list?  We saw it
> the first time, and the commit messages tell us it was applied ... *shrug*

OK.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026

Re: Patch for JDBC timestamp problems

From
Thomas Lockhart
Date:
> Oh, great, I see this patch fixes Mike Cannon's problem too.  I will
> apply it to the main tree now, and it will appear in 7.1.  If you have
> any more improvements, please send them over.  Thanks.

I'm confused, as usual. How does forcing the client time zone to GMT fix
the problem of mismatching client and server time zones? istm that you
still have a problem if the server is not on GMT.

Does creating a static variable affect the threadsafe capabilities of
the driver? If so, it shouldn't be done, but perhaps this is a feature
of the code already?

                      - Thomas

Re: Patch for JDBC timestamp problems

From
Bruce Momjian
Date:
> > Oh, great, I see this patch fixes Mike Cannon's problem too.  I will
> > apply it to the main tree now, and it will appear in 7.1.  If you have
> > any more improvements, please send them over.  Thanks.
>
> I'm confused, as usual. How does forcing the client time zone to GMT fix
> the problem of mismatching client and server time zones? istm that you
> still have a problem if the server is not on GMT.

No idea.  When it fixed someone's problem, I was excited.

>
> Does creating a static variable affect the threadsafe capabilities of
> the driver? If so, it shouldn't be done, but perhaps this is a feature
> of the code already?

That was a question I had too.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026

Re: Patch for JDBC timestamp problems

From
Thomas Lockhart
Date:
> > I'm confused, as usual. How does forcing the client time zone to GMT fix
> > the problem of mismatching client and server time zones? istm that you
> > still have a problem if the server is not on GMT.
> No idea.  When it fixed someone's problem, I was excited.

Well, afaict it might have broken most other people, so is not a big
win.

                      - Thomas

Re: Patch for JDBC timestamp problems

From
Bruce Momjian
Date:
> > > I'm confused, as usual. How does forcing the client time zone to GMT fix
> > > the problem of mismatching client and server time zones? istm that you
> > > still have a problem if the server is not on GMT.
> > No idea.  When it fixed someone's problem, I was excited.
>
> Well, afaict it might have broken most other people, so is not a big
> win.

That bad, huh?  The reason I posted the patch in the email is because I
don't understand Java and wanted to make sure other people reviewed it,
especially because we are in beta.

It dealt with date/time stuff, which made it double-complex.  I am ready
to yank it if someone gives the word.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026

Re: Patch for JDBC timestamp problems

From
The Hermit Hacker
Date:
On Sat, 13 Jan 2001, Bruce Momjian wrote:

> > > Oh, great, I see this patch fixes Mike Cannon's problem too.  I will
> > > apply it to the main tree now, and it will appear in 7.1.  If you have
> > > any more improvements, please send them over.  Thanks.
> >
> > I'm confused, as usual. How does forcing the client time zone to GMT fix
> > the problem of mismatching client and server time zones? istm that you
> > still have a problem if the server is not on GMT.
>
> No idea.  When it fixed someone's problem, I was excited.

Since it obviously creates potential problems with others, please reverse
said patch ASAP ...



Re: Patch for JDBC timestamp problems

From
The Hermit Hacker
Date:
On Sat, 13 Jan 2001, Bruce Momjian wrote:

> > > > I'm confused, as usual. How does forcing the client time zone to GMT fix
> > > > the problem of mismatching client and server time zones? istm that you
> > > > still have a problem if the server is not on GMT.
> > > No idea.  When it fixed someone's problem, I was excited.
> >
> > Well, afaict it might have broken most other people, so is not a big
> > win.
>
> That bad, huh?  The reason I posted the patch in the email is because I
> don't understand Java and wanted to make sure other people reviewed it,
> especially because we are in beta.

review is *before* you commit, not afterwards ... whether in beta period
or not ...

> It dealt with date/time stuff, which made it double-complex.  I am ready
> to yank it if someone gives the word.

consider it given ... and please refrain from letting your "excitement"
impair your judgement in future patch dealings ... review *before*, not
after ...



Re: Patch for JDBC timestamp problems

From
Bruce Momjian
Date:
I have backed out this patch because of concerns that have been raised.
I would be glad to re-apply it after these issues have been addressed.


> I have applied this patch.  It fixes a number of bugs, even one that was
> just reported a few day ago.  Thanks.
>
>
> ---------------------------------------------------------------------------
>
>
> Attached is a set of patches for a couple of bugs dealing with
> timestamps in JDBC.
>
> Bug#1) Incorrect timestamp stored in DB if client timezone different
> than DB.
>
> The buggy implementation of setTimestamp() in PreparedStatement simply
> used the toString() method of the java.sql.Timestamp object to convert
> to a string to send to the database.  The format of this is yyyy-MM-dd
> hh:mm:ss.SSS which doesn't include any timezone information.  Therefore
> the DB assumes its timezone since none is specified.  That is OK if the
> timezone of the client and server are the same, however if they are
> different the wrong timestamp is received by the server.  For example if
> the client is running in timezone GMT and wants to send the timestamp
> for noon to a server running in PST (GMT-8 hours), then the server will
> receive 2000-01-12 12:00:00.0 and interprete it as 2000-01-12
> 12:00:00-08 which is 2000-01-12 04:00:00 in GMT.  The fix is to send a
> format to the server that includes the timezone offset.  For simplicity
> sake the fix uses a SimpleDateFormat object with its timezone set to GMT
> so that '+00' can be used as the timezone for postgresql.  This is done
> as SimpleDateFormat doesn't support formating timezones in the way
> postgresql expects.
>
> Bug#2) Incorrect handling of partial seconds in getting timestamps from
> the DB
>
> When the SimpleDateFormat object parses a string with a format like
> yyyy-MM-dd hh:mm:ss.SS it expects the fractional seconds to be three
> decimal places (time precision in java is miliseconds = three decimal
> places).  This seems like a bug in java to me, but it is unlikely to be
> fixed anytime soon, so the postgresql code needed modification to
> support the java behaviour.  So for example a string of '2000-01-12
> 12:00:00.12-08' coming from the database was being converted to a
> timestamp object with a value of 2000-01-12 12:00:00.012GMT-08:00.  The
> fix was to check for a '.' in the string and if one is found append on
> an extra zero to the fractional seconds part.
>
> Bug#3) Performance problems
>
> In fixing the above two bugs, I noticed some things that could be
> improved.  In PreparedStatement.setTimestamp(),
> PreparedStatement.setDate(), ResultSet.getTimestamp(), and
> ResultSet.getDate() these methods were creating a new SimpleDateFormat
> object everytime they were called.  To avoid this unnecessary object
> creation overhead, I changed the code to use static variables for
> keeping a single instance of the needed formating objects.
> Also the code used the + operator for string concatenation.  As everyone
> should know this is very inefficient and the use of StringBuffers is
> prefered.
>
> I also did some cleanup in ResultSet.getTimestamp().  This method has
> had multiple patches applied some of which resulted in code that was no
> longer needed.  For example the ISO timestamp format that postgresql
> uses specifies the timezone as an offset like '-08'.  Code was added at
> one point to convert the postgresql format to the java one which is
> GMT-08:00, however the old code was left around which did nothing.  So
> there was code that looked for yyyy-MM-dd hh:mm:sszzzzzzzzz and
> yyyy-MM-dd hh:mm:sszzz.  This second format would never be encountered
> because zzz (i.e. -08) would be converted into the former (also note
> that the SimpleDateFormat object treats zzzzzzzzz and zzz the same, the
> number of z's does not matter).
>
>
> There was another problem/fix mentioned on the email lists today by
> mcannon@internet.com which is also fixed by this patch:
>
> Bug#4) Fractional seconds lost when getting timestamp from the DB
> A patch by Jan Thomea handled the case of yyyy-MM-dd hh:mm:sszzzzzzzzz
> but not the fractional seconds version yyyy-MM-dd hh:mm:ss.SSzzzzzzzzz.
> The code is fixed to handle this case as well.
>
> thanks,
> --Barry
> --------------F902A91CC78EA5B0218A576D
> Content-Type: text/plain; charset=UTF-8; name="patch.diff"
> Content-Transfer-Encoding: 7bit
> Content-Disposition: inline; filename="patch.diff"
>
> *** interfaces/jdbc/org/postgresql/jdbc1/PreparedStatement.java.orig    Fri Jan 12 17:17:48 2001
> --- interfaces/jdbc/org/postgresql/jdbc1/PreparedStatement.java    Fri Jan 12 17:27:28 2001
> ***************
> *** 310,321 ****
>        * @param x the parameter value
>        * @exception SQLException if a database access error occurs
>        */
>       public void setDate(int parameterIndex, java.sql.Date x) throws SQLException
>       {
> !       SimpleDateFormat df = new SimpleDateFormat("''yyyy-MM-dd''");
> !
> !       set(parameterIndex, df.format(x));
> !
>         // The above is how the date should be handled.
>         //
>         // However, in JDK's prior to 1.1.6 (confirmed with the
> --- 310,320 ----
>        * @param x the parameter value
>        * @exception SQLException if a database access error occurs
>        */
> +         private static final SimpleDateFormat DF1 = new SimpleDateFormat("yyyy-MM-dd");
>       public void setDate(int parameterIndex, java.sql.Date x) throws SQLException
>       {
> !       set(parameterIndex, DF1.format(x));
> !
>         // The above is how the date should be handled.
>         //
>         // However, in JDK's prior to 1.1.6 (confirmed with the
> ***************
> *** 349,357 ****
>        * @param x the parameter value
>        * @exception SQLException if a database access error occurs
>        */
>       public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException
> !     {
> !         set(parameterIndex, "'" + x.toString() + "'");
>       }
>
>       /**
> --- 348,364 ----
>        * @param x the parameter value
>        * @exception SQLException if a database access error occurs
>        */
> +         private static SimpleDateFormat DF2 = getDF2();
> +         private static SimpleDateFormat getDF2() {
> +           SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
> +           sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
> +           return sdf;
> +         }
>       public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException
> !         {
> !             StringBuffer strBuf = new StringBuffer("'");
> !             strBuf.append(DF2.format(x)).append('.').append(x.getNanos()/10000000).append("+00'");
> !         set(parameterIndex, strBuf.toString());
>       }
>
>       /**
> *** interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java.orig    Fri Jan 12 17:18:45 2001
> --- interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java    Fri Jan 12 17:25:02 2001
> ***************
> *** 412,420 ****
>       String s = getString(columnIndex);
>       if(s==null)
>         return null;
> -     SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
>       try {
> !       return new java.sql.Date(df.parse(s).getTime());
>       } catch (ParseException e) {
>         throw new PSQLException("postgresql.res.baddate",new Integer(e.getErrorOffset()),s);
>       }
> --- 412,419 ----
>       String s = getString(columnIndex);
>       if(s==null)
>         return null;
>       try {
> !       return new java.sql.Date(DF5.parse(s).getTime());
>       } catch (ParseException e) {
>         throw new PSQLException("postgresql.res.baddate",new Integer(e.getErrorOffset()),s);
>       }
> ***************
> *** 457,486 ****
>      * @return the column value; null if SQL NULL
>      * @exception SQLException if a database access error occurs
>      */
>     public Timestamp getTimestamp(int columnIndex) throws SQLException
>     {
>       String s = getString(columnIndex);
>       if(s==null)
>       return null;
> !
> !     // This works, but it's commented out because Michael Stephenson's
> !     // solution is better still:
> !     //SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
> !
> !     // Michael Stephenson's solution:
>       SimpleDateFormat df = null;
> !     if (s.length()>21 && s.indexOf('.') != -1) {
> !     df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSzzz");
> !     } else if (s.length()>19 && s.indexOf('.') == -1) {
> !     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:sszzz");
> !     } else if (s.length()>19 && s.indexOf('.') != -1) {
> !     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss.SS");
> !     } else if (s.length()>10 && s.length()<=18) {
> !     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss");
>       } else {
> !     df = new SimpleDateFormat("yyyy-MM-dd");
>       }
> !
>       try {
>       return new Timestamp(df.parse(s).getTime());
>       } catch(ParseException e) {
> --- 456,514 ----
>      * @return the column value; null if SQL NULL
>      * @exception SQLException if a database access error occurs
>      */
> +   private static final SimpleDateFormat DF1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSzzzzzzzzz");
> +   private static final SimpleDateFormat DF2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzzzzzzzz");
> +   private static final SimpleDateFormat DF3 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
> +   private static final SimpleDateFormat DF4 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
> +   private static final SimpleDateFormat DF5 = new SimpleDateFormat("yyyy-MM-dd");
>     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;
> !     }
> !
> !     //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);
> !     char sub = strBuf.charAt(strBuf.length()-3);
> !     if (sub == '+' || sub == '-') {
> !       strBuf.setLength(strBuf.length()-3);
> !       if (subsecond)  {
> !         strBuf = strBuf.append('0').append("GMT").append(s.substring(s.length()-3, s.length())).append(":00");
> !       } else {
> !         strBuf = strBuf.append("GMT").append(s.substring(s.length()-3, s.length())).append(":00");
> !       }
> !     } else if (subsecond) {
> !       strBuf = strBuf.append('0');
> !     }
> !
> !     s = strBuf.toString();
> !
>       SimpleDateFormat df = null;
> !
> !     if (s.length()>23 && subsecond) {
> !       df = DF1;
> !     } else if (s.length()>23 && !subsecond) {
> !       df = DF2;
> !     } else if (s.length()>10 && subsecond) {
> !       df = DF3;
> !     } else if (s.length()>10 && !subsecond) {
> !       df = DF4;
>       } else {
> !       df = DF5;
>       }
> !
>       try {
>       return new Timestamp(df.parse(s).getTime());
>       } catch(ParseException e) {
> *** interfaces/jdbc/org/postgresql/jdbc2/PreparedStatement.java.orig    Fri Jan 12 17:40:55 2001
> --- interfaces/jdbc/org/postgresql/jdbc2/PreparedStatement.java    Fri Jan 12 17:47:42 2001
> ***************
> *** 310,321 ****
>        * @param x the parameter value
>        * @exception SQLException if a database access error occurs
>        */
>       public void setDate(int parameterIndex, java.sql.Date x) throws SQLException
>       {
> !       SimpleDateFormat df = new SimpleDateFormat("''yyyy-MM-dd''");
> !
> !       set(parameterIndex, df.format(x));
> !
>         // The above is how the date should be handled.
>         //
>         // However, in JDK's prior to 1.1.6 (confirmed with the
> --- 310,320 ----
>        * @param x the parameter value
>        * @exception SQLException if a database access error occurs
>        */
> +         private static final SimpleDateFormat DF1 = new SimpleDateFormat("yyyy-MM-dd");
>       public void setDate(int parameterIndex, java.sql.Date x) throws SQLException
>       {
> !       set(parameterIndex, DF1.format(x));
> !
>         // The above is how the date should be handled.
>         //
>         // However, in JDK's prior to 1.1.6 (confirmed with the
> ***************
> *** 349,357 ****
>        * @param x the parameter value
>        * @exception SQLException if a database access error occurs
>        */
>       public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException
> !     {
> !         set(parameterIndex, "'" + x.toString() + "'");
>       }
>
>       /**
> --- 348,364 ----
>        * @param x the parameter value
>        * @exception SQLException if a database access error occurs
>        */
> +         private static SimpleDateFormat DF2 = getDF2();
> +         private static SimpleDateFormat getDF2() {
> +           SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
> +           sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
> +           return sdf;
> +         }
>       public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException
> !         {
> !             StringBuffer strBuf = new StringBuffer("'");
> !             strBuf.append(DF2.format(x)).append('.').append(x.getNanos()/10000000).append("+00'");
> !         set(parameterIndex, strBuf.toString());
>       }
>
>       /**
> *** interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java.orig    Fri Jan 12 16:47:28 2001
> --- interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java    Fri Jan 12 17:03:03 2001
> ***************
> *** 415,423 ****
>       String s = getString(columnIndex);
>       if(s==null)
>         return null;
> -     SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
>       try {
> !       return new java.sql.Date(df.parse(s).getTime());
>       } catch (ParseException e) {
>         throw new PSQLException("postgresql.res.baddate",new Integer(e.getErrorOffset()),s);
>       }
> --- 415,422 ----
>       String s = getString(columnIndex);
>       if(s==null)
>         return null;
>       try {
> !       return new java.sql.Date(DF5.parse(s).getTime());
>       } catch (ParseException e) {
>         throw new PSQLException("postgresql.res.baddate",new Integer(e.getErrorOffset()),s);
>       }
> ***************
> *** 460,506 ****
>      * @return the column value; null if SQL NULL
>      * @exception SQLException if a database access error occurs
>      */
>     public Timestamp getTimestamp(int columnIndex) throws SQLException
>     {
>       String s = getString(columnIndex);
>       if(s==null)
>       return null;
> !
> !     // This works, but it's commented out because Michael Stephenson's
> !     // solution is better still:
> !     //SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
> ! // Modification by Jan Thomae
> !     String sub = s.substring(s.length() - 3, s.length()-2);
> !     if (sub.equals("+") || sub.equals("-")) {
> !             s = s.substring(0, s.length()-3) + "GMT"+ s.substring(s.length()-3, s.length())+":00";
>       }
> ! // -------
> !        // Michael Stephenson's solution:
>       SimpleDateFormat df = null;
>
> ! // Modification by Jan Thomae
> !     if (s.length()>27) {
> !     df = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzzzzzzzz");
> !     } else
> ! // -------
> !     if (s.length()>21 && s.indexOf('.') != -1) {
> !     df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSzzz");
> !     } else if (s.length()>19 && s.indexOf('.') == -1) {
> !     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:sszzz");
> !     } else if (s.length()>19 && s.indexOf('.') != -1) {
> !     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss.SS");
> !     } else if (s.length()>10 && s.length()<=18) {
> !     df = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss");
>       } else {
> !     df = new SimpleDateFormat("yyyy-MM-dd");
>       }
> !
>       try {
>       return new Timestamp(df.parse(s).getTime());
>       } catch(ParseException e) {
>       throw new PSQLException("postgresql.res.badtimestamp",new Integer(e.getErrorOffset()),s);
>       }
>     }
>
>     /**
>      * A column value can be retrieved as a stream of ASCII characters
> --- 459,524 ----
>      * @return the column value; null if SQL NULL
>      * @exception SQLException if a database access error occurs
>      */
> +   private static final SimpleDateFormat DF1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSzzzzzzzzz");
> +   private static final SimpleDateFormat DF2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzzzzzzzz");
> +   private static final SimpleDateFormat DF3 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
> +   private static final SimpleDateFormat DF4 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
> +   private static final SimpleDateFormat DF5 = new SimpleDateFormat("yyyy-MM-dd");
>     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;
> !     }
> !
> !     //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);
> !     char sub = strBuf.charAt(strBuf.length()-3);
> !     if (sub == '+' || sub == '-') {
> !       strBuf.setLength(strBuf.length()-3);
> !       if (subsecond)  {
> !         strBuf = strBuf.append('0').append("GMT").append(s.substring(s.length()-3, s.length())).append(":00");
> !       } else {
> !         strBuf = strBuf.append("GMT").append(s.substring(s.length()-3, s.length())).append(":00");
> !       }
> !     } else if (subsecond) {
> !       strBuf = strBuf.append('0');
>       }
> !
> !     s = strBuf.toString();
> !
>       SimpleDateFormat df = null;
>
> !     if (s.length()>23 && subsecond) {
> !       df = DF1;
> !     } else if (s.length()>23 && !subsecond) {
> !       df = DF2;
> !     } else if (s.length()>10 && subsecond) {
> !       df = DF3;
> !     } else if (s.length()>10 && !subsecond) {
> !       df = DF4;
>       } else {
> !       df = DF5;
>       }
> !
>       try {
>       return new Timestamp(df.parse(s).getTime());
>       } catch(ParseException e) {
>       throw new PSQLException("postgresql.res.badtimestamp",new Integer(e.getErrorOffset()),s);
>       }
>     }
> +
>
>     /**
>      * A column value can be retrieved as a stream of ASCII characters
>
> --------------F902A91CC78EA5B0218A576D--
>
>
>
>
> --
>   Bruce Momjian                        |  http://candle.pha.pa.us
>   pgman@candle.pha.pa.us               |  (610) 853-3000
>   +  If your life is a hard drive,     |  830 Blythe Avenue
>   +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026


--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026

Re: Patch for JDBC timestamp problems

From
Barry Lind
Date:
Let me answer the questions Thomas raises:

> I'm confused, as usual. How does forcing the client time zone to GMT fix
> the problem of mismatching client and server time zones? istm that you
> still have a problem if the server is not on GMT.

Java has an internal representation for a date the is a long storing
miliseconds since 1970, similar to unix.  By using a date format object
you convert this long value into strings that make sense to humans.
Java also provides a SimpleDateFormat object that can format dates
according to the standard Gregorian calendar, but java also allows other
calendars/dateformat objects.  Lets walk through a couple of examples:

Consider a java Date object with the underlying long value
of979419043128
Taking a SimpleDateFormat object and formatting this date to a String
with the format mask of "yyyy-MM-dd hh:mm:ss.SSSzzz" will result in:
2001-01-13 20:50:43.128GMT  if the timezone setting for the
SimpleDateFormat object is GMT
2001-01-13 12:50:43.128PST  if the timezone setting is PST
...

So back to the original bug, the original code was simply performing a
Timestamp.toString() which uses the following format mask to format the
date 'yyyy-MM-dd hh:mm:ss.SSS' (notice no timezone is included in the
format).  So postgresql assumes the date value is in the DB server
timezone and essentially appends on that timezone.  So looking further
at the example:
DB Server running with EST (GMT-05), jdbc running with PST (GMT-08)
given the date object above:
Timestamp.toString() would format it as '2001-01-13 12:50:43.128' the
server would receive this, see no timezone and assume the value was in
EST and treat it as '2001-01-13 12:50:43.128-05'  which is not what the
client intended, and is the incorrect value, so when the client selects
back the date and converts it to PST it will have '2001-01-13
09:50:43.128PST', three hours different than it sent to the server.

So that was the problem, now to the solution.  Java's SimpleDateFormat
object formats timezone information as either the three letter code
(i.e. PST) if the format mask has three or fewer z's in it, or as a full
name (i.e. Pacific Standard Time) if the format mask has four or more
z's in it.  Whereas the ISO date format expected by postgres is of the
format +/-XX (i.e. -08 for PST).  So there were two ways to code a
solution to this, a) figure out how to get from the java timezone object
the offset from GMT and append on that value for timezone, or b) append
on a known value (i.e. +00) for timezone, and set the timezone of the
SimpleDateFormat object to timezone corresponding to it (i.e. GMT).  As
explained above setting the timezone on the SimpleDateFormat object
doesn't change the absolute value of the date object, just how it gets
formated.  So going back to the above example:

  a) would have resulted in formatting the date as '2001-01-13
12:50:43.128-08' (if I could have figured out how to implement this
easily)
  b) results in formatting the date as '2001-01-13 20:50:43.128+00'
both a) and b) are simply different formats for the same underlying Date
value of 979419043128, and both are interpreted by postgresql as the
same timestamp value.


> Does creating a static variable affect the threadsafe capabilities of
> the driver? If so, it shouldn't be done, but perhaps this is a feature
> of the code already?

The Java core library objects are supposed to be thread safe unless
documented otherwise, and since SimpleDateFormat is a core library
object, it should be safe to use it from multiple threads
simultaniously.  However, I just looked at the source code for
SimpleDateFormat and it is clearly *not* thread safe.  So to work around
this java bug I will resubmit the original patch to take this into
account.

thanks,
--Barry

Thomas Lockhart wrote:
>
> > Oh, great, I see this patch fixes Mike Cannon's problem too.  I will
> > apply it to the main tree now, and it will appear in 7.1.  If you have
> > any more improvements, please send them over.  Thanks.
>
> I'm confused, as usual. How does forcing the client time zone to GMT fix
> the problem of mismatching client and server time zones? istm that you
> still have a problem if the server is not on GMT.
>
> Does creating a static variable affect the threadsafe capabilities of
> the driver? If so, it shouldn't be done, but perhaps this is a feature
> of the code already?
>
>                       - Thomas

Re: Patch for JDBC timestamp problems

From
The Hermit Hacker
Date:
Bruce, please pass *any* JDBC related patches by Peter Mount
(peter@retep.org.uk) , unless you've recently become a JDBC expert?  AS
Thomas pointed out, not only did you miss the GMT implications, but you
also missed the threadsafe implications :(

On Sat, 13 Jan 2001, Barry Lind wrote:

> Let me answer the questions Thomas raises:
>
> > I'm confused, as usual. How does forcing the client time zone to GMT fix
> > the problem of mismatching client and server time zones? istm that you
> > still have a problem if the server is not on GMT.
>
> Java has an internal representation for a date the is a long storing
> miliseconds since 1970, similar to unix.  By using a date format object
> you convert this long value into strings that make sense to humans.
> Java also provides a SimpleDateFormat object that can format dates
> according to the standard Gregorian calendar, but java also allows other
> calendars/dateformat objects.  Lets walk through a couple of examples:
>
> Consider a java Date object with the underlying long value
> of979419043128
> Taking a SimpleDateFormat object and formatting this date to a String
> with the format mask of "yyyy-MM-dd hh:mm:ss.SSSzzz" will result in:
> 2001-01-13 20:50:43.128GMT  if the timezone setting for the
> SimpleDateFormat object is GMT
> 2001-01-13 12:50:43.128PST  if the timezone setting is PST
> ...
>
> So back to the original bug, the original code was simply performing a
> Timestamp.toString() which uses the following format mask to format the
> date 'yyyy-MM-dd hh:mm:ss.SSS' (notice no timezone is included in the
> format).  So postgresql assumes the date value is in the DB server
> timezone and essentially appends on that timezone.  So looking further
> at the example:
> DB Server running with EST (GMT-05), jdbc running with PST (GMT-08)
> given the date object above:
> Timestamp.toString() would format it as '2001-01-13 12:50:43.128' the
> server would receive this, see no timezone and assume the value was in
> EST and treat it as '2001-01-13 12:50:43.128-05'  which is not what the
> client intended, and is the incorrect value, so when the client selects
> back the date and converts it to PST it will have '2001-01-13
> 09:50:43.128PST', three hours different than it sent to the server.
>
> So that was the problem, now to the solution.  Java's SimpleDateFormat
> object formats timezone information as either the three letter code
> (i.e. PST) if the format mask has three or fewer z's in it, or as a full
> name (i.e. Pacific Standard Time) if the format mask has four or more
> z's in it.  Whereas the ISO date format expected by postgres is of the
> format +/-XX (i.e. -08 for PST).  So there were two ways to code a
> solution to this, a) figure out how to get from the java timezone object
> the offset from GMT and append on that value for timezone, or b) append
> on a known value (i.e. +00) for timezone, and set the timezone of the
> SimpleDateFormat object to timezone corresponding to it (i.e. GMT).  As
> explained above setting the timezone on the SimpleDateFormat object
> doesn't change the absolute value of the date object, just how it gets
> formated.  So going back to the above example:
>
>   a) would have resulted in formatting the date as '2001-01-13
> 12:50:43.128-08' (if I could have figured out how to implement this
> easily)
>   b) results in formatting the date as '2001-01-13 20:50:43.128+00'
> both a) and b) are simply different formats for the same underlying Date
> value of 979419043128, and both are interpreted by postgresql as the
> same timestamp value.
>
>
> > Does creating a static variable affect the threadsafe capabilities of
> > the driver? If so, it shouldn't be done, but perhaps this is a feature
> > of the code already?
>
> The Java core library objects are supposed to be thread safe unless
> documented otherwise, and since SimpleDateFormat is a core library
> object, it should be safe to use it from multiple threads
> simultaniously.  However, I just looked at the source code for
> SimpleDateFormat and it is clearly *not* thread safe.  So to work around
> this java bug I will resubmit the original patch to take this into
> account.
>
> thanks,
> --Barry
>
> Thomas Lockhart wrote:
> >
> > > Oh, great, I see this patch fixes Mike Cannon's problem too.  I will
> > > apply it to the main tree now, and it will appear in 7.1.  If you have
> > > any more improvements, please send them over.  Thanks.
> >
> > I'm confused, as usual. How does forcing the client time zone to GMT fix
> > the problem of mismatching client and server time zones? istm that you
> > still have a problem if the server is not on GMT.
> >
> > Does creating a static variable affect the threadsafe capabilities of
> > the driver? If so, it shouldn't be done, but perhaps this is a feature
> > of the code already?
> >
> >                       - Thomas
>

Marc G. Fournier                   ICQ#7615664               IRC Nick: Scrappy
Systems Administrator @ hub.org
primary: scrappy@hub.org           secondary: scrappy@{freebsd|postgresql}.org


Re: [INTERFACES] Patch for JDBC timestamp problems

From
Joseph Shraibman
Date:
Barry Lind wrote:
>
> Attached is a set of patches for a couple of bugs dealing with
> timestamps in JDBC.
>
> Bug#1) Incorrect timestamp stored in DB if client timezone different
> than DB.
>
> The buggy implementation of setTimestamp() in PreparedStatement simply
> used the toString() method of the java.sql.Timestamp object to convert
> to a string to send to the database.  The format of this is yyyy-MM-dd
> hh:mm:ss.SSS which doesn't include any timezone information.  Therefore
> the DB assumes its timezone since none is specified.  That is OK if the
> timezone of the client and server are the same, however if they are
> different the wrong timestamp is received by the server.  For example if
> the client is running in timezone GMT and wants to send the timestamp
> for noon to a server running in PST (GMT-8 hours), then the server will
> receive 2000-01-12 12:00:00.0 and interprete it as 2000-01-12
> 12:00:00-08 which is 2000-01-12 04:00:00 in GMT.  The fix is to send a
> format to the server that includes the timezone offset.  For simplicity
> sake the fix uses a SimpleDateFormat object with its timezone set to GMT
> so that '+00' can be used as the timezone for postgresql.  This is done
> as SimpleDateFormat doesn't support formating timezones in the way
> postgresql expects.
>
> Bug#2) Incorrect handling of partial seconds in getting timestamps from
> the DB
>
> When the SimpleDateFormat object parses a string with a format like
> yyyy-MM-dd hh:mm:ss.SS it expects the fractional seconds to be three
> decimal places (time precision in java is miliseconds = three decimal
> places).  This seems like a bug in java to me, but it is unlikely to be
> fixed anytime soon, so the postgresql code needed modification to
> support the java behaviour.  So for example a string of '2000-01-12
> 12:00:00.12-08' coming from the database was being converted to a
> timestamp object with a value of 2000-01-12 12:00:00.012GMT-08:00.  The
> fix was to check for a '.' in the string and if one is found append on
> an extra zero to the fractional seconds part.
>
> Bug#3) Performance problems
>
> In fixing the above two bugs, I noticed some things that could be
> improved.  In PreparedStatement.setTimestamp(),
> PreparedStatement.setDate(), ResultSet.getTimestamp(), and
> ResultSet.getDate() these methods were creating a new SimpleDateFormat
> object everytime they were called.  To avoid this unnecessary object
> creation overhead, I changed the code to use static variables for
> keeping a single instance of the needed formating objects.
> Also the code used the + operator for string concatenation.  As everyone
> should know this is very inefficient and the use of StringBuffers is
> prefered.
>

While the java spec says that a+b+c should be converted into
a.concat(b.toString()).concat(c.toString())
 probably every single java compiler (including javac) uses
StringBuffers.  The only case where it is an advantage to use your own
stringBuffer is in a case like:

StringBuffer sb = new StringBuffer("blah");
sb.append(a+b+c);

Since that would create a temporary StringBuffer to calculate a+b+c just
to append to the original sb it it might be better to explictly append
a,b,and c.


Using static SimpleDateFormats will probably not cause threading
issues.  Common sense says that if the set methods are never called on
them there will be no state change that my cause sync problems.  But the
spec doesn't garuntee it.  Personally I would have no problem using
static SimpleDateFormats if this were my code.



--
Joseph Shraibman
jks@selectacast.net
Increase signal to noise ratio.  http://www.targabot.com

Re: Patch for JDBC timestamp problems

From
Richard Bullington-McGuire
Date:
I've been reading the recent patch messages for issues related to the JDBC
driver, and I read this notice:

> In fixing the above two bugs, I noticed some things that could be
> improved.  In PreparedStatement.setTimestamp(),
> PreparedStatement.setDate(), ResultSet.getTimestamp(), and
> ResultSet.getDate() these methods were creating a new SimpleDateFormat
> object everytime they were called.  To avoid this unnecessary object
> creation overhead, I changed the code to use static variables for
> keeping a single instance of the needed formating objects.

In fact, the SimpleDateFormat class is not threadsafe, and you do need to
instantiate a new SimpleDateFormat for each pass through the thread. It is
more efficient to have a static instance; unfortunately, it is not
correct. It is desirable to centralize the creation of each type of
DateFormat, but the way to do it is a series of getMyDateFormat() calls
that instantiate a new instance of the variety of SimpleDateFormat you
want to get.

See
<http://developer.java.sun.com/developer/bugParade/bugs/4093418.html> for
details (this requires registration with the Sun Developer Connection).

--
 Richard Bullington-McGuire  <rbulling@microstate.com>
 Chief Technology Officer, The Microstate Corporation
 Phone: 703-796-6446  URL: http://www.microstate.com/
 PGP key IDs:    RSA: 0x93862305   DH/DSS: 0xDAC3028E



Re: Re: [INTERFACES] Patch for JDBC timestamp problems

From
Bruce Momjian
Date:
What was the conclusion of this discussion?  Do we leave it static?

> Barry Lind wrote:
> >
> > Attached is a set of patches for a couple of bugs dealing with
> > timestamps in JDBC.
> >
> > Bug#1) Incorrect timestamp stored in DB if client timezone different
> > than DB.
> >
> > The buggy implementation of setTimestamp() in PreparedStatement simply
> > used the toString() method of the java.sql.Timestamp object to convert
> > to a string to send to the database.  The format of this is yyyy-MM-dd
> > hh:mm:ss.SSS which doesn't include any timezone information.  Therefore
> > the DB assumes its timezone since none is specified.  That is OK if the
> > timezone of the client and server are the same, however if they are
> > different the wrong timestamp is received by the server.  For example if
> > the client is running in timezone GMT and wants to send the timestamp
> > for noon to a server running in PST (GMT-8 hours), then the server will
> > receive 2000-01-12 12:00:00.0 and interprete it as 2000-01-12
> > 12:00:00-08 which is 2000-01-12 04:00:00 in GMT.  The fix is to send a
> > format to the server that includes the timezone offset.  For simplicity
> > sake the fix uses a SimpleDateFormat object with its timezone set to GMT
> > so that '+00' can be used as the timezone for postgresql.  This is done
> > as SimpleDateFormat doesn't support formating timezones in the way
> > postgresql expects.
> >
> > Bug#2) Incorrect handling of partial seconds in getting timestamps from
> > the DB
> >
> > When the SimpleDateFormat object parses a string with a format like
> > yyyy-MM-dd hh:mm:ss.SS it expects the fractional seconds to be three
> > decimal places (time precision in java is miliseconds = three decimal
> > places).  This seems like a bug in java to me, but it is unlikely to be
> > fixed anytime soon, so the postgresql code needed modification to
> > support the java behaviour.  So for example a string of '2000-01-12
> > 12:00:00.12-08' coming from the database was being converted to a
> > timestamp object with a value of 2000-01-12 12:00:00.012GMT-08:00.  The
> > fix was to check for a '.' in the string and if one is found append on
> > an extra zero to the fractional seconds part.
> >
> > Bug#3) Performance problems
> >
> > In fixing the above two bugs, I noticed some things that could be
> > improved.  In PreparedStatement.setTimestamp(),
> > PreparedStatement.setDate(), ResultSet.getTimestamp(), and
> > ResultSet.getDate() these methods were creating a new SimpleDateFormat
> > object everytime they were called.  To avoid this unnecessary object
> > creation overhead, I changed the code to use static variables for
> > keeping a single instance of the needed formating objects.
> > Also the code used the + operator for string concatenation.  As everyone
> > should know this is very inefficient and the use of StringBuffers is
> > prefered.
> >
>
> While the java spec says that a+b+c should be converted into
> a.concat(b.toString()).concat(c.toString())
>  probably every single java compiler (including javac) uses
> StringBuffers.  The only case where it is an advantage to use your own
> stringBuffer is in a case like:
>
> StringBuffer sb = new StringBuffer("blah");
> sb.append(a+b+c);
>
> Since that would create a temporary StringBuffer to calculate a+b+c just
> to append to the original sb it it might be better to explictly append
> a,b,and c.
>
>
> Using static SimpleDateFormats will probably not cause threading
> issues.  Common sense says that if the set methods are never called on
> them there will be no state change that my cause sync problems.  But the
> spec doesn't garuntee it.  Personally I would have no problem using
> static SimpleDateFormats if this were my code.
>
>
>
> --
> Joseph Shraibman
> jks@selectacast.net
> Increase signal to noise ratio.  http://www.targabot.com
>


--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026

Re: Re: [INTERFACES] Patch for JDBC timestamp problems

From
Joseph Shraibman
Date:
No, it cannot be static.

Bruce Momjian wrote:
>
> What was the conclusion of this discussion?  Do we leave it static?
>
> > Barry Lind wrote:
> > >
> > > Attached is a set of patches for a couple of bugs dealing with
> > > timestamps in JDBC.
> > >
> > > Bug#1) Incorrect timestamp stored in DB if client timezone different
> > > than DB.
> > >
> > > The buggy implementation of setTimestamp() in PreparedStatement simply
> > > used the toString() method of the java.sql.Timestamp object to convert
> > > to a string to send to the database.  The format of this is yyyy-MM-dd
> > > hh:mm:ss.SSS which doesn't include any timezone information.  Therefore
> > > the DB assumes its timezone since none is specified.  That is OK if the
> > > timezone of the client and server are the same, however if they are
> > > different the wrong timestamp is received by the server.  For example if
> > > the client is running in timezone GMT and wants to send the timestamp
> > > for noon to a server running in PST (GMT-8 hours), then the server will
> > > receive 2000-01-12 12:00:00.0 and interprete it as 2000-01-12
> > > 12:00:00-08 which is 2000-01-12 04:00:00 in GMT.  The fix is to send a
> > > format to the server that includes the timezone offset.  For simplicity
> > > sake the fix uses a SimpleDateFormat object with its timezone set to GMT
> > > so that '+00' can be used as the timezone for postgresql.  This is done
> > > as SimpleDateFormat doesn't support formating timezones in the way
> > > postgresql expects.
> > >
> > > Bug#2) Incorrect handling of partial seconds in getting timestamps from
> > > the DB
> > >
> > > When the SimpleDateFormat object parses a string with a format like
> > > yyyy-MM-dd hh:mm:ss.SS it expects the fractional seconds to be three
> > > decimal places (time precision in java is miliseconds = three decimal
> > > places).  This seems like a bug in java to me, but it is unlikely to be
> > > fixed anytime soon, so the postgresql code needed modification to
> > > support the java behaviour.  So for example a string of '2000-01-12
> > > 12:00:00.12-08' coming from the database was being converted to a
> > > timestamp object with a value of 2000-01-12 12:00:00.012GMT-08:00.  The
> > > fix was to check for a '.' in the string and if one is found append on
> > > an extra zero to the fractional seconds part.
> > >
> > > Bug#3) Performance problems
> > >
> > > In fixing the above two bugs, I noticed some things that could be
> > > improved.  In PreparedStatement.setTimestamp(),
> > > PreparedStatement.setDate(), ResultSet.getTimestamp(), and
> > > ResultSet.getDate() these methods were creating a new SimpleDateFormat
> > > object everytime they were called.  To avoid this unnecessary object
> > > creation overhead, I changed the code to use static variables for
> > > keeping a single instance of the needed formating objects.
> > > Also the code used the + operator for string concatenation.  As everyone
> > > should know this is very inefficient and the use of StringBuffers is
> > > prefered.
> > >
> >
> > While the java spec says that a+b+c should be converted into
> > a.concat(b.toString()).concat(c.toString())
> >  probably every single java compiler (including javac) uses
> > StringBuffers.  The only case where it is an advantage to use your own
> > stringBuffer is in a case like:
> >
> > StringBuffer sb = new StringBuffer("blah");
> > sb.append(a+b+c);
> >
> > Since that would create a temporary StringBuffer to calculate a+b+c just
> > to append to the original sb it it might be better to explictly append
> > a,b,and c.
> >
> >
> > Using static SimpleDateFormats will probably not cause threading
> > issues.  Common sense says that if the set methods are never called on
> > them there will be no state change that my cause sync problems.  But the
> > spec doesn't garuntee it.  Personally I would have no problem using
> > static SimpleDateFormats if this were my code.
> >
> >
> >
> > --
> > Joseph Shraibman
> > jks@selectacast.net
> > Increase signal to noise ratio.  http://www.targabot.com
> >
>
> --
>   Bruce Momjian                        |  http://candle.pha.pa.us
>   pgman@candle.pha.pa.us               |  (610) 853-3000
>   +  If your life is a hard drive,     |  830 Blythe Avenue
>   +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026

--
Joseph Shraibman
jks@selectacast.net
Increase signal to noise ratio.  http://www.targabot.com