Permalink
Browse files

fix: use 'time with time zone' and 'timestamp with time zone' values …

…as is and avoid computation with user-provided/default Calendars

Previous behavior:
getTime(int), getTime(int, Calendar) kind of methods ensured that the resulting java.sql.Time
would render as 1970-01-01 ... in the given time zone (e.g. given Calendar and/or default TimeZone)
Note: the resulting Time object

New behavior:
for 'time with time zone' and 'timestamp with time zone' the value from the server is assumed to be
"instant" (i.e. absolute point in time), and no further adjustments is made to make the date part to be 1970-01-01

'time' and 'timestamp' work as earlier except "00:00:00" and "24:00:00" times in text format.
Previously, text times "00:00:00" and "24:00:00" were mapped to Time(0) and Time.valueOf("24:00:00"):
1) Time(0) is 1970-01-01 00:00:00 UTC (it does not account neither user-provided nor default Calendar)
2) Time.valueOf("24:00:00") uses system-provided calendar, and it does not account user-provided one
  • Loading branch information...
vlsi committed Jan 3, 2018
1 parent 684a699 commit e8c43f36ab2a6843f37d27e7417a5214f7142084
@@ -71,6 +71,7 @@
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;


@@ -518,8 +519,14 @@ public Time getTime(int i, java.util.Calendar cal) throws SQLException {
// If backend provides just TIMESTAMP, we use "cal" timezone
// If backend provides TIMESTAMPTZ, we ignore "cal" as we know true instant value
Timestamp timestamp = getTimestamp(i, cal);
long timeMillis = timestamp.getTime();
if (oid == Oid.TIMESTAMPTZ) {
// time zone == UTC since BINARY "timestamp with time zone" is always sent in UTC
// So we truncate days
return new Time(timeMillis % TimeUnit.DAYS.toMillis(1));
}
// Here we just truncate date part
return connection.getTimestampUtils().convertToTime(timestamp.getTime(), tz);
return connection.getTimestampUtils().convertToTime(timeMillis, tz);
} else {
throw new PSQLException(
GT.tr("Cannot convert the column of type {0} to requested type {1}.",
@@ -529,13 +536,7 @@ public Time getTime(int i, java.util.Calendar cal) throws SQLException {
}

String string = getString(i);
if (string.equals("24:00:00")) {
return Time.valueOf(string);
} else if (string.equals("00:00:00")) {
return new Time(0);
} else {
return connection.getTimestampUtils().toTime(cal, string);
}
return connection.getTimestampUtils().toTime(cal, string);
}

//#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2"
@@ -463,25 +463,45 @@ public LocalDateTime toLocalDateTime(String s) throws SQLException {

public synchronized Time toTime(Calendar cal, String s) throws SQLException {
// 1) Parse backend string
Timestamp timestamp = toTimestamp(cal, s);

if (timestamp == null) {
if (s == null) {
return null;
}

long millis = timestamp.getTime();
// infinity cannot be represented as Time
// so there's not much we can do here.
if (millis <= PGStatement.DATE_NEGATIVE_INFINITY
|| millis >= PGStatement.DATE_POSITIVE_INFINITY) {
throw new PSQLException(
GT.tr("Infinite value found for timestamp/date. This cannot be represented as time."),
PSQLState.DATETIME_OVERFLOW);
ParsedTimestamp ts = parseBackendTimestamp(s);
Calendar useCal = ts.tz != null ? ts.tz : setupCalendar(cal);
if (ts.tz == null) {
// When no time zone provided (e.g. time or timestamp)
// We get the year-month-day from the string, then truncate the day to 1970-01-01
// This is used for timestamp -> time conversion
// Note: this cannot be merged with "else" branch since
// timestamps at which the time flips to/from DST depend on the date
// For instance, 2000-03-26 02:00:00 is invalid timestamp in Europe/Moscow time zone
// and the valid one is 2000-03-26 03:00:00. That is why we parse full timestamp
// then set year to 1970 later
useCal.set(Calendar.ERA, ts.era);
useCal.set(Calendar.YEAR, ts.year);
useCal.set(Calendar.MONTH, ts.month - 1);
useCal.set(Calendar.DAY_OF_MONTH, ts.day);
} else {
// When time zone is given, we just pick the time part and assume date to be 1970-01-01
// this is used for time, timez, and timestamptz parsing
useCal.set(Calendar.ERA, GregorianCalendar.AD);
useCal.set(Calendar.YEAR, 1970);
useCal.set(Calendar.MONTH, Calendar.JANUARY);
useCal.set(Calendar.DAY_OF_MONTH, 1);
}
useCal.set(Calendar.HOUR_OF_DAY, ts.hour);
useCal.set(Calendar.MINUTE, ts.minute);
useCal.set(Calendar.SECOND, ts.second);
useCal.set(Calendar.MILLISECOND, 0);

long timeMillis = useCal.getTimeInMillis() + ts.nanos / 1000000;
if (ts.tz != null || (ts.year == 1970 && ts.era == GregorianCalendar.AD)) {
// time with time zone has proper time zone, so the value can be returned as is
return new Time(timeMillis);
}

// 2) Truncate date part so in given time zone the date would be formatted as 00:00
return convertToTime(timestamp.getTime(), cal == null ? null : cal.getTimeZone());
// 2) Truncate date part so in given time zone the date would be formatted as 01/01/1970
return convertToTime(timeMillis, useCal == null ? null : useCal.getTimeZone());
}

public synchronized Date toDate(Calendar cal, String s) throws SQLException {
@@ -913,16 +933,17 @@ public Time toTimeBin(TimeZone tz, byte[] bytes) throws PSQLException {
timeOffset = ByteConverter.int4(bytes, 8);
timeOffset *= -1000;
millis -= timeOffset;
} else {
if (tz == null) {
tz = getDefaultTz();
}
return new Time(millis);
}

// Here be dragons: backend did not provide us the timezone, so we guess the actual point in
// time
millis = guessTimestamp(millis, tz);
if (tz == null) {
tz = getDefaultTz();
}

// Here be dragons: backend did not provide us the timezone, so we guess the actual point in
// time
millis = guessTimestamp(millis, tz);

return convertToTime(millis, tz); // Ensure date part is 1970-01-01
}

@@ -189,8 +189,8 @@ public void testGetTimestamp() throws Exception {
// 1970-01-01 15:00:00 +0300 -> 1970-01-01 07:00:00 -0500
assertEquals(43200000L, ts.getTime());
ts = rs.getTimestamp(4, cGMT13);
// 1970-01-01 15:00:00 +0300 -> 1970-01-02 01:00:00 +1300 (CHECK ME)
assertEquals(-43200000L, ts.getTime());
// 1970-01-01 15:00:00 +0300 -> 1970-01-02 01:00:00 +1300
assertEquals(43200000L, ts.getTime());
str = rs.getString(4);
assertEquals("timetz -> getString" + format, "15:00:00+03", str);

@@ -299,7 +299,7 @@ public void testGetTime() throws Exception {
assertEquals(43200000L, t.getTime());
t = rs.getTime(1, cGMT13);
// 2005-01-02 01:00:00 +1300 -> 1970-01-01 01:00:00 +1300
assertEquals(-43200000L, t.getTime());
assertEquals(43200000L, t.getTime());

// timestamp: 2005-01-01 15:00:00
t = rs.getTime(2);
@@ -335,7 +335,7 @@ public void testGetTime() throws Exception {
t = rs.getTime(4, cGMT05);
assertEquals(43200000L, t.getTime()); // 1970-01-01 07:00:00 -0500
t = rs.getTime(4, cGMT13);
assertEquals(-43200000L, t.getTime()); // 1970-01-01 01:00:00 +1300
assertEquals(43200000L, t.getTime()); // 1970-01-01 01:00:00 +1300
rs.close();
}
}

0 comments on commit e8c43f3

Please sign in to comment.