Permalink
Browse files

feat: support microsecond resolution for JSR-310 types (#691)

PostgreSQL supports microsecond resolution for Date/Time Types. JSR-310
types support nanosecond resolution. For LocalTime we currently
truncate to the millisecond.

 * test microsecond resolution for JSR-310 types
 * add support for microsecond resolution for LocalTime
  • Loading branch information...
marschall authored and vlsi committed Jan 27, 2017
1 parent ee51dfc commit 6b3a1efb36709e570caf0dc71a42a53e89f5a5e0
@@ -531,6 +531,31 @@ public Time getTime(int i, java.util.Calendar cal) throws SQLException {
return connection.getTimestampUtils().toTime(cal, string);
}
//#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2"
private LocalTime getLocalTime(int i) throws SQLException {
checkResultSet(i);
if (wasNullFlag) {
return null;
}
if (isBinary(i)) {
int col = i - 1;
int oid = fields[col].getOID();
if (oid == Oid.TIME) {
return connection.getTimestampUtils().toLocalTimeBin(this_row[col]);
} else {
throw new PSQLException(
GT.tr("Cannot convert the column of type {0} to requested type {1}.",
Oid.toString(oid), "time"),
PSQLState.DATA_TYPE_MISMATCH);
}
}
String string = getString(i);
return connection.getTimestampUtils().toLocalTime(string);
}
//#endif
@Override
public Timestamp getTimestamp(int i, java.util.Calendar cal) throws SQLException {
@@ -3266,11 +3291,7 @@ public void updateArray(String columnName, Array x) throws SQLException {
}
} else if (type == LocalTime.class) {
if (sqlType == Types.TIME) {
Time timeValue = getTime(columnIndex);
if (wasNull()) {
return null;
}
return type.cast(timeValue.toLocalTime());
return type.cast(getLocalTime(columnIndex));
} else {
throw new SQLException("conversion to " + type + " from " + sqlType + " not supported");
}
@@ -25,6 +25,7 @@
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.chrono.IsoEra;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
//#endif
import java.util.Calendar;
@@ -393,6 +394,27 @@ public synchronized Timestamp toTimestamp(Calendar cal, String s) throws SQLExce
}
//#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2"
/**
* Parse a string and return a LocalTime representing its value.
*
* @param s The ISO formated time string to parse.
* @return null if s is null or a LocalTime of the parsed string s.
* @throws SQLException if there is a problem parsing s.
*/
public LocalTime toLocalTime(String s) throws SQLException {
if (s == null) {
return null;
}
try {
return LocalTime.parse(s);
} catch (DateTimeParseException nfe) {
throw new PSQLException(
GT.tr("Bad value for type timestamp/date/time: {1}", s),
PSQLState.BAD_DATETIME_FORMAT, nfe);
}
}
/**
* Parse a string and return a LocalDateTime representing its value.
*
@@ -888,6 +910,36 @@ public Time toTimeBin(TimeZone tz, byte[] bytes) throws PSQLException {
return convertToTime(millis, tz); // Ensure date part is 1970-01-01
}
//#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2"
/**
* Returns the SQL Time object matching the given bytes with {@link Oid#TIME}.
*
* @param bytes The binary encoded time value.
* @return The parsed time object.
* @throws PSQLException If binary format could not be parsed.
*/
public LocalTime toLocalTimeBin(byte[] bytes) throws PSQLException {
if (bytes.length != 8) {
throw new PSQLException(GT.tr("Unsupported binary encoding of {0}.", "time"),
PSQLState.BAD_DATETIME_FORMAT);
}
long micros;
if (usesDouble) {
double seconds = ByteConverter.float8(bytes, 0);
micros = (long) (seconds * 1000000d);
} else {
micros = ByteConverter.int8(bytes, 0);
}
return LocalTime.ofNanoOfDay(micros * 1000);
}
//#endif
/**
* Returns the SQL Timestamp object matching the given bytes with {@link Oid#TIMESTAMP} or
* {@link Oid#TIMESTAMPTZ}.
@@ -7,6 +7,8 @@
import org.postgresql.test.TestUtil;
import org.postgresql.test.jdbc2.BaseTest;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -74,19 +76,63 @@ public void testGetLocalDate() throws SQLException {
*/
public void testGetLocalTime() throws SQLException {
Statement stmt = con.createStatement();
stmt.executeUpdate(TestUtil.insertSQL("table1","time_without_time_zone_column","TIME '04:05:06'"));
stmt.executeUpdate(TestUtil.insertSQL("table1","time_without_time_zone_column","TIME '04:05:06.123456'"));
ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "time_without_time_zone_column"));
try {
assertTrue(rs.next());
LocalTime localTime = LocalTime.of(4, 5, 6);
LocalTime localTime = LocalTime.of(4, 5, 6, 123456000);
assertEquals(localTime, rs.getObject("time_without_time_zone_column", LocalTime.class));
assertEquals(localTime, rs.getObject(1, LocalTime.class));
} finally {
rs.close();
}
}
/**
* Test the behavior getObject for time columns with null.
*/
public void testGetLocalTimeNull() throws SQLException {
Statement stmt = con.createStatement();
stmt.executeUpdate(TestUtil.insertSQL("table1","time_without_time_zone_column","NULL"));
ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "time_without_time_zone_column"));
try {
assertTrue(rs.next());
assertNull(rs.getObject("time_without_time_zone_column", LocalTime.class));
assertNull(rs.getObject(1, LocalTime.class));
} finally {
rs.close();
}
}
/**
* Test the behavior getObject for time columns with null.
*/
public void testGetLocalTimeInvalidType() throws SQLException {
Statement stmt = con.createStatement();
stmt.executeUpdate(TestUtil.insertSQL("table1","time_with_time_zone_column", "TIME '04:05:06.123456-08:00'"));
ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "time_with_time_zone_column"));
try {
assertTrue(rs.next());
try {
assertNull(rs.getObject("time_with_time_zone_column", LocalTime.class));
} catch (PSQLException e) {
assertTrue(e.getSQLState().equals(PSQLState.DATA_TYPE_MISMATCH.getState())
|| e.getSQLState().equals(PSQLState.BAD_DATETIME_FORMAT.getState()));
}
try {
assertNull(rs.getObject(1, LocalTime.class));
} catch (PSQLException e) {
assertTrue(e.getSQLState().equals(PSQLState.DATA_TYPE_MISMATCH.getState())
|| e.getSQLState().equals(PSQLState.BAD_DATETIME_FORMAT.getState()));
}
} finally {
rs.close();
}
}
/**
* Test the behavior getObject for timestamp columns.
*/
@@ -106,17 +152,17 @@ public void testGetLocalDateTime() throws SQLException {
"1997-06-30T23:59:59", "1997-07-01T00:00:00", "2012-06-30T23:59:59", "2012-07-01T00:00:00",
"2015-06-30T23:59:59", "2015-07-01T00:00:00", "2005-12-31T23:59:59", "2006-01-01T00:00:00",
"2008-12-31T23:59:59", "2009-01-01T00:00:00", /* "2015-06-30T23:59:60", */ "2015-07-31T00:00:00",
"2015-07-31T00:00:01",
"2015-07-31T00:00:01", "2015-07-31T00:00:00.000001",
// On 2000-03-26 02:00:00 Moscow went to DST, thus local time became 03:00:00
"2000-03-26T01:59:59", "2000-03-26T02:00:00", "2000-03-26T02:00:01", "2000-03-26T02:59:59",
"2000-03-26T03:00:00", "2000-03-26T03:00:01", "2000-03-26T03:59:59", "2000-03-26T04:00:00",
"2000-03-26T04:00:01",
"2000-03-26T04:00:01", "2000-03-26T04:00:00.000001",
// On 2000-10-29 03:00:00 Moscow went to regular time, thus local time became 02:00:00
"2000-10-29T01:59:59", "2000-10-29T02:00:00", "2000-10-29T02:00:01", "2000-10-29T02:59:59",
"2000-10-29T03:00:00", "2000-10-29T03:00:01", "2000-10-29T03:59:59", "2000-10-29T04:00:00",
"2000-10-29T04:00:01");
"2000-10-29T04:00:01", "2000-10-29T04:00:00.000001");
for (String zoneId : zoneIdsToTest) {
ZoneId zone = ZoneId.of(zoneId);
@@ -160,12 +206,12 @@ public void testGetTimestampWithTimeZone() throws SQLException {
private void runGetOffsetDateTime(ZoneOffset offset) throws SQLException {
Statement stmt = con.createStatement();
try {
stmt.executeUpdate(TestUtil.insertSQL("table1","timestamp_with_time_zone_column","TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54" + offset.toString() + "'"));
stmt.executeUpdate(TestUtil.insertSQL("table1","timestamp_with_time_zone_column","TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54.123456" + offset.toString() + "'"));
ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "timestamp_with_time_zone_column"));
try {
assertTrue(rs.next());
LocalDateTime localDateTime = LocalDateTime.of(2004, 10, 19, 10, 23, 54);
LocalDateTime localDateTime = LocalDateTime.of(2004, 10, 19, 10, 23, 54, 123456000);
OffsetDateTime offsetDateTime = localDateTime.atOffset(offset).withOffsetSameInstant(ZoneOffset.UTC);
assertEquals(offsetDateTime, rs.getObject("timestamp_with_time_zone_column", OffsetDateTime.class));
@@ -98,7 +98,7 @@ private String insertThenReadStringWithType(LocalDateTime data, String columnNam
return readString(columnName);
}
private void insertWithoutType(OffsetDateTime data, String columnName) throws SQLException {
private void insertWithoutType(Object data, String columnName) throws SQLException {
insert(data, columnName, null);
}
@@ -210,17 +210,17 @@ public void testSetOffsetDateTime() throws SQLException {
"1997-06-30T23:59:59", "1997-07-01T00:00:00", "2012-06-30T23:59:59", "2012-07-01T00:00:00",
"2015-06-30T23:59:59", "2015-07-01T00:00:00", "2005-12-31T23:59:59", "2006-01-01T00:00:00",
"2008-12-31T23:59:59", "2009-01-01T00:00:00", /* "2015-06-30T23:59:60", */ "2015-07-31T00:00:00",
"2015-07-31T00:00:01",
"2015-07-31T00:00:01", "2015-07-31T00:00:00.000001",
// On 2000-03-26 02:00:00 Moscow went to DST, thus local time became 03:00:00
"2000-03-26T01:59:59", "2000-03-26T02:00:00", "2000-03-26T02:00:01", "2000-03-26T02:59:59",
"2000-03-26T03:00:00", "2000-03-26T03:00:01", "2000-03-26T03:59:59", "2000-03-26T04:00:00",
"2000-03-26T04:00:01",
"2000-03-26T04:00:01", "2000-03-26T04:00:00.000001",
// On 2000-10-29 03:00:00 Moscow went to regular time, thus local time became 02:00:00
"2000-10-29T01:59:59", "2000-10-29T02:00:00", "2000-10-29T02:00:01", "2000-10-29T02:59:59",
"2000-10-29T03:00:00", "2000-10-29T03:00:01", "2000-10-29T03:59:59", "2000-10-29T04:00:00",
"2000-10-29T04:00:01");
"2000-10-29T04:00:01", "2000-10-29T04:00:00.000001");
}
private List<String> getZoneIdsToTest() {
@@ -281,11 +281,11 @@ private void offsetTimestamps(ZoneId dataZone, LocalDateTime localDateTime, Stri
public void testSetLocalDateTimeBc() throws SQLException {
// use BC for funsies
List<LocalDateTime> bcDates = new ArrayList<LocalDateTime>();
bcDates.add(LocalDateTime.parse("1997-06-30T23:59:59").with(ChronoField.ERA, IsoEra.BCE.getValue()));
bcDates.add(LocalDateTime.parse("0997-06-30T23:59:59").with(ChronoField.ERA, IsoEra.BCE.getValue()));
bcDates.add(LocalDateTime.parse("1997-06-30T23:59:59.999999").with(ChronoField.ERA, IsoEra.BCE.getValue()));
bcDates.add(LocalDateTime.parse("0997-06-30T23:59:59.999999").with(ChronoField.ERA, IsoEra.BCE.getValue()));
for (LocalDateTime bcDate : bcDates) {
// -1997-06-30T23:59:59 -> 1997-06-30 23:59:59 BC
// -1997-06-30T23:59:59.999999 -> 1997-06-30 23:59:59.999999 BC
String expected = bcDate.toString().substring(1).replace('T', ' ') + " BC";
localTimestamps(ZoneOffset.UTC, bcDate, expected);
}
@@ -313,6 +313,19 @@ public void testSetLocalDateWithoutType() throws SQLException {
assertEquals(expected, actual);
}
/**
* Test the behavior setObject for time columns.
*/
@Test
public void testSetLocalTimeAndReadBack() throws SQLException {
LocalTime data = LocalTime.parse("16:21:51.123456");
insertWithoutType(data, "time_without_time_zone_column");
String readBack = readString("time_without_time_zone_column");
assertEquals("16:21:51.123456", readBack);
}
/**
* Test the behavior setObject for time columns.
*/

0 comments on commit 6b3a1ef

Please sign in to comment.