Unverified 提交 bef4c05e authored 作者: Noel Grandin's avatar Noel Grandin 提交者: GitHub

Merge pull request #890 from katzyn/datetime

Implement conversions for TIMESTAMP WITH TIME ZONE
......@@ -510,6 +510,41 @@ public class DateTimeUtils {
return ValueTimestamp.fromDateValueAndNanos(dateValue, nanos);
}
/**
* Calculates the time zone offset in minutes for the specified time zone, date
* value, and nanoseconds since midnight.
*
* @param tz
* time zone, or {@code null} for default
* @param dateValue
* date value
* @param timeNanos
* nanoseconds since midnight
* @return time zone offset in milliseconds
*/
public static int getTimeZoneOffsetMillis(TimeZone tz, long dateValue, long timeNanos) {
long msec = timeNanos / 1_000_000;
long utc = convertDateTimeValueToMillis(tz, dateValue, msec);
long local = absoluteDayFromDateValue(dateValue) * MILLIS_PER_DAY + msec;
return (int) (local - utc);
}
/**
* Calculates the milliseconds since epoch for the specified date value,
* nanoseconds since midnight, and time zone offset.
* @param dateValue
* date value
* @param timeNanos
* nanoseconds since midnight
* @param offsetMins
* time zone offset in minutes
* @return milliseconds since epoch in UTC
*/
public static long getMillis(long dateValue, long timeNanos, short offsetMins) {
return absoluteDayFromDateValue(dateValue) * MILLIS_PER_DAY
+ timeNanos / 1_000_000 - offsetMins * 60_000;
}
/**
* Calculate the milliseconds since 1970-01-01 (UTC) for the given date and
* time (in the specified timezone).
......@@ -971,8 +1006,7 @@ public class DateTimeUtils {
* @return the timestamp
*/
public static Timestamp convertTimestampTimeZoneToTimestamp(long dateValue, long timeNanos, short offsetMins) {
Timestamp ts = new Timestamp(absoluteDayFromDateValue(dateValue) * MILLIS_PER_DAY
+ timeNanos / 1_000_000 - offsetMins * 60_000);
Timestamp ts = new Timestamp(getMillis(dateValue, timeNanos, offsetMins));
ts.setNanos((int) (timeNanos % 1_000_000_000));
return ts;
}
......@@ -1157,6 +1191,34 @@ public class DateTimeUtils {
dateValueFromAbsoluteDay(absoluteDay), nanos);
}
/**
* Converts local date value and nanoseconds to timestamp with time zone.
*
* @param dateValue
* date value
* @param timeNanos
* nanoseconds since midnight
* @return timestamp with time zone
*/
public static ValueTimestampTimeZone timestampTimeZoneFromLocalDateValueAndNanos(long dateValue, long timeNanos) {
int timeZoneOffset = DateTimeUtils.getTimeZoneOffsetMillis(null, dateValue, timeNanos);
int offsetMins = timeZoneOffset / 60_000;
int correction = timeZoneOffset % 60_000;
if (correction != 0) {
timeNanos -= correction;
if (timeNanos < 0) {
timeNanos += DateTimeUtils.NANOS_PER_DAY;
dateValue = DateTimeUtils
.dateValueFromAbsoluteDay(DateTimeUtils.absoluteDayFromDateValue(dateValue) - 1);
} else if (timeNanos >= DateTimeUtils.NANOS_PER_DAY) {
timeNanos -= DateTimeUtils.NANOS_PER_DAY;
dateValue = DateTimeUtils
.dateValueFromAbsoluteDay(DateTimeUtils.absoluteDayFromDateValue(dateValue) + 1);
}
}
return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, timeNanos, (short) offsetMins);
}
/**
* Calculate the absolute day from an encoded date value.
*
......
......@@ -101,9 +101,13 @@ public class LocalDateTimeUtils {
private static final Method LOCAL_DATE_AT_START_OF_DAY;
/**
* {@code java.sql.Timestamp.from(java.time.Instant)} or {@code null}.
* {@code java.time.Instant#getEpochSecond()} or {@code null}.
*/
private static final Method TIMESTAMP_FROM;
private static final Method INSTANT_GET_EPOCH_SECOND;
/**
* {@code java.time.Instant#getNano()} or {@code null}.
*/
private static final Method INSTANT_GET_NANO;
/**
* {@code java.sql.Timestamp.toInstant()} or {@code null}.
*/
......@@ -186,7 +190,8 @@ public class LocalDateTimeUtils {
LOCAL_DATE_GET_DAY_OF_MONTH = getMethod(LOCAL_DATE, "getDayOfMonth");
LOCAL_DATE_AT_START_OF_DAY = getMethod(LOCAL_DATE, "atStartOfDay");
TIMESTAMP_FROM = getMethod(Timestamp.class, "from", INSTANT);
INSTANT_GET_EPOCH_SECOND = getMethod(INSTANT, "getEpochSecond");
INSTANT_GET_NANO = getMethod(INSTANT, "getNano");
TIMESTAMP_TO_INSTANT = getMethod(Timestamp.class, "toInstant");
LOCAL_TIME_PARSE = getMethod(LOCAL_TIME, "parse", CharSequence.class);
......@@ -214,7 +219,8 @@ public class LocalDateTimeUtils {
LOCAL_DATE_GET_MONTH_VALUE = null;
LOCAL_DATE_GET_DAY_OF_MONTH = null;
LOCAL_DATE_AT_START_OF_DAY = null;
TIMESTAMP_FROM = null;
INSTANT_GET_EPOCH_SECOND = null;
INSTANT_GET_NANO = null;
TIMESTAMP_TO_INSTANT = null;
LOCAL_TIME_PARSE = null;
LOCAL_DATE_TIME_PLUS_NANOS = null;
......@@ -485,7 +491,16 @@ public class LocalDateTimeUtils {
*/
public static Value instantToValue(Object instant) {
try {
return ValueTimestamp.get((Timestamp) TIMESTAMP_FROM.invoke(null, instant));
long epochSecond = (long) INSTANT_GET_EPOCH_SECOND.invoke(instant);
int nano = (int) INSTANT_GET_NANO.invoke(instant);
long absoluteDay = epochSecond / 86_400;
// Round toward negative infinity
if (epochSecond < 0 && (absoluteDay * 86_400 != epochSecond)) {
absoluteDay--;
}
long timeNanos = (epochSecond - absoluteDay * 86_400) * 1_000_000_000 + nano;
return ValueTimestampTimeZone.fromDateValueAndNanos(
DateTimeUtils.dateValueFromAbsoluteDay(absoluteDay), timeNanos, (short) 0);
} catch (IllegalAccessException e) {
throw DbException.convert(e);
} catch (InvocationTargetException e) {
......
......@@ -1020,9 +1020,9 @@ public class DataType {
return Value.DATE;
} else if (LocalDateTimeUtils.LOCAL_TIME == x) {
return Value.TIME;
} else if (LocalDateTimeUtils.LOCAL_DATE_TIME == x || LocalDateTimeUtils.INSTANT == x) {
} else if (LocalDateTimeUtils.LOCAL_DATE_TIME == x) {
return Value.TIMESTAMP;
} else if (LocalDateTimeUtils.OFFSET_DATE_TIME == x) {
} else if (LocalDateTimeUtils.OFFSET_DATE_TIME == x || LocalDateTimeUtils.INSTANT == x) {
return Value.TIMESTAMP_TZ;
} else {
if (JdbcUtils.customDataTypesHandler != null) {
......
......@@ -839,9 +839,12 @@ public abstract class Value {
case TIMESTAMP:
return ValueDate.fromDateValue(
((ValueTimestamp) this).getDateValue());
case TIMESTAMP_TZ:
return ValueDate.fromDateValue(
((ValueTimestampTimeZone) this).getDateValue());
case TIMESTAMP_TZ: {
ValueTimestampTimeZone ts = (ValueTimestampTimeZone) this;
long dateValue = ts.getDateValue(), timeNanos = ts.getTimeNanos();
long millis = DateTimeUtils.getMillis(dateValue, timeNanos, ts.getTimeZoneOffsetMins());
return ValueDate.fromMillis(millis);
}
case ENUM:
throw DbException.get(
ErrorCode.DATA_CONVERSION_ERROR_1, getString());
......@@ -857,9 +860,12 @@ public abstract class Value {
case TIMESTAMP:
return ValueTime.fromNanos(
((ValueTimestamp) this).getTimeNanos());
case TIMESTAMP_TZ:
return ValueTime.fromNanos(
((ValueTimestampTimeZone) this).getTimeNanos());
case TIMESTAMP_TZ: {
ValueTimestampTimeZone ts = (ValueTimestampTimeZone) this;
long dateValue = ts.getDateValue(), timeNanos = ts.getTimeNanos();
long millis = DateTimeUtils.getMillis(dateValue, timeNanos, ts.getTimeZoneOffsetMins());
return ValueTime.fromNanos(DateTimeUtils.nanosFromDate(millis) + timeNanos % 1_000_000);
}
case ENUM:
throw DbException.get(
ErrorCode.DATA_CONVERSION_ERROR_1, getString());
......@@ -874,10 +880,33 @@ public abstract class Value {
case DATE:
return ValueTimestamp.fromDateValueAndNanos(
((ValueDate) this).getDateValue(), 0);
case TIMESTAMP_TZ:
return ValueTimestamp.fromDateValueAndNanos(
((ValueTimestampTimeZone) this).getDateValue(),
((ValueTimestampTimeZone) this).getTimeNanos());
case TIMESTAMP_TZ: {
ValueTimestampTimeZone ts = (ValueTimestampTimeZone) this;
long dateValue = ts.getDateValue(), timeNanos = ts.getTimeNanos();
long millis = DateTimeUtils.getMillis(dateValue, timeNanos, ts.getTimeZoneOffsetMins());
return ValueTimestamp.fromMillisNanos(millis, (int) (timeNanos % 1_000_000));
}
case ENUM:
throw DbException.get(
ErrorCode.DATA_CONVERSION_ERROR_1, getString());
}
break;
}
case TIMESTAMP_TZ: {
switch (getType()) {
case TIME: {
ValueTimestamp ts = DateTimeUtils.normalizeTimestamp(0, ((ValueTime) this).getNanos());
return DateTimeUtils.timestampTimeZoneFromLocalDateValueAndNanos(
ts.getDateValue(), ts.getTimeNanos());
}
case DATE:
return DateTimeUtils.timestampTimeZoneFromLocalDateValueAndNanos(
((ValueDate) this).getDateValue(), 0);
case TIMESTAMP: {
ValueTimestamp ts = (ValueTimestamp) this;
return DateTimeUtils.timestampTimeZoneFromLocalDateValueAndNanos(
ts.getDateValue(), ts.getTimeNanos());
}
case ENUM:
throw DbException.get(
ErrorCode.DATA_CONVERSION_ERROR_1, getString());
......
......@@ -148,7 +148,7 @@ public class ValueTimestampTimeZone extends Value {
@Override
public Timestamp getTimestamp() {
throw new UnsupportedOperationException("unimplemented");
return DateTimeUtils.convertTimestampTimeZoneToTimestamp(dateValue, timeNanos, timeZoneOffsetMins);
}
@Override
......
......@@ -9,6 +9,7 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
......@@ -755,18 +756,26 @@ public class TestPreparedStatement extends TestBase {
if (!LocalDateTimeUtils.isJava8DateApiPresent()) {
return;
}
Method timestampToInstant = Timestamp.class.getMethod("toInstant"),
now = LocalDateTimeUtils.INSTANT.getMethod("now");
Method timestampToInstant = Timestamp.class.getMethod("toInstant");
Method now = LocalDateTimeUtils.INSTANT.getMethod("now");
Method parse = LocalDateTimeUtils.INSTANT.getMethod("parse", CharSequence.class);
PreparedStatement prep = conn.prepareStatement("SELECT ?");
Object instant1 = now.invoke(null);
prep.setObject(1, instant1);
testInstant8Impl(prep, timestampToInstant, now.invoke(null));
testInstant8Impl(prep, timestampToInstant, parse.invoke(null, "2000-01-15T12:13:14.123456789Z"));
testInstant8Impl(prep, timestampToInstant, parse.invoke(null, "1500-09-10T23:22:11.123456789Z"));
}
private void testInstant8Impl(PreparedStatement prep, Method timestampToInstant, Object instant)
throws SQLException, IllegalAccessException, InvocationTargetException {
prep.setObject(1, instant);
ResultSet rs = prep.executeQuery();
rs.next();
Object instant2 = rs.getObject(1, LocalDateTimeUtils.INSTANT);
assertEquals(instant1, instant2);
assertEquals(instant, instant2);
Timestamp ts = rs.getTimestamp(1);
assertEquals(instant1, timestampToInstant.invoke(ts));
assertEquals(instant, timestampToInstant.invoke(ts));
assertFalse(rs.next());
rs.close();
......@@ -774,7 +783,7 @@ public class TestPreparedStatement extends TestBase {
rs = prep.executeQuery();
rs.next();
instant2 = rs.getObject(1, LocalDateTimeUtils.INSTANT);
assertEquals(instant1, instant2);
assertEquals(instant, instant2);
assertFalse(rs.next());
rs.close();
}
......
......@@ -11,11 +11,16 @@ import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.TimeZone;
import org.h2.api.TimestampWithTimeZone;
import org.h2.test.TestBase;
import org.h2.util.DateTimeUtils;
import org.h2.util.LocalDateTimeUtils;
import org.h2.value.Value;
import org.h2.value.ValueDate;
import org.h2.value.ValueTime;
import org.h2.value.ValueTimestamp;
import org.h2.value.ValueTimestampTimeZone;
/**
......@@ -40,6 +45,7 @@ public class TestTimeStampWithTimeZone extends TestBase {
test4();
test5();
testOrder();
testConversions();
deleteDb(getTestName());
}
......@@ -186,4 +192,39 @@ public class TestTimeStampWithTimeZone extends TestBase {
conn.close();
}
private void testConversionsImpl(String timeStr, boolean testReverse) {
ValueTimestamp ts = ValueTimestamp.parse(timeStr);
ValueDate d = (ValueDate) ts.convertTo(Value.DATE);
ValueTime t = (ValueTime) ts.convertTo(Value.TIME);
ValueTimestampTimeZone tstz = ValueTimestampTimeZone.parse(timeStr);
assertEquals(ts, tstz.convertTo(Value.TIMESTAMP));
assertEquals(d, tstz.convertTo(Value.DATE));
assertEquals(t, tstz.convertTo(Value.TIME));
assertEquals(ts.getTimestamp(), tstz.getTimestamp());
if (testReverse) {
assertEquals(0, tstz.compareTo(ts.convertTo(Value.TIMESTAMP_TZ), null));
assertEquals(d.convertTo(Value.TIMESTAMP).convertTo(Value.TIMESTAMP_TZ),
d.convertTo(Value.TIMESTAMP_TZ));
assertEquals(t.convertTo(Value.TIMESTAMP).convertTo(Value.TIMESTAMP_TZ),
t.convertTo(Value.TIMESTAMP_TZ));
}
}
private void testConversions() {
TimeZone current = TimeZone.getDefault();
try {
for (String id : TimeZone.getAvailableIDs()) {
TimeZone.setDefault(TimeZone.getTimeZone(id));
DateTimeUtils.resetCalendar();
testConversionsImpl("2017-12-05 23:59:30.987654321-12:00", true);
testConversionsImpl("2000-01-02 10:20:30.123456789+07:30", true);
boolean testReverse = !"Africa/Monrovia".equals(id);
testConversionsImpl("1960-04-06 12:13:14.777666555+12:00", testReverse);
}
} finally {
TimeZone.setDefault(current);
DateTimeUtils.resetCalendar();
}
}
}
......@@ -765,4 +765,4 @@ jacoco xdata invokes sourcefiles classfiles duplication crypto stacktraces prt d
interpolated thead
die weekdiff osx subprocess dow proleptic microsecond microseconds divisible cmp denormalized suppressed saturated mcs
london dfs weekdays intermittent looked
london dfs weekdays intermittent looked msec tstz africa monrovia
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论