NIFI-6479 Fixes timezone issues in TestJdbcCommon

Dates calculated for method testConvertToAvroStreamForDateTimeAsLogicalType now handle timezone correctly
Added debug logging of date/time calculations and expectations

This closes #3630.

Signed-off-by: Koji Kawamura <ijokarumawak@apache.org>
This commit is contained in:
Jeff Storck 2019-07-24 14:14:25 -04:00 committed by Koji Kawamura
parent 403c07d4e8
commit b938fc20a3
No known key found for this signature in database
GPG Key ID: 36136B0EC89E4758
1 changed files with 58 additions and 26 deletions

View File

@ -45,11 +45,18 @@ import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.util.HashSet;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
@ -73,11 +80,13 @@ import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TestJdbcCommon {
private static final Logger LOGGER = LoggerFactory.getLogger(TestJdbcCommon.class);
static final String createTable = "create table restaurants(id integer, name varchar(20), city varchar(50))";
static final String dropTable = "drop table restaurants";
@ -652,7 +661,7 @@ public class TestJdbcCommon {
}
@Test
public void testConvertToAvroStreamForDateTimeAsString() throws SQLException, IOException, ParseException {
public void testConvertToAvroStreamForDateTimeAsString() throws SQLException, IOException {
final JdbcCommon.AvroConversionOptions options = JdbcCommon.AvroConversionOptions
.builder().convertNames(true).useLogicalTypes(false).build();
@ -664,7 +673,7 @@ public class TestJdbcCommon {
}
@Test
public void testConvertToAvroStreamForDateTimeAsLogicalType() throws SQLException, IOException, ParseException {
public void testConvertToAvroStreamForDateTimeAsLogicalType() throws SQLException, IOException {
final JdbcCommon.AvroConversionOptions options = JdbcCommon.AvroConversionOptions
.builder().convertNames(true).useLogicalTypes(true).build();
@ -672,31 +681,56 @@ public class TestJdbcCommon {
(record, date) -> {
final int daysSinceEpoch = (int) record.get("date");
final long millisSinceEpoch = TimeUnit.MILLISECONDS.convert(daysSinceEpoch, TimeUnit.DAYS);
assertEquals(date, new java.sql.Date(millisSinceEpoch));
java.sql.Date actual = java.sql.Date.valueOf(Instant.ofEpochMilli(millisSinceEpoch).atZone(ZoneOffset.UTC).toLocalDate());
LOGGER.debug("comparing dates, expecting '{}', actual '{}'", date, actual);
assertEquals(date, actual);
},
(record, time) -> assertEquals(time, new Time((int) record.get("time"))),
(record, timestamp) -> assertEquals(timestamp, new Timestamp((long) record.get("timestamp")))
(record, time) -> {
int millisSinceMidnight = (int) record.get("time");
LocalTime localTime = Instant.ofEpochMilli(millisSinceMidnight).atZone(ZoneId.systemDefault()).toLocalTime();
Time actual = Time.valueOf(localTime);
LOGGER.debug("comparing times, expecting '{}', actual '{}'", time, actual);
assertEquals(time, actual);
},
(record, timestamp) -> {
Timestamp actual = new Timestamp((long) record.get("timestamp"));
LOGGER.debug("comparing date/time, expecting '{}', actual '{}'", timestamp, actual);
assertEquals(timestamp, actual);
}
);
}
private void testConvertToAvroStreamForDateTime(
JdbcCommon.AvroConversionOptions options, BiConsumer<GenericRecord, java.sql.Date> assertDate,
BiConsumer<GenericRecord, Time> assertTime, BiConsumer<GenericRecord, Timestamp> assertTimeStamp)
throws SQLException, IOException, ParseException {
throws SQLException, IOException {
final ResultSetMetaData metadata = mock(ResultSetMetaData.class);
final ResultSet rs = mock(ResultSet.class);
when(rs.getMetaData()).thenReturn(metadata);
BiFunction<String, String, Long> toMillis = (format, dateStr) -> {
try {
final SimpleDateFormat dateFormat = new SimpleDateFormat(format);
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
return dateFormat.parse(dateStr).getTime();
} catch (ParseException e) {
throw new RuntimeException(e);
// create a ZonedDateTime (UTC) given a formatting pattern and a date/time string
BiFunction<String, String, ZonedDateTime> toZonedDateTime = (format, dateStr) -> {
DateTimeFormatterBuilder dateTimeFormatterBuilder = new DateTimeFormatterBuilder().appendPattern(format);
TemporalAccessor temporalAccessor = DateTimeFormatter.ofPattern(format).parse(dateStr);
if (!temporalAccessor.isSupported(ChronoField.EPOCH_DAY)) {
ZonedDateTime utcNow = LocalDateTime.now().atZone(ZoneId.systemDefault());
dateTimeFormatterBuilder.parseDefaulting(ChronoField.DAY_OF_MONTH, utcNow.getDayOfMonth())
.parseDefaulting(ChronoField.MONTH_OF_YEAR, utcNow.getMonthValue())
.parseDefaulting(ChronoField.YEAR, utcNow.getYear());
}
if (!temporalAccessor.isSupported(ChronoField.MILLI_OF_SECOND)) {
dateTimeFormatterBuilder.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0);
}
DateTimeFormatter formatter = dateTimeFormatterBuilder.toFormatter();
LocalDateTime dateTime = LocalDateTime.parse(dateStr, formatter);
ZonedDateTime zonedDateTime = dateTime.atZone(ZoneOffset.UTC).withZoneSameInstant(ZoneOffset.UTC);
LOGGER.debug("calculated ZonedDateTime '{}' from format '{}', date/time string '{}'", zonedDateTime, format, dateStr);
return zonedDateTime;
};
when(metadata.getColumnCount()).thenReturn(3);
@ -704,26 +738,24 @@ public class TestJdbcCommon {
when(metadata.getColumnType(1)).thenReturn(Types.DATE);
when(metadata.getColumnName(1)).thenReturn("date");
final java.sql.Date date = new java.sql.Date(toMillis.apply("yyyy/MM/dd", "2017/05/10"));
ZonedDateTime parsedDate = toZonedDateTime.apply("yyyy/MM/dd", "2017/05/10");
final java.sql.Date date = java.sql.Date.valueOf(parsedDate.toLocalDate());
when(rs.getObject(1)).thenReturn(date);
when(metadata.getColumnType(2)).thenReturn(Types.TIME);
when(metadata.getColumnName(2)).thenReturn("time");
final Time time = new Time(toMillis.apply("HH:mm:ss.SSS", "12:34:56.789"));
ZonedDateTime parsedTime = toZonedDateTime.apply("HH:mm:ss.SSS", "12:34:56.789");
final Time time = Time.valueOf(parsedTime.toLocalTime());
when(rs.getObject(2)).thenReturn(time);
when(metadata.getColumnType(3)).thenReturn(Types.TIMESTAMP);
when(metadata.getColumnName(3)).thenReturn("timestamp");
final Timestamp timestamp = new Timestamp(toMillis.apply("yyyy/MM/dd HH:mm:ss.SSS", "2017/05/11 19:59:39.123"));
ZonedDateTime parsedDateTime = toZonedDateTime.apply("yyyy/MM/dd HH:mm:ss.SSS", "2017/05/11 19:59:39.123");
final Timestamp timestamp = Timestamp.valueOf(parsedDateTime.toLocalDateTime());
when(rs.getObject(3)).thenReturn(timestamp);
final AtomicInteger counter = new AtomicInteger(1);
Mockito.doAnswer(new Answer<Boolean>() {
@Override
public Boolean answer(InvocationOnMock invocation) throws Throwable {
return counter.getAndDecrement() > 0;
}
}).when(rs).next();
Mockito.doAnswer((Answer<Boolean>) invocation -> counter.getAndDecrement() > 0).when(rs).next();
final ByteArrayOutputStream baos = new ByteArrayOutputStream();