Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 26e5664

Browse files
committed
Attached is a revised patch that removes the static SimpleDateFormat
objects that Thomas pointed out might be a problem. PPS. I have included and updated the comments from the original patch request to reflect the changes made in this revised patch. > 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. > > > 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#3) 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. Barry Lind
1 parent 7b9dc71 commit 26e5664

File tree

4 files changed

+95
-50
lines changed

4 files changed

+95
-50
lines changed

src/interfaces/jdbc/org/postgresql/jdbc1/PreparedStatement.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -312,10 +312,9 @@ public void setBytes(int parameterIndex, byte x[]) throws SQLException
312312
*/
313313
public void setDate(int parameterIndex, java.sql.Date x) throws SQLException
314314
{
315-
SimpleDateFormat df = new SimpleDateFormat("''yyyy-MM-dd''");
316-
315+
SimpleDateFormat df = new SimpleDateFormat("''yyyy-MM-dd''");
317316
set(parameterIndex, df.format(x));
318-
317+
319318
// The above is how the date should be handled.
320319
//
321320
// However, in JDK's prior to 1.1.6 (confirmed with the
@@ -350,8 +349,12 @@ public void setTime(int parameterIndex, Time x) throws SQLException
350349
* @exception SQLException if a database access error occurs
351350
*/
352351
public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException
353-
{
354-
set(parameterIndex, "'" + x.toString() + "'");
352+
{
353+
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
354+
df.setTimeZone(TimeZone.getTimeZone("GMT"));
355+
StringBuffer strBuf = new StringBuffer("'");
356+
strBuf.append(df.format(x)).append('.').append(x.getNanos()/10000000).append("+00'");
357+
set(parameterIndex, strBuf.toString());
355358
}
356359

357360
/**

src/interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -462,25 +462,49 @@ public Timestamp getTimestamp(int columnIndex) throws SQLException
462462
String s = getString(columnIndex);
463463
if(s==null)
464464
return null;
465-
466-
// This works, but it's commented out because Michael Stephenson's
467-
// solution is better still:
468-
//SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
469-
470-
// Michael Stephenson's solution:
465+
466+
boolean subsecond;
467+
//if string contains a '.' we have fractional seconds
468+
if (s.indexOf('.') == -1) {
469+
subsecond = false;
470+
} else {
471+
subsecond = true;
472+
}
473+
474+
//here we are modifying the string from ISO format to a format java can understand
475+
//java expects timezone info as 'GMT-08:00' instead of '-08' in postgres ISO format
476+
//and java expects three digits if fractional seconds are present instead of two for postgres
477+
//so this code strips off timezone info and adds on the GMT+/-...
478+
//as well as adds a third digit for partial seconds if necessary
479+
StringBuffer strBuf = new StringBuffer(s);
480+
char sub = strBuf.charAt(strBuf.length()-3);
481+
if (sub == '+' || sub == '-') {
482+
strBuf.setLength(strBuf.length()-3);
483+
if (subsecond) {
484+
strBuf = strBuf.append('0').append("GMT").append(s.substring(s.length()-3, s.length())).append(":00");
485+
} else {
486+
strBuf = strBuf.append("GMT").append(s.substring(s.length()-3, s.length())).append(":00");
487+
}
488+
} else if (subsecond) {
489+
strBuf = strBuf.append('0');
490+
}
491+
492+
s = strBuf.toString();
493+
471494
SimpleDateFormat df = null;
472-
if (s.length()>21 && s.indexOf('.') != -1) {
473-
df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSzzz");
474-
} else if (s.length()>19 && s.indexOf('.') == -1) {
475-
df = new SimpleDateFormat("yyyy-MM-dd HH:MM:sszzz");
476-
} else if (s.length()>19 && s.indexOf('.') != -1) {
477-
df = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss.SS");
478-
} else if (s.length()>10 && s.length()<=18) {
479-
df = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss");
495+
496+
if (s.length()>23 && subsecond) {
497+
df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSzzzzzzzzz");
498+
} else if (s.length()>23 && !subsecond) {
499+
df = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzzzzzzzz");
500+
} else if (s.length()>10 && subsecond) {
501+
df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
502+
} else if (s.length()>10 && !subsecond) {
503+
df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
480504
} else {
481-
df = new SimpleDateFormat("yyyy-MM-dd");
505+
df = new SimpleDateFormat("yyyy-MM-dd");
482506
}
483-
507+
484508
try {
485509
return new Timestamp(df.parse(s).getTime());
486510
} catch(ParseException e) {

src/interfaces/jdbc/org/postgresql/jdbc2/PreparedStatement.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -312,10 +312,10 @@ public void setBytes(int parameterIndex, byte x[]) throws SQLException
312312
*/
313313
public void setDate(int parameterIndex, java.sql.Date x) throws SQLException
314314
{
315-
SimpleDateFormat df = new SimpleDateFormat("''yyyy-MM-dd''");
316-
315+
SimpleDateFormat df = new SimpleDateFormat("''yyyy-MM-dd''");
316+
317317
set(parameterIndex, df.format(x));
318-
318+
319319
// The above is how the date should be handled.
320320
//
321321
// However, in JDK's prior to 1.1.6 (confirmed with the
@@ -350,8 +350,12 @@ public void setTime(int parameterIndex, Time x) throws SQLException
350350
* @exception SQLException if a database access error occurs
351351
*/
352352
public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException
353-
{
354-
set(parameterIndex, "'" + x.toString() + "'");
353+
{
354+
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
355+
df.setTimeZone(TimeZone.getTimeZone("GMT"));
356+
StringBuffer strBuf = new StringBuffer("'");
357+
strBuf.append(df.format(x)).append('.').append(x.getNanos()/10000000).append("+00'");
358+
set(parameterIndex, strBuf.toString());
355359
}
356360

357361
/**

src/interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -468,33 +468,46 @@ public Timestamp getTimestamp(int columnIndex) throws SQLException
468468
if(s==null)
469469
return null;
470470

471-
// This works, but it's commented out because Michael Stephenson's
472-
// solution is better still:
473-
//SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
474-
// Modification by Jan Thomae
475-
String sub = s.substring(s.length() - 3, s.length()-2);
476-
if (sub.equals("+") || sub.equals("-")) {
477-
s = s.substring(0, s.length()-3) + "GMT"+ s.substring(s.length()-3, s.length())+":00";
478-
}
479-
// -------
480-
// Michael Stephenson's solution:
471+
boolean subsecond;
472+
//if string contains a '.' we have fractional seconds
473+
if (s.indexOf('.') == -1) {
474+
subsecond = false;
475+
} else {
476+
subsecond = true;
477+
}
478+
479+
//here we are modifying the string from ISO format to a format java can understand
480+
//java expects timezone info as 'GMT-08:00' instead of '-08' in postgres ISO format
481+
//and java expects three digits if fractional seconds are present instead of two for postgres
482+
//so this code strips off timezone info and adds on the GMT+/-...
483+
//as well as adds a third digit for partial seconds if necessary
484+
StringBuffer strBuf = new StringBuffer(s);
485+
char sub = strBuf.charAt(strBuf.length()-3);
486+
if (sub == '+' || sub == '-') {
487+
strBuf.setLength(strBuf.length()-3);
488+
if (subsecond) {
489+
strBuf = strBuf.append('0').append("GMT").append(s.substring(s.length()-3, s.length())).append(":00");
490+
} else {
491+
strBuf = strBuf.append("GMT").append(s.substring(s.length()-3, s.length())).append(":00");
492+
}
493+
} else if (subsecond) {
494+
strBuf = strBuf.append('0');
495+
}
496+
497+
s = strBuf.toString();
498+
481499
SimpleDateFormat df = null;
482500

483-
// Modification by Jan Thomae
484-
if (s.length()>27) {
485-
df = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzzzzzzzz");
486-
} else
487-
// -------
488-
if (s.length()>21 && s.indexOf('.') != -1) {
489-
df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSzzz");
490-
} else if (s.length()>19 && s.indexOf('.') == -1) {
491-
df = new SimpleDateFormat("yyyy-MM-dd HH:MM:sszzz");
492-
} else if (s.length()>19 && s.indexOf('.') != -1) {
493-
df = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss.SS");
494-
} else if (s.length()>10 && s.length()<=18) {
495-
df = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss");
501+
if (s.length()>23 && subsecond) {
502+
df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSzzzzzzzzz");
503+
} else if (s.length()>23 && !subsecond) {
504+
df = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzzzzzzzz");
505+
} else if (s.length()>10 && subsecond) {
506+
df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
507+
} else if (s.length()>10 && !subsecond) {
508+
df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
496509
} else {
497-
df = new SimpleDateFormat("yyyy-MM-dd");
510+
df = new SimpleDateFormat("yyyy-MM-dd");
498511
}
499512

500513
try {
@@ -504,6 +517,7 @@ public Timestamp getTimestamp(int columnIndex) throws SQLException
504517
}
505518
}
506519

520+
507521
/**
508522
* A column value can be retrieved as a stream of ASCII characters
509523
* and then read in chunks from the stream. This method is

0 commit comments

Comments
 (0)