Merge pull request #209 from rhauch/dbz-205

DBZ-205 Corrected MySQL connector to handle 2-digit years
This commit is contained in:
Randall Hauch 2017-03-27 11:10:04 -05:00 committed by GitHub
commit 25adc3f642
18 changed files with 360 additions and 117 deletions

View File

@ -13,6 +13,9 @@
import java.sql.Types;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.util.List;
import org.apache.kafka.connect.data.Field;
@ -50,6 +53,52 @@
@Immutable
public class MySqlValueConverters extends JdbcValueConverters {
/**
* A utility method that adjusts <a href="https://dev.mysql.com/doc/refman/5.7/en/two-digit-years.html">ambiguous</a> 2-digit
* year values of DATETIME, DATE, and TIMESTAMP types using these MySQL-specific rules:
* <ul>
* <li>Year values in the range 00-69 are converted to 2000-2069.</li>
* <li>Year values in the range 70-99 are converted to 1970-1999.</li>
* </ul>
*
* @param temporal the temporal instance to adjust; may not be null
* @return the possibly adjusted temporal instance; never null
*/
protected static Temporal adjustTemporal(Temporal temporal) {
if (temporal.isSupported(ChronoField.YEAR)) {
int year = temporal.get(ChronoField.YEAR);
if (0 <= year && year <= 69) {
temporal = temporal.plus(2000, ChronoUnit.YEARS);
} else if (70 <= year && year <= 99) {
temporal = temporal.plus(1900, ChronoUnit.YEARS);
}
}
return temporal;
}
/**
* A utility method that adjusts <a href="https://dev.mysql.com/doc/refman/5.7/en/two-digit-years.html">ambiguous</a> 2-digit
* year values of YEAR type using these MySQL-specific rules:
* <ul>
* <li>Year values in the range 01-69 are converted to 2001-2069.</li>
* <li>Year values in the range 70-99 are converted to 1970-1999.</li>
* </ul>
* MySQL treats YEAR(4) the same, except that a numeric 00 inserted into YEAR(4) results in 0000 rather than 2000; to
* specify zero for YEAR(4) and have it be interpreted as 2000, specify it as a string '0' or '00'. This should be handled
* by MySQL before Debezium sees the value.
*
* @param year the year value to adjust; may not be null
* @return the possibly adjusted year number; never null
*/
protected static int adjustYear(int year) {
if (0 < year && year <= 69) {
year += 2000;
} else if (70 <= year && year <= 99) {
year += 1900;
}
return year;
}
/**
* Create a new instance that always uses UTC for the default time zone when converting values without timezone information
* to values that require timezones.
@ -79,7 +128,7 @@ public MySqlValueConverters(DecimalMode decimalMode, boolean adaptiveTimePrecisi
* have timezones; may be null if UTC is to be used
*/
public MySqlValueConverters(DecimalMode decimalMode, boolean adaptiveTimePrecision, ZoneOffset defaultOffset) {
super(decimalMode, adaptiveTimePrecision, defaultOffset);
super(decimalMode, adaptiveTimePrecision, defaultOffset, MySqlValueConverters::adjustTemporal);
}
@Override
@ -265,15 +314,15 @@ protected Object convertYearToInt(Column column, Field fieldDefn, Object data) {
}
if (data instanceof java.time.Year) {
// The MySQL binlog always returns a Year object ...
return ((java.time.Year) data).getValue();
return adjustYear(((java.time.Year) data).getValue());
}
if (data instanceof java.sql.Date) {
// MySQL JDBC driver sometimes returns a Java SQL Date object ...
return ((java.sql.Date) data).getYear();
return adjustYear(((java.sql.Date) data).getYear());
}
if (data instanceof Number) {
// MySQL JDBC driver sometimes returns a short ...
return ((Number) data).intValue();
return adjustYear(((Number) data).intValue());
}
return handleUnknownData(column, fieldDefn, data);
}
@ -306,7 +355,7 @@ protected Object convertEnumToString(List<String> options, Column column, Field
if (options != null) {
// The binlog will contain an int with the 1-based index of the option in the enum value ...
int value = ((Integer)data).intValue();
int value = ((Integer) data).intValue();
if (value == 0) {
// an invalid value was specified, which corresponds to the empty string '' and an index of 0
return "";

View File

@ -94,7 +94,7 @@ VALUES (default, '2016-01-16', 1001, 1, 102),
(default, '2016-01-17', 1002, 2, 105),
(default, '2016-02-18', 1004, 3, 109),
(default, '2016-02-19', 1002, 2, 106),
(default, '2016-02-21', 1003, 1, 107);
(default, '16-02-21', 1003, 1, 107);
-- ----------------------------------------------------------------------------------------------------------------

View File

@ -18,6 +18,7 @@
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.temporal.TemporalAdjuster;
import java.util.concurrent.TimeUnit;
import org.apache.kafka.connect.data.Struct;
@ -46,6 +47,7 @@
public class MySqlConnectorRegressionIT extends AbstractConnectorTest {
private static final Path DB_HISTORY_PATH = Testing.Files.createTestingPath("file-db-history-regression.txt").toAbsolutePath();
private static final TemporalAdjuster ADJUSTER = MySqlValueConverters::adjustTemporal;
private Configuration config;
@ -151,7 +153,7 @@ public void shouldConsumeAllEventsFromDatabaseUsingBinlogAndNoSnapshot() throws
assertThat(c1Date.getYear()).isEqualTo(2014);
assertThat(c1Date.getMonth()).isEqualTo(Month.SEPTEMBER);
assertThat(c1Date.getDayOfMonth()).isEqualTo(8);
assertThat(io.debezium.time.Date.toEpochDay(c1Date)).isEqualTo(c1);
assertThat(io.debezium.time.Date.toEpochDay(c1Date, ADJUSTER)).isEqualTo(c1);
// '17:51:04.777'
Integer c2 = after.getInt32("c2"); // milliseconds past midnight
@ -160,7 +162,7 @@ public void shouldConsumeAllEventsFromDatabaseUsingBinlogAndNoSnapshot() throws
assertThat(c2Time.getMinute()).isEqualTo(51);
assertThat(c2Time.getSecond()).isEqualTo(4);
assertThat(c2Time.getNano()).isEqualTo((int) TimeUnit.MILLISECONDS.toNanos(780));
assertThat(io.debezium.time.Time.toMilliOfDay(c2Time)).isEqualTo(c2);
assertThat(io.debezium.time.Time.toMilliOfDay(c2Time, ADJUSTER)).isEqualTo(c2);
// '2014-09-08 17:51:04.777'
Long c3 = after.getInt64("c3"); // epoch millis
@ -176,7 +178,7 @@ public void shouldConsumeAllEventsFromDatabaseUsingBinlogAndNoSnapshot() throws
assertThat(c3DateTime.getMinute()).isEqualTo(51);
assertThat(c3DateTime.getSecond()).isEqualTo(4);
assertThat(c3DateTime.getNano()).isEqualTo((int) TimeUnit.MILLISECONDS.toNanos(780));
assertThat(io.debezium.time.Timestamp.toEpochMillis(c3DateTime)).isEqualTo(c3);
assertThat(io.debezium.time.Timestamp.toEpochMillis(c3DateTime, ADJUSTER)).isEqualTo(c3);
// '2014-09-08 17:51:04.777'
String c4 = after.getString("c4"); // timestamp
@ -213,7 +215,7 @@ public void shouldConsumeAllEventsFromDatabaseUsingBinlogAndNoSnapshot() throws
assertThat(c2Time.getMinute() == 0 || c2Time.getMinute() == 1).isTrue();
assertThat(c2Time.getSecond()).isEqualTo(0);
assertThat(c2Time.getNano()).isEqualTo(0);
assertThat(io.debezium.time.Time.toMilliOfDay(c2Time)).isEqualTo(c2);
assertThat(io.debezium.time.Time.toMilliOfDay(c2Time, ADJUSTER)).isEqualTo(c2);
assertThat(after.getInt64("c3")).isNull(); // epoch millis
@ -367,7 +369,7 @@ public void shouldConsumeAllEventsFromDatabaseUsingBinlogAndNoSnapshotAndConnect
assertThat(c2Time.getMinute()).isEqualTo(51);
assertThat(c2Time.getSecond()).isEqualTo(4);
assertThat(c2Time.getNano()).isEqualTo((int) TimeUnit.MILLISECONDS.toNanos(780));
assertThat(io.debezium.time.Time.toMilliOfDay(c2Time)).isEqualTo((int) c2.getTime());
assertThat(io.debezium.time.Time.toMilliOfDay(c2Time, ADJUSTER)).isEqualTo((int) c2.getTime());
// '2014-09-08 17:51:04.777'
java.util.Date c3 = (java.util.Date) after.get("c3"); // epoch millis
@ -383,7 +385,7 @@ public void shouldConsumeAllEventsFromDatabaseUsingBinlogAndNoSnapshotAndConnect
assertThat(c3DateTime.getMinute()).isEqualTo(51);
assertThat(c3DateTime.getSecond()).isEqualTo(4);
assertThat(c3DateTime.getNano()).isEqualTo((int) TimeUnit.MILLISECONDS.toNanos(780));
assertThat(io.debezium.time.Timestamp.toEpochMillis(c3DateTime)).isEqualTo(c3.getTime());
assertThat(io.debezium.time.Timestamp.toEpochMillis(c3DateTime, ADJUSTER)).isEqualTo(c3.getTime());
// '2014-09-08 17:51:04.777'
String c4 = after.getString("c4"); // MySQL timestamp, so always ZonedTimestamp
@ -421,7 +423,7 @@ public void shouldConsumeAllEventsFromDatabaseUsingBinlogAndNoSnapshotAndConnect
assertThat(c2Time.getMinute() == 0 || c2Time.getMinute() == 1).isTrue();
assertThat(c2Time.getSecond()).isEqualTo(0);
assertThat(c2Time.getNano()).isEqualTo(0);
assertThat(io.debezium.time.Time.toMilliOfDay(c2Time)).isEqualTo((int) c2.getTime());
assertThat(io.debezium.time.Time.toMilliOfDay(c2Time, ADJUSTER)).isEqualTo((int) c2.getTime());
java.util.Date c3 = (java.util.Date) after.get("c3"); // epoch millis
assertThat(c3).isNull();
@ -568,7 +570,7 @@ public void shouldConsumeAllEventsFromDatabaseUsingSnapshot() throws SQLExceptio
assertThat(c1Date.getYear()).isEqualTo(2014);
assertThat(c1Date.getMonth()).isEqualTo(Month.SEPTEMBER);
assertThat(c1Date.getDayOfMonth()).isEqualTo(8);
assertThat(io.debezium.time.Date.toEpochDay(c1Date)).isEqualTo(c1);
assertThat(io.debezium.time.Date.toEpochDay(c1Date, ADJUSTER)).isEqualTo(c1);
// '17:51:04.777'
Integer c2 = after.getInt32("c2"); // milliseconds past midnight
@ -578,7 +580,7 @@ public void shouldConsumeAllEventsFromDatabaseUsingSnapshot() throws SQLExceptio
assertThat(c2Time.getSecond()).isEqualTo(4);
assertThat(c2Time.getNano()).isEqualTo(0); // What!?!? The MySQL Connect/J driver indeed returns 0 for fractional
// part
assertThat(io.debezium.time.Time.toMilliOfDay(c2Time)).isEqualTo(c2);
assertThat(io.debezium.time.Time.toMilliOfDay(c2Time, ADJUSTER)).isEqualTo(c2);
// '2014-09-08 17:51:04.777'
Long c3 = after.getInt64("c3"); // epoch millis
@ -594,7 +596,7 @@ public void shouldConsumeAllEventsFromDatabaseUsingSnapshot() throws SQLExceptio
assertThat(c3DateTime.getMinute()).isEqualTo(51);
assertThat(c3DateTime.getSecond()).isEqualTo(4);
assertThat(c3DateTime.getNano()).isEqualTo((int) TimeUnit.MILLISECONDS.toNanos(780));
assertThat(io.debezium.time.Timestamp.toEpochMillis(c3DateTime)).isEqualTo(c3);
assertThat(io.debezium.time.Timestamp.toEpochMillis(c3DateTime, ADJUSTER)).isEqualTo(c3);
// '2014-09-08 17:51:04.777'
String c4 = after.getString("c4"); // timestamp

View File

@ -0,0 +1,67 @@
/*
* Copyright Debezium Authors.
*
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package io.debezium.connector.mysql;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.temporal.TemporalAdjuster;
import org.junit.Test;
import static org.fest.assertions.Assertions.assertThat;
/**
* @author Randall Hauch
*
*/
public class MySqlValueConvertersTest {
private static final TemporalAdjuster ADJUSTER = MySqlValueConverters::adjustTemporal;
@Test
public void shouldAdjustLocalDateWithTwoDigitYears() {
assertThat(ADJUSTER.adjustInto(localDateWithYear(00))).isEqualTo(localDateWithYear(2000));
assertThat(ADJUSTER.adjustInto(localDateWithYear(01))).isEqualTo(localDateWithYear(2001));
assertThat(ADJUSTER.adjustInto(localDateWithYear(10))).isEqualTo(localDateWithYear(2010));
assertThat(ADJUSTER.adjustInto(localDateWithYear(69))).isEqualTo(localDateWithYear(2069));
assertThat(ADJUSTER.adjustInto(localDateWithYear(70))).isEqualTo(localDateWithYear(1970));
assertThat(ADJUSTER.adjustInto(localDateWithYear(71))).isEqualTo(localDateWithYear(1971));
assertThat(ADJUSTER.adjustInto(localDateWithYear(99))).isEqualTo(localDateWithYear(1999));
}
@Test
public void shouldAdjustLocalDateTimeWithTwoDigitYears() {
assertThat(ADJUSTER.adjustInto(localDateTimeWithYear(00))).isEqualTo(localDateTimeWithYear(2000));
assertThat(ADJUSTER.adjustInto(localDateTimeWithYear(01))).isEqualTo(localDateTimeWithYear(2001));
assertThat(ADJUSTER.adjustInto(localDateTimeWithYear(10))).isEqualTo(localDateTimeWithYear(2010));
assertThat(ADJUSTER.adjustInto(localDateTimeWithYear(69))).isEqualTo(localDateTimeWithYear(2069));
assertThat(ADJUSTER.adjustInto(localDateTimeWithYear(70))).isEqualTo(localDateTimeWithYear(1970));
assertThat(ADJUSTER.adjustInto(localDateTimeWithYear(71))).isEqualTo(localDateTimeWithYear(1971));
assertThat(ADJUSTER.adjustInto(localDateTimeWithYear(99))).isEqualTo(localDateTimeWithYear(1999));
}
@Test
public void shouldNotAdjustLocalDateWithThreeDigitYears() {
assertThat(ADJUSTER.adjustInto(localDateWithYear(-1))).isEqualTo(localDateWithYear(-1));
assertThat(ADJUSTER.adjustInto(localDateWithYear(100))).isEqualTo(localDateWithYear(100));
}
@Test
public void shouldNotAdjustLocalDateTimeWithThreeDigitYears() {
assertThat(ADJUSTER.adjustInto(localDateTimeWithYear(-1))).isEqualTo(localDateTimeWithYear(-1));
assertThat(ADJUSTER.adjustInto(localDateTimeWithYear(100))).isEqualTo(localDateTimeWithYear(100));
}
protected LocalDate localDateWithYear(int year) {
return LocalDate.of(year, Month.APRIL, 4);
}
protected LocalDateTime localDateTimeWithYear(int year) {
return LocalDateTime.of(year, Month.APRIL, 4, 0, 0, 0);
}
}

View File

@ -17,6 +17,7 @@
import java.time.ZoneOffset;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import org.apache.kafka.connect.data.Decimal;
import org.apache.kafka.connect.data.Field;
import org.apache.kafka.connect.data.Schema;
@ -53,7 +54,7 @@ public class PostgresValueConverter extends JdbcValueConverters {
protected static final double DAYS_PER_MONTH_AVG = 365.25 / 12.0d;
protected PostgresValueConverter(boolean adaptiveTimePrecision, ZoneOffset defaultOffset) {
super(DecimalMode.PRECISE, adaptiveTimePrecision, defaultOffset);
super(DecimalMode.PRECISE, adaptiveTimePrecision, defaultOffset, null);
}
@Override

View File

@ -30,6 +30,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.kafka.connect.data.Decimal;
import org.apache.kafka.connect.data.Field;
import org.apache.kafka.connect.data.Schema;
@ -120,9 +121,9 @@ protected List<SchemaAndValueField> schemaAndValuesForBinTypes() {
}
protected List<SchemaAndValueField> schemaAndValuesForDateTimeTypes() {
long expectedTs = NanoTimestamp.toEpochNanos(LocalDateTime.parse("2016-11-04T13:51:30"));
long expectedTs = NanoTimestamp.toEpochNanos(LocalDateTime.parse("2016-11-04T13:51:30"), null);
String expectedTz = "2016-11-04T11:51:30Z"; //timestamp is stored with TZ, should be read back with UTC
int expectedDate = Date.toEpochDay(LocalDate.parse("2016-11-04"));
int expectedDate = Date.toEpochDay(LocalDate.parse("2016-11-04"), null);
long expectedTi = LocalTime.parse("13:51:30").toNanoOfDay();
String expectedTtz = "11:51:30Z"; //time is stored with TZ, should be read back at GMT
double interval = MicroDuration.durationMicros(1, 2, 3, 4, 5, 0, PostgresValueConverter.DAYS_PER_MONTH_AVG);

View File

@ -164,11 +164,11 @@ private Document parseDocument(JsonParser parser, boolean nested) throws IOExcep
// disregard this, since it's an extension ...
break;
case NOT_AVAILABLE:
throw new JsonParseException("Non-blocking parsers are not supported", parser.getCurrentLocation());
throw new JsonParseException(parser, "Non-blocking parsers are not supported", parser.getCurrentLocation());
case END_ARRAY:
throw new JsonParseException("Not expecting an END_ARRAY token", parser.getCurrentLocation());
throw new JsonParseException(parser, "Not expecting an END_ARRAY token", parser.getCurrentLocation());
case END_OBJECT:
throw new JsonParseException("Not expecting an END_OBJECT token", parser.getCurrentLocation());
throw new JsonParseException(parser, "Not expecting an END_OBJECT token", parser.getCurrentLocation());
}
token = parser.nextToken();
}
@ -234,13 +234,13 @@ private Array parseArray(JsonParser parser, boolean nested) throws IOException {
// disregard this, since it's an extension ...
break;
case NOT_AVAILABLE:
throw new JsonParseException("Non-blocking parsers are not supported", parser.getCurrentLocation());
throw new JsonParseException(parser, "Non-blocking parsers are not supported", parser.getCurrentLocation());
case FIELD_NAME:
throw new JsonParseException("Not expecting a FIELD_NAME token", parser.getCurrentLocation());
throw new JsonParseException(parser, "Not expecting a FIELD_NAME token", parser.getCurrentLocation());
case END_ARRAY:
throw new JsonParseException("Not expecting an END_ARRAY token", parser.getCurrentLocation());
throw new JsonParseException(parser, "Not expecting an END_ARRAY token", parser.getCurrentLocation());
case END_OBJECT:
throw new JsonParseException("Not expecting an END_OBJECT token", parser.getCurrentLocation());
throw new JsonParseException(parser, "Not expecting an END_OBJECT token", parser.getCurrentLocation());
}
token = parser.nextToken();
}

View File

@ -16,8 +16,10 @@
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZoneOffset;
import java.time.temporal.TemporalAdjuster;
import java.util.BitSet;
import java.util.concurrent.TimeUnit;
import org.apache.kafka.connect.data.Decimal;
import org.apache.kafka.connect.data.Field;
import org.apache.kafka.connect.data.SchemaBuilder;
@ -75,6 +77,7 @@ public static enum DecimalMode {
private final ZoneOffset defaultOffset;
private final boolean adaptiveTimePrecision;
private final DecimalMode decimalMode;
private final TemporalAdjuster adjuster;
/**
* Create a new instance that always uses UTC for the default time zone when converting values without timezone information
@ -82,7 +85,7 @@ public static enum DecimalMode {
* columns.
*/
public JdbcValueConverters() {
this(null, true, ZoneOffset.UTC);
this(null, true, ZoneOffset.UTC, null);
}
/**
@ -91,17 +94,21 @@ public JdbcValueConverters() {
* with the expected SQL/JDBC types.
*
* @param decimalMode how {@code DECIMAL} and {@code NUMERIC} values should be treated; may be null if
* {@link DecimalMode#PRECISE} is to be used
* {@link DecimalMode#PRECISE} is to be used
* @param adaptiveTimePrecision {@code true} if the time, date, and timestamp values should be based upon the precision of the
* database columns using {@link io.debezium.time} semantic types, or {@code false} if they should be fixed to
* millisecond precision using Kafka Connect {@link org.apache.kafka.connect.data} logical types.
* @param defaultOffset the zone offset that is to be used when converting non-timezone related values to values that do
* have timezones; may be null if UTC is to be used
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
*/
public JdbcValueConverters(DecimalMode decimalMode, boolean adaptiveTimePrecision, ZoneOffset defaultOffset) {
public JdbcValueConverters(DecimalMode decimalMode, boolean adaptiveTimePrecision, ZoneOffset defaultOffset,
TemporalAdjuster adjuster) {
this.defaultOffset = defaultOffset != null ? defaultOffset : ZoneOffset.UTC;
this.adaptiveTimePrecision = adaptiveTimePrecision;
this.decimalMode = decimalMode != null ? decimalMode : DecimalMode.PRECISE;
this.adjuster = adjuster;
}
@Override
@ -154,7 +161,7 @@ public SchemaBuilder schemaBuilder(Column column) {
return SchemaBuilder.float64();
case Types.NUMERIC:
case Types.DECIMAL:
switch(decimalMode) {
switch (decimalMode) {
case DOUBLE:
return SchemaBuilder.float64();
case PRECISE:
@ -163,7 +170,7 @@ public SchemaBuilder schemaBuilder(Column column) {
return Decimal.builder(column.scale());
}
// Fixed-length string values
// Fixed-length string values
case Types.CHAR:
case Types.NCHAR:
case Types.NVARCHAR:
@ -259,21 +266,21 @@ public ValueConverter converter(Column column, Field fieldDefn) {
case Types.REAL:
return (data) -> convertReal(column, fieldDefn, data);
case Types.NUMERIC:
switch(decimalMode) {
switch (decimalMode) {
case DOUBLE:
return (data) -> convertDouble(column, fieldDefn, data);
case PRECISE:
return (data) -> convertNumeric(column, fieldDefn, data);
}
case Types.DECIMAL:
switch(decimalMode) {
switch (decimalMode) {
case DOUBLE:
return (data) -> convertDouble(column, fieldDefn, data);
case PRECISE:
return (data) -> convertDecimal(column, fieldDefn, data);
}
// String values
// String values
case Types.CHAR: // variable-length
case Types.VARCHAR: // variable-length
case Types.LONGVARCHAR: // variable-length
@ -361,7 +368,7 @@ protected Object convertTimestampWithZone(Column column, Field fieldDefn, Object
data = OffsetDateTime.of(LocalDate.ofEpochDay(0), LocalTime.MIDNIGHT, defaultOffset); // return epoch
}
try {
return ZonedTimestamp.toIsoString(data, defaultOffset);
return ZonedTimestamp.toIsoString(data, defaultOffset, adjuster);
} catch (IllegalArgumentException e) {
return handleUnknownData(column, fieldDefn, data);
}
@ -392,7 +399,7 @@ protected Object convertTimeWithZone(Column column, Field fieldDefn, Object data
data = OffsetTime.of(LocalTime.MIDNIGHT, defaultOffset); // return epoch time
}
try {
return ZonedTime.toIsoString(data, defaultOffset);
return ZonedTime.toIsoString(data, defaultOffset, adjuster);
} catch (IllegalArgumentException e) {
return handleUnknownData(column, fieldDefn, data);
}
@ -421,7 +428,7 @@ protected Object convertTimestampToEpochMillis(Column column, Field fieldDefn, O
return 0L; // return epoch
}
try {
return Timestamp.toEpochMillis(data);
return Timestamp.toEpochMillis(data, adjuster);
} catch (IllegalArgumentException e) {
return handleUnknownData(column, fieldDefn, data);
}
@ -450,7 +457,7 @@ protected Object convertTimestampToEpochMicros(Column column, Field fieldDefn, O
return 0L; // return epoch
}
try {
return MicroTimestamp.toEpochMicros(data);
return MicroTimestamp.toEpochMicros(data, adjuster);
} catch (IllegalArgumentException e) {
return handleUnknownData(column, fieldDefn, data);
}
@ -479,7 +486,7 @@ protected Object convertTimestampToEpochNanos(Column column, Field fieldDefn, Ob
return 0L; // return epoch
}
try {
return NanoTimestamp.toEpochNanos(data);
return NanoTimestamp.toEpochNanos(data, adjuster);
} catch (IllegalArgumentException e) {
return handleUnknownData(column, fieldDefn, data);
}
@ -508,7 +515,7 @@ protected Object convertTimestampToEpochMillisAsDate(Column column, Field fieldD
return new java.util.Date(0L); // return epoch
}
try {
return new java.util.Date(Timestamp.toEpochMillis(data));
return new java.util.Date(Timestamp.toEpochMillis(data, adjuster));
} catch (IllegalArgumentException e) {
return handleUnknownData(column, fieldDefn, data);
}
@ -538,7 +545,7 @@ protected Object convertTimeToMillisPastMidnight(Column column, Field fieldDefn,
return 0; // return epoch
}
try {
return Time.toMilliOfDay(data);
return Time.toMilliOfDay(data, adjuster);
} catch (IllegalArgumentException e) {
return handleUnknownData(column, fieldDefn, data);
}
@ -568,7 +575,7 @@ protected Object convertTimeToMicrosPastMidnight(Column column, Field fieldDefn,
return 0L; // return epoch
}
try {
return MicroTime.toMicroOfDay(data);
return MicroTime.toMicroOfDay(data, adjuster);
} catch (IllegalArgumentException e) {
return handleUnknownData(column, fieldDefn, data);
}
@ -598,7 +605,7 @@ protected Object convertTimeToNanosPastMidnight(Column column, Field fieldDefn,
return 0L; // return epoch
}
try {
return NanoTime.toNanoOfDay(data);
return NanoTime.toNanoOfDay(data, adjuster);
} catch (IllegalArgumentException e) {
return handleUnknownData(column, fieldDefn, data);
}
@ -628,7 +635,7 @@ protected Object convertTimeToMillisPastMidnightAsDate(Column column, Field fiel
return 0L; // return epoch
}
try {
return new java.util.Date(Time.toMilliOfDay(data));
return new java.util.Date(Time.toMilliOfDay(data, adjuster));
} catch (IllegalArgumentException e) {
return handleUnknownData(column, fieldDefn, data);
}
@ -657,7 +664,7 @@ protected Object convertDateToEpochDays(Column column, Field fieldDefn, Object d
return 0; // return epoch
}
try {
return Date.toEpochDay(data);
return Date.toEpochDay(data, adjuster);
} catch (IllegalArgumentException e) {
logger.warn("Unexpected JDBC DATE value for field {} with schema {}: class={}, value={}", fieldDefn.name(),
fieldDefn.schema(), data.getClass(), data);
@ -689,7 +696,7 @@ protected Object convertDateToEpochDaysAsDate(Column column, Field fieldDefn, Ob
return new java.util.Date(0L); // return epoch
}
try {
int epochDay = Date.toEpochDay(data);
int epochDay = Date.toEpochDay(data, adjuster);
long epochMillis = TimeUnit.DAYS.toMillis(epochDay);
return new java.util.Date(epochMillis);
} catch (IllegalArgumentException e) {
@ -1020,7 +1027,7 @@ protected Object convertString(Column column, Field fieldDefn, Object data) {
return ((SQLXML) data).getString();
} catch (SQLException e) {
throw new RuntimeException("Error processing data from " + column.jdbcType() + " and column " + column +
": class=" + data.getClass(), e);
": class=" + data.getClass(), e);
}
}
return data.toString();
@ -1210,7 +1217,8 @@ protected Object handleUnknownData(Column column, Field fieldDefn, Object data)
if (column.isOptional() || fieldDefn.schema().isOptional()) {
Class<?> dataClass = data.getClass();
logger.warn("Unexpected value for JDBC type {} and column {}: class={}", column.jdbcType(), column,
dataClass.isArray() ? dataClass.getSimpleName() : dataClass.getName()); // don't include value in case its sensitive
dataClass.isArray() ? dataClass.getSimpleName() : dataClass.getName()); // don't include value in case its
// sensitive
return null;
}
throw new IllegalArgumentException("Unexpected value for JDBC type " + column.jdbcType() + " and column " + column +

View File

@ -938,7 +938,7 @@ public TokenStream consumeThrough(String expected, String skipMatchingTokens) th
* @throws IllegalStateException if this method was called before the stream was {@link #start() started}
*/
public TokenStream consumeUntil(char expected) throws ParsingException, IllegalStateException {
return consumeUntil(String.valueOf(expected), null);
return consumeUntil(String.valueOf(expected), (String[])null);
}
/**
@ -972,7 +972,7 @@ public TokenStream consumeUntil(char expected, char skipMatchingTokens) throws P
* @throws IllegalStateException if this method was called before the stream was {@link #start() started}
*/
public TokenStream consumeUntil(String expected) throws ParsingException, IllegalStateException {
return consumeUntil(expected, null);
return consumeUntil(expected, (String[])null);
}
/**

View File

@ -5,6 +5,9 @@
*/
package io.debezium.time;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjuster;
import org.apache.kafka.connect.data.Schema;
import org.apache.kafka.connect.data.SchemaBuilder;
@ -55,11 +58,17 @@ public static Schema schema() {
* {@link java.sql.Timestamp}, ignoring any time portions of the supplied value.
*
* @param value the local or SQL date, time, or timestamp value; may not be null
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the number of days past epoch
* @throws IllegalArgumentException if the value is not an instance of the acceptable types
*/
public static int toEpochDay(Object value) {
return (int)Conversions.toLocalDate(value).toEpochDay();
public static int toEpochDay(Object value, TemporalAdjuster adjuster) {
LocalDate date = Conversions.toLocalDate(value);
if (adjuster != null) {
date = date.with(adjuster);
}
return (int)date.toEpochDay();
}
private Date() {

View File

@ -6,6 +6,7 @@
package io.debezium.time;
import java.time.LocalTime;
import java.time.temporal.TemporalAdjuster;
import org.apache.kafka.connect.data.Schema;
import org.apache.kafka.connect.data.SchemaBuilder;
@ -57,11 +58,16 @@ public static Schema schema() {
* {@link java.sql.Timestamp}, ignoring any date portions of the supplied value.
*
* @param value the local or SQL date, time, or timestamp value; may not be null
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the microseconds past midnight
* @throws IllegalArgumentException if the value is not an instance of the acceptable types
*/
public static long toMicroOfDay(Object value) {
public static long toMicroOfDay(Object value, TemporalAdjuster adjuster) {
LocalTime time = Conversions.toLocalTime(value);
if (adjuster != null) {
time = time.with(adjuster);
}
return Math.floorDiv(time.toNanoOfDay(), Conversions.NANOSECONDS_PER_MICROSECOND);
}

View File

@ -6,6 +6,7 @@
package io.debezium.time;
import java.time.LocalDateTime;
import java.time.temporal.TemporalAdjuster;
import org.apache.kafka.connect.data.Schema;
import org.apache.kafka.connect.data.SchemaBuilder;
@ -58,11 +59,16 @@ public static Schema schema() {
* {@link java.sql.Timestamp}.
*
* @param value the local or SQL date, time, or timestamp value; may not be null
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the epoch microseconds
* @throws IllegalArgumentException if the value is not an instance of the acceptable types
*/
public static long toEpochMicros(Object value) {
public static long toEpochMicros(Object value, TemporalAdjuster adjuster) {
LocalDateTime dateTime = Conversions.toLocalDateTime(value);
if (adjuster != null) {
dateTime = dateTime.with(adjuster);
}
long epochNanos = Conversions.toEpochNanos(dateTime);
return Math.floorDiv(epochNanos, Conversions.NANOSECONDS_PER_MICROSECOND);
}

View File

@ -5,6 +5,9 @@
*/
package io.debezium.time;
import java.time.LocalTime;
import java.time.temporal.TemporalAdjuster;
import org.apache.kafka.connect.data.Schema;
import org.apache.kafka.connect.data.SchemaBuilder;
@ -61,11 +64,17 @@ public static Schema schema() {
* {@link java.sql.Timestamp}, ignoring any date portions of the supplied value.
*
* @param value the local or SQL date, time, or timestamp value; may not be null
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the nanoseconds past midnight
* @throws IllegalArgumentException if the value is not an instance of the acceptable types
*/
public static long toNanoOfDay(Object value) {
return Conversions.toLocalTime(value).toNanoOfDay();
public static long toNanoOfDay(Object value, TemporalAdjuster adjuster) {
LocalTime time = Conversions.toLocalTime(value);
if (adjuster !=null) {
time = time.with(adjuster);
}
return time.toNanoOfDay();
}
private NanoTime() {

View File

@ -6,6 +6,7 @@
package io.debezium.time;
import java.time.LocalDateTime;
import java.time.temporal.TemporalAdjuster;
import org.apache.kafka.connect.data.Schema;
import org.apache.kafka.connect.data.SchemaBuilder;
@ -58,11 +59,16 @@ public static Schema schema() {
* {@link java.sql.Timestamp}.
*
* @param value the local or SQL date, time, or timestamp value; may not be null
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the epoch nanoseconds
* @throws IllegalArgumentException if the value is not an instance of the acceptable types
*/
public static long toEpochNanos(Object value) {
public static long toEpochNanos(Object value, TemporalAdjuster adjuster) {
LocalDateTime dateTime = Conversions.toLocalDateTime(value);
if ( adjuster != null) {
dateTime = dateTime.with(adjuster);
}
return Conversions.toEpochNanos(dateTime);
}

View File

@ -6,6 +6,7 @@
package io.debezium.time;
import java.time.LocalTime;
import java.time.temporal.TemporalAdjuster;
import org.apache.kafka.connect.data.Schema;
import org.apache.kafka.connect.data.SchemaBuilder;
@ -57,11 +58,16 @@ public static Schema schema() {
* {@link java.sql.Timestamp}, ignoring any date portions of the supplied value.
*
* @param value the local or SQL date, time, or timestamp value; may not be null
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the milliseconds past midnight
* @throws IllegalArgumentException if the value is not an instance of the acceptable types
*/
public static int toMilliOfDay(Object value) {
public static int toMilliOfDay(Object value, TemporalAdjuster adjuster) {
LocalTime time = Conversions.toLocalTime(value);
if (adjuster != null) {
time = time.with(adjuster);
}
long micros = Math.floorDiv(time.toNanoOfDay(), Conversions.NANOSECONDS_PER_MILLISECOND);
assert Math.abs(micros) < Integer.MAX_VALUE;
return (int) micros;

View File

@ -6,6 +6,7 @@
package io.debezium.time;
import java.time.LocalDateTime;
import java.time.temporal.TemporalAdjuster;
import org.apache.kafka.connect.data.Schema;
import org.apache.kafka.connect.data.SchemaBuilder;
@ -58,11 +59,16 @@ public static Schema schema() {
* {@link java.sql.Timestamp}.
*
* @param value the local or SQL date, time, or timestamp value; may not be null
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the epoch milliseconds
* @throws IllegalArgumentException if the value is not an instance of the acceptable types
*/
public static long toEpochMillis(Object value) {
public static long toEpochMillis(Object value, TemporalAdjuster adjuster) {
LocalDateTime dateTime = Conversions.toLocalDateTime(value);
if (adjuster != null) {
dateTime = dateTime.with(adjuster);
}
long epochNanos = Conversions.toEpochNanos(dateTime);
return Math.floorDiv(epochNanos, Conversions.NANOSECONDS_PER_MILLISECOND);
}

View File

@ -12,6 +12,7 @@
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjuster;
import org.apache.kafka.connect.data.Schema;
import org.apache.kafka.connect.data.SchemaBuilder;
@ -67,18 +68,20 @@ public static Schema schema() {
* @param value the local or SQL date, time, or timestamp value; may not be null
* @param defaultZone the time zone that should be used by default if the value does not have timezone information; may not be
* null
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the microseconds past midnight
* @throws IllegalArgumentException if the value is not an instance of the acceptable types or is null
*/
public static String toIsoString(Object value, ZoneId defaultZone) {
public static String toIsoString(Object value, ZoneId defaultZone, TemporalAdjuster adjuster) {
if ( value instanceof OffsetTime ) {
return toIsoString((OffsetTime) value);
return toIsoString((OffsetTime) value, adjuster);
}
if ( value instanceof OffsetDateTime ) {
return toIsoString((OffsetDateTime) value);
return toIsoString((OffsetDateTime) value, adjuster);
}
if (value instanceof java.util.Date) { // or JDBC subtypes
return toIsoString((java.util.Date) value,defaultZone);
return toIsoString((java.util.Date) value,defaultZone, adjuster);
}
throw new IllegalArgumentException("Unable to convert to OffsetTime from unexpected value '" + value + "' of type " + value.getClass().getName());
}
@ -87,18 +90,28 @@ public static String toIsoString(Object value, ZoneId defaultZone) {
* Get the ISO 8601 formatted representation of the given {@link OffsetDateTime}.
*
* @param timestamp the timestamp value; may not be null
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the ISO 8601 formatted string
*/
public static String toIsoString(OffsetDateTime timestamp) {
public static String toIsoString(OffsetDateTime timestamp, TemporalAdjuster adjuster) {
if (adjuster != null) {
timestamp = timestamp.with(adjuster);
}
return timestamp.toOffsetTime().format(FORMATTER);
}
/**
* Get the ISO 8601 formatted representation of the given {@link OffsetTime}.
*
* @param timestamp the timestamp value; may not be null
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the ISO 8601 formatted string
*/
public static String toIsoString(OffsetTime timestamp) {
public static String toIsoString(OffsetTime timestamp, TemporalAdjuster adjuster) {
if (adjuster != null) {
timestamp = timestamp.with(adjuster);
}
return timestamp.format(FORMATTER);
}
@ -108,17 +121,19 @@ public static String toIsoString(OffsetTime timestamp) {
*
* @param timestamp the timestamp value; may not be null
* @param zoneId the timezone identifier or offset where the timestamp is defined
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the ISO 8601 formatted string
*/
public static String toIsoString(java.util.Date timestamp, ZoneId zoneId) {
public static String toIsoString(java.util.Date timestamp, ZoneId zoneId, TemporalAdjuster adjuster) {
if (timestamp instanceof java.sql.Timestamp) {
return toIsoString((java.sql.Timestamp) timestamp, zoneId);
return toIsoString((java.sql.Timestamp) timestamp, zoneId, adjuster);
}
if (timestamp instanceof java.sql.Date) {
return toIsoString((java.sql.Date) timestamp, zoneId);
return toIsoString((java.sql.Date) timestamp, zoneId, adjuster);
}
if (timestamp instanceof java.sql.Time) {
return toIsoString((java.sql.Time) timestamp, zoneId);
return toIsoString((java.sql.Time) timestamp, zoneId, adjuster);
}
return timestamp.toInstant().atZone(zoneId).format(FORMATTER);
}
@ -129,10 +144,15 @@ public static String toIsoString(java.util.Date timestamp, ZoneId zoneId) {
*
* @param timestamp the JDBC timestamp value; may not be null
* @param zoneId the timezone identifier or offset where the timestamp is defined
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the ISO 8601 formatted string
*/
public static String toIsoString(java.sql.Timestamp timestamp, ZoneId zoneId) {
public static String toIsoString(java.sql.Timestamp timestamp, ZoneId zoneId, TemporalAdjuster adjuster) {
ZonedDateTime zdt = timestamp.toInstant().atZone(zoneId);
if (adjuster != null) {
zdt = zdt.with(adjuster);
}
return zdt.format(FORMATTER);
}
@ -142,10 +162,15 @@ public static String toIsoString(java.sql.Timestamp timestamp, ZoneId zoneId) {
*
* @param date the date value; may not be null
* @param zoneId the timezone identifier or offset where the date is defined
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the ISO 8601 formatted string
*/
public static String toIsoString(java.sql.Date date, ZoneId zoneId) {
public static String toIsoString(java.sql.Date date, ZoneId zoneId, TemporalAdjuster adjuster) {
LocalDate localDate = date.toLocalDate();
if (adjuster != null) {
localDate = localDate.with(adjuster);
}
ZonedDateTime zdt = ZonedDateTime.of(localDate, LocalTime.MIDNIGHT, zoneId);
return zdt.format(FORMATTER);
}
@ -156,10 +181,15 @@ public static String toIsoString(java.sql.Date date, ZoneId zoneId) {
*
* @param time the JDBC time value; may not be null
* @param zoneId the timezone identifier or offset where the time is defined
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the ISO 8601 formatted string
*/
public static String toIsoString(java.sql.Time time, ZoneId zoneId) {
public static String toIsoString(java.sql.Time time, ZoneId zoneId, TemporalAdjuster adjuster) {
LocalTime localTime = time.toLocalTime();
if (adjuster != null) {
localTime = localTime.with(adjuster);
}
ZonedDateTime zdt = ZonedDateTime.of(Conversions.EPOCH, localTime, zoneId);
return zdt.format(FORMATTER);
}

View File

@ -5,6 +5,7 @@
*/
package io.debezium.time;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.OffsetDateTime;
@ -12,6 +13,7 @@
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjuster;
import org.apache.kafka.connect.data.Schema;
import org.apache.kafka.connect.data.SchemaBuilder;
@ -70,21 +72,23 @@ public static Schema schema() {
* @param value the local or SQL date, time, or timestamp value; may not be null
* @param defaultZone the time zone that should be used by default if the value does not have timezone information; may not be
* null
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the microseconds past midnight
* @throws IllegalArgumentException if the value is not an instance of the acceptable types
*/
public static String toIsoString(Object value, ZoneId defaultZone) {
public static String toIsoString(Object value, ZoneId defaultZone, TemporalAdjuster adjuster) {
if (value instanceof OffsetDateTime) {
return toIsoString((OffsetDateTime) value);
return toIsoString((OffsetDateTime) value, adjuster);
}
if (value instanceof ZonedDateTime) {
return toIsoString((ZonedDateTime) value);
return toIsoString((ZonedDateTime) value, adjuster);
}
if (value instanceof OffsetTime) {
return toIsoString((OffsetTime) value);
return toIsoString((OffsetTime) value, adjuster);
}
if (value instanceof java.util.Date) { // or JDBC subtypes
return toIsoString((java.util.Date) value, defaultZone);
return toIsoString((java.util.Date) value, defaultZone, adjuster);
}
throw new IllegalArgumentException(
"Unable to convert to OffsetDateTime from unexpected value '" + value + "' of type " + value.getClass().getName());
@ -94,9 +98,14 @@ public static String toIsoString(Object value, ZoneId defaultZone) {
* Get the ISO 8601 formatted representation of the given {@link OffsetDateTime}.
*
* @param timestamp the timestamp value
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the ISO 8601 formatted string
*/
public static String toIsoString(OffsetDateTime timestamp) {
public static String toIsoString(OffsetDateTime timestamp, TemporalAdjuster adjuster) {
if (adjuster != null) {
timestamp = timestamp.with(adjuster);
}
return timestamp.format(FORMATTER);
}
@ -104,9 +113,14 @@ public static String toIsoString(OffsetDateTime timestamp) {
* Get the ISO 8601 formatted representation of the given {@link ZonedDateTime}.
*
* @param timestamp the timestamp value
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the ISO 8601 formatted string
*/
public static String toIsoString(ZonedDateTime timestamp) {
public static String toIsoString(ZonedDateTime timestamp, TemporalAdjuster adjuster) {
if (adjuster != null) {
timestamp = timestamp.with(adjuster);
}
return timestamp.format(FORMATTER);
}
@ -114,9 +128,14 @@ public static String toIsoString(ZonedDateTime timestamp) {
* Get the ISO 8601 formatted representation of the given {@link OffsetTime}.
*
* @param timestamp the timestamp value
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the ISO 8601 formatted string
*/
public static String toIsoString(OffsetTime timestamp) {
public static String toIsoString(OffsetTime timestamp, TemporalAdjuster adjuster) {
if (adjuster != null) {
timestamp = timestamp.with(adjuster);
}
return timestamp.format(FORMATTER);
}
@ -126,17 +145,19 @@ public static String toIsoString(OffsetTime timestamp) {
*
* @param timestamp the timestamp value
* @param zoneId the timezone identifier or offset where the timestamp is defined
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the ISO 8601 formatted string
*/
public static String toIsoString(java.util.Date timestamp, ZoneId zoneId) {
public static String toIsoString(java.util.Date timestamp, ZoneId zoneId, TemporalAdjuster adjuster) {
if (timestamp instanceof java.sql.Timestamp) {
return toIsoString((java.sql.Timestamp) timestamp, zoneId);
return toIsoString((java.sql.Timestamp) timestamp, zoneId, adjuster);
}
if (timestamp instanceof java.sql.Date) {
return toIsoString((java.sql.Date) timestamp, zoneId);
return toIsoString((java.sql.Date) timestamp, zoneId, adjuster);
}
if (timestamp instanceof java.sql.Time) {
return toIsoString((java.sql.Time) timestamp, zoneId);
return toIsoString((java.sql.Time) timestamp, zoneId, adjuster);
}
return timestamp.toInstant().atZone(zoneId).format(FORMATTER);
}
@ -147,10 +168,16 @@ public static String toIsoString(java.util.Date timestamp, ZoneId zoneId) {
*
* @param timestamp the JDBC timestamp value
* @param zoneId the timezone identifier or offset where the timestamp is defined
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the ISO 8601 formatted string
*/
public static String toIsoString(java.sql.Timestamp timestamp, ZoneId zoneId) {
ZonedDateTime zdt = timestamp.toInstant().atZone(zoneId);
public static String toIsoString(java.sql.Timestamp timestamp, ZoneId zoneId, TemporalAdjuster adjuster) {
Instant instant = timestamp.toInstant();
if (adjuster != null) {
instant = instant.with(adjuster);
}
ZonedDateTime zdt = instant.atZone(zoneId);
return zdt.format(FORMATTER);
}
@ -160,10 +187,15 @@ public static String toIsoString(java.sql.Timestamp timestamp, ZoneId zoneId) {
*
* @param date the date value
* @param zoneId the timezone identifier or offset where the date is defined
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the ISO 8601 formatted string
*/
public static String toIsoString(java.sql.Date date, ZoneId zoneId) {
public static String toIsoString(java.sql.Date date, ZoneId zoneId, TemporalAdjuster adjuster) {
LocalDate localDate = date.toLocalDate();
if (adjuster != null) {
localDate = localDate.with(adjuster);
}
ZonedDateTime zdt = ZonedDateTime.of(localDate, LocalTime.MIDNIGHT, zoneId);
return zdt.format(FORMATTER);
}
@ -174,10 +206,15 @@ public static String toIsoString(java.sql.Date date, ZoneId zoneId) {
*
* @param time the JDBC time value
* @param zoneId the timezone identifier or offset where the time is defined
* @param adjuster the optional component that adjusts the local date value before obtaining the epoch day; may be null if no
* adjustment is necessary
* @return the ISO 8601 formatted string
*/
public static String toIsoString(java.sql.Time time, ZoneId zoneId) {
public static String toIsoString(java.sql.Time time, ZoneId zoneId, TemporalAdjuster adjuster) {
LocalTime localTime = time.toLocalTime();
if (adjuster != null) {
localTime = localTime.with(adjuster);
}
ZonedDateTime zdt = ZonedDateTime.of(Conversions.EPOCH, localTime, zoneId);
return zdt.format(FORMATTER);
}