Skip to content

Commit

Permalink
Support TIMESTAMP WITH TIME ZONE as ZonedDateTime in JDBC
Browse files Browse the repository at this point in the history
  • Loading branch information
findepi committed Dec 8, 2020
1 parent 73a6929 commit 2d92d05
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 0 deletions.
Expand Up @@ -149,6 +149,7 @@ abstract class AbstractPrestoResultSet
.add("time with time zone", String.class, Time.class, AbstractPrestoResultSet::parseTimeWithTimeZone)
.add("timestamp", String.class, Timestamp.class, string -> parseTimestampAsSqlTimestamp(string, ZoneId.systemDefault()))
.add("timestamp with time zone", String.class, Timestamp.class, AbstractPrestoResultSet::parseTimestampWithTimeZoneAsSqlTimestamp)
.add("timestamp with time zone", String.class, ZonedDateTime.class, AbstractPrestoResultSet::parseTimestampWithTimeZone)
.add("interval year to month", String.class, PrestoIntervalYearMonth.class, AbstractPrestoResultSet::parseIntervalYearMonth)
.add("interval day to second", String.class, PrestoIntervalDayTime.class, AbstractPrestoResultSet::parseIntervalDayTime)
.add("array", List.class, List.class, (type, list) -> (List<?>) convertFromClientRepresentation(type, list))
Expand Down Expand Up @@ -417,6 +418,12 @@ private Timestamp getTimestamp(int columnIndex, DateTimeZone localTimeZone)
throw new IllegalArgumentException("Expected column to be a timestamp type but is " + columnInfo.getColumnTypeName());
}

private static ZonedDateTime parseTimestampWithTimeZone(String value)
{
ParsedTimestamp parsed = parseTimestamp(value);
return toZonedDateTime(parsed, timezone -> ZoneId.of(timezone.orElseThrow(() -> new IllegalArgumentException("Time zone missing: " + value))));
}

@Override
public InputStream getAsciiStream(int columnIndex)
throws SQLException
Expand Down Expand Up @@ -1999,6 +2006,25 @@ private static Timestamp toTimestamp(String originalValue, ParsedTimestamp parse
return timestamp;
}

private static ZonedDateTime toZonedDateTime(ParsedTimestamp parsed, Function<Optional<String>, ZoneId> timeZoneParser)
{
int year = parsed.year;
int month = parsed.month;
int day = parsed.day;
int hour = parsed.hour;
int minute = parsed.minute;
int second = parsed.second;
long picosOfSecond = parsed.picosOfSecond;
ZoneId zoneId = timeZoneParser.apply(parsed.timezone);

ZonedDateTime zonedDateTime = LocalDateTime.of(year, month, day, hour, minute, second, 0)
.atZone(zoneId);

int nanoOfSecond = (int) rescale(picosOfSecond, 12, 9);
zonedDateTime = zonedDateTime.plusNanos(nanoOfSecond);
return zonedDateTime;
}

private static Time parseTime(String value, ZoneId localTimeZone)
{
Matcher matcher = TIME_PATTERN.matcher(value);
Expand Down
Expand Up @@ -28,6 +28,7 @@
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
Expand Down Expand Up @@ -493,10 +494,27 @@ public void testTimestampWithTimeZone()
throws Exception
{
try (ConnectedStatement connectedStatement = newStatement()) {
// zero
checkRepresentation(connectedStatement.getStatement(), "TIMESTAMP '1970-01-01 00:00:00.000 +00:00'", Types.TIMESTAMP_WITH_TIMEZONE, (rs, column) -> {
Timestamp timestampForPointInTime = Timestamp.from(Instant.EPOCH);
assertEquals(rs.getObject(column), timestampForPointInTime);
assertEquals(rs.getObject(column, ZonedDateTime.class), ZonedDateTime.ofInstant(Instant.EPOCH, ZoneId.of("UTC")));
assertThatThrownBy(() -> rs.getDate(column))
.isInstanceOf(SQLException.class)
.hasMessage("Expected value to be a date but is: 1970-01-01 00:00:00.000 UTC");
assertThatThrownBy(() -> rs.getTime(column))
.isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/prestosql/presto/issues/5315) SQLException
.hasMessage(serverSupportsVariablePrecisionTimestampWithTimeZone()
? "Expected column to be a time type but is timestamp with time zone(3)" // TODO (https://github.com/prestosql/presto/issues/5317) placement of precision parameter
: "Expected column to be a time type but is timestamp with time zone");
assertEquals(rs.getTimestamp(column), timestampForPointInTime);
});

checkRepresentation(connectedStatement.getStatement(), "TIMESTAMP '2018-02-13 13:14:15.227 Europe/Warsaw'", Types.TIMESTAMP_WITH_TIMEZONE, (rs, column) -> {
ZonedDateTime zonedDateTime = ZonedDateTime.of(2018, 2, 13, 13, 14, 15, 227_000_000, ZoneId.of("Europe/Warsaw"));
Timestamp timestampForPointInTime = Timestamp.from(zonedDateTime.toInstant());
assertEquals(rs.getObject(column), timestampForPointInTime); // TODO this should represent TIMESTAMP '2018-02-13 13:14:15.227 Europe/Warsaw'
assertEquals(rs.getObject(column, ZonedDateTime.class), zonedDateTime);
assertThatThrownBy(() -> rs.getDate(column))
.isInstanceOf(SQLException.class)
.hasMessage("Expected value to be a date but is: 2018-02-13 13:14:15.227 Europe/Warsaw");
Expand All @@ -514,6 +532,7 @@ public void testTimestampWithTimeZone()
ZonedDateTime zonedDateTime = ZonedDateTime.of(2020, 1, 1, 0, 0, 0, 0, ZoneId.of("Europe/Warsaw"));
Timestamp timestampForPointInTime = Timestamp.from(zonedDateTime.toInstant());
assertEquals(rs.getObject(column), timestampForPointInTime); // TODO this should represent TIMESTAMP '2019-12-31 23:59:59.999999999999 Europe/Warsaw'
assertEquals(rs.getObject(column, ZonedDateTime.class), zonedDateTime);
assertThatThrownBy(() -> rs.getDate(column))
.isInstanceOf(SQLException.class)
.hasMessage("Expected value to be a date but is: 2019-12-31 23:59:59.999999999999 Europe/Warsaw");
Expand All @@ -532,6 +551,7 @@ public void testTimestampWithTimeZone()
ZonedDateTime zonedDateTime = ZonedDateTime.of(2020, 1, 1, 0, 0, 0, 0, jvmZone);
Timestamp timestampForPointInTime = Timestamp.from(zonedDateTime.toInstant());
assertEquals(rs.getObject(column), timestampForPointInTime); // TODO this should represent TIMESTAMP '2019-12-31 23:59:59.999999999999 JVM ZONE'
assertEquals(rs.getObject(column, ZonedDateTime.class), zonedDateTime);
assertThatThrownBy(() -> rs.getDate(column))
.isInstanceOf(SQLException.class)
.hasMessage("Expected value to be a date but is: 2019-12-31 23:59:59.999999999999 America/Bahia_Banderas");
Expand Down Expand Up @@ -560,6 +580,7 @@ public void testTimestampWithTimeZone()
ZonedDateTime zonedDateTime = ZonedDateTime.of(1970, 1, 1, 9, 14, 15, 227_000_000, ZoneId.of("Europe/Warsaw"));
Timestamp timestampForPointInTime = Timestamp.from(zonedDateTime.toInstant());
assertEquals(rs.getObject(column), timestampForPointInTime); // TODO this should represent TIMESTAMP '1970-01-01 09:14:15.227 Europe/Warsaw'
assertEquals(rs.getObject(column, ZonedDateTime.class), zonedDateTime);
assertThatThrownBy(() -> rs.getDate(column))
.isInstanceOf(SQLException.class)
.hasMessage("Expected value to be a date but is: 1970-01-01 09:14:15.227 Europe/Warsaw");
Expand All @@ -575,6 +596,7 @@ public void testTimestampWithTimeZone()
ZonedDateTime zonedDateTime = ZonedDateTime.of(1970, 1, 1, 0, 14, 15, 227_000_000, ZoneId.of("Europe/Warsaw"));
Timestamp timestampForPointInTime = Timestamp.from(zonedDateTime.toInstant());
assertEquals(rs.getObject(column), timestampForPointInTime); // TODO this should represent TIMESTAMP '1970-01-01 00:14:15.227 Europe/Warsaw'
assertEquals(rs.getObject(column, ZonedDateTime.class), zonedDateTime);
assertThatThrownBy(() -> rs.getDate(column))
.isInstanceOf(SQLException.class)
.hasMessage("Expected value to be a date but is: 1970-01-01 00:14:15.227 Europe/Warsaw");
Expand All @@ -596,6 +618,7 @@ public void testTimestampWithTimeZone()
ZonedDateTime zonedDateTime = ZonedDateTime.of(12345, 1, 23, 1, 23, 45, 123_456_789, ZoneId.of("Europe/Warsaw"));
Timestamp timestampForPointInTime = Timestamp.from(zonedDateTime.toInstant());
assertEquals(rs.getObject(column), timestampForPointInTime); // TODO this should contain the zone
assertEquals(rs.getObject(column, ZonedDateTime.class), zonedDateTime);
assertThatThrownBy(() -> rs.getDate(column))
.isInstanceOf(SQLException.class)
.hasMessage("Expected value to be a date but is: +12345-01-23 01:23:45.123456789 Europe/Warsaw");
Expand Down
Expand Up @@ -42,6 +42,8 @@
import java.sql.Timestamp;
import java.sql.Types;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.List;
Expand Down Expand Up @@ -299,16 +301,20 @@ public void testTypes()
assertEquals(rs.getTimestamp(5), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis()));
assertEquals(rs.getTimestamp(5, ASIA_ORAL_CALENDAR), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis()));
assertEquals(rs.getObject(5), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis()));
assertEquals(rs.getObject(5, ZonedDateTime.class), ZonedDateTime.of(2004, 5, 6, 6, 7, 8, 0, ZoneOffset.ofHoursMinutes(6, 17)));
assertEquals(rs.getTimestamp("e"), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis()));
assertEquals(rs.getTimestamp("e", ASIA_ORAL_CALENDAR), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis()));
assertEquals(rs.getObject("e"), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis()));
assertEquals(rs.getObject("e", ZonedDateTime.class), ZonedDateTime.of(2004, 5, 6, 6, 7, 8, 0, ZoneOffset.ofHoursMinutes(6, 17)));

assertEquals(rs.getTimestamp(6), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis()));
assertEquals(rs.getTimestamp(6, ASIA_ORAL_CALENDAR), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis()));
assertEquals(rs.getObject(6), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis()));
assertEquals(rs.getObject(6, ZonedDateTime.class), ZonedDateTime.of(2007, 8, 9, 9, 10, 11, 0, ZoneId.of("Europe/Berlin")));
assertEquals(rs.getTimestamp("f"), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis()));
assertEquals(rs.getTimestamp("f", ASIA_ORAL_CALENDAR), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis()));
assertEquals(rs.getObject("f"), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis()));
assertEquals(rs.getObject("f", ZonedDateTime.class), ZonedDateTime.of(2007, 8, 9, 9, 10, 11, 0, ZoneId.of("Europe/Berlin")));

assertEquals(rs.getDate(7), new Date(new DateTime(2013, 3, 22, 0, 0).getMillis()));
assertEquals(rs.getDate(7, ASIA_ORAL_CALENDAR), new Date(new DateTime(2013, 3, 22, 0, 0, ASIA_ORAL_ZONE).getMillis()));
Expand Down

0 comments on commit 2d92d05

Please sign in to comment.