Skip to content

Commit

Permalink
fix #1499 rounding for timestamps truncated to dates before 1970 (#1502)
Browse files Browse the repository at this point in the history
* fix #1499 rounding for timestamps truncated to dates before 1970

* Implement floorDiv until we drop support for Java 1.7
  • Loading branch information
davecramer committed Jun 17, 2019
1 parent 1970c4a commit c9a7078
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 2 deletions.
23 changes: 21 additions & 2 deletions pgjdbc/src/main/java/org/postgresql/jdbc/TimestampUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -1232,7 +1232,12 @@ public Date convertToDate(long millis, TimeZone tz) {
if (tz == null) {
tz = getDefaultTz();
}
if (isSimpleTimeZone(tz.getID())) {
/*
the following math does not work for negative times
Punt and use Calendar for negative times.
This probably introduces a performance regression for times before 1970
*/
if ( isSimpleTimeZone(tz.getID())) {
// Truncate to 00:00 of the day.
// Suppose the input date is 7 Jan 15:40 GMT+02:00 (that is 13:40 UTC)
// We want it to become 7 Jan 00:00 GMT+02:00
Expand All @@ -1241,20 +1246,22 @@ public Date convertToDate(long millis, TimeZone tz) {
millis += offset;
// 2) Truncate hours, minutes, etc. Day is always 86400 seconds, no matter what leap seconds
// are
millis = millis / ONEDAY * ONEDAY;
millis = floorDiv(millis, ONEDAY) * ONEDAY;
// 2) Now millis is 7 Jan 00:00 UTC, however we need that in GMT+02:00, so subtract some
// offset
millis -= offset;
// Now we have brand-new 7 Jan 00:00 GMT+02:00
return new Date(millis);
}

Calendar cal = calendarWithUserTz;
cal.setTimeZone(tz);
cal.setTimeInMillis(millis);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);

return new Date(cal.getTimeInMillis());
}

Expand Down Expand Up @@ -1411,4 +1418,16 @@ public static TimeZone parseBackendTimeZone(String timeZone) {
}
return TimeZone.getTimeZone(timeZone);
}

/*
provide our own as this is not available until 1.8
*/
public static long floorDiv(long x, long y) {
long r = x / y;
// if the signs are different and modulo not zero, round down
if ((x ^ y) < 0 && (r * y != x)) {
r--;
}
return r;
}
}
20 changes: 20 additions & 0 deletions pgjdbc/src/test/java/org/postgresql/test/jdbc2/TimestampTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ public TimestampTest(BinaryMode binaryMode) {
setBinaryMode(binaryMode);
}

private TimeZone currentTZ;

@Parameterized.Parameters(name = "binary = {0}")
public static Iterable<Object[]> data() {
Collection<Object[]> ids = new ArrayList<Object[]>();
Expand All @@ -62,13 +64,15 @@ public void setUp() throws Exception {
TestUtil.createTable(con, TSWTZ_TABLE, "ts timestamp with time zone");
TestUtil.createTable(con, TSWOTZ_TABLE, "ts timestamp without time zone");
TestUtil.createTable(con, DATE_TABLE, "ts date");
currentTZ = TimeZone.getDefault();
}

@Override
public void tearDown() throws SQLException {
TestUtil.dropTable(con, TSWTZ_TABLE);
TestUtil.dropTable(con, TSWOTZ_TABLE);
TestUtil.dropTable(con, DATE_TABLE);
TimeZone.setDefault(currentTZ);
super.tearDown();
}

Expand Down Expand Up @@ -555,6 +559,22 @@ public void testSetTimestampWOTZ() throws SQLException {
stmt.close();
}

/*
Times before 1970 are negative in Java.
the convert to date does some math that assumes times are positive.
This test assures that the convertToDate still works properly with negative times
*/
@Test
public void TestTimeStampBefore1970() {

TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
TimestampUtils tsu = ((BaseConnection) con).getTimestampUtils();

Date date = tsu.convertToDate(Timestamp.valueOf("1969-01-10 12:00:00").getTime(), TimeZone.getDefault());

assertEquals(new GregorianCalendar(1969, Calendar.JANUARY, 10).getTime().getTime(), date.getTime() );
}

/*
* Helper for the TimestampTests. It tests what should be in the db
*/
Expand Down

0 comments on commit c9a7078

Please sign in to comment.