提交 f4507bb3 authored 作者: Philippe Marschall's avatar Philippe Marschall 提交者: Sergi Vladykin

Add JSR-310 Support (#386)

Add support for Java 8 Date and Time API AKA JSR-310. On the face of it
the existing JDBC types java.sql.Date, java.sql.Time and
java.sql.Timestamp offer conversion methods to and from JSR-310 data
types. Unfortunately java.sql.Timestamp does silent data truncation
when the timestamp falls into a DST transition on the JVM time zone.
Worse still for java.time.OffsetDateTime there is no corresponding JDBC
type. Therefore these conversions have to be implemented. All of this
has to be programmed using reflection so the code compiles and can be
executed on Java 7.
上级 0cd81ddb
...@@ -38,6 +38,7 @@ import org.h2.result.ResultInterface; ...@@ -38,6 +38,7 @@ import org.h2.result.ResultInterface;
import org.h2.result.UpdatableRow; import org.h2.result.UpdatableRow;
import org.h2.util.DateTimeUtils; import org.h2.util.DateTimeUtils;
import org.h2.util.IOUtils; import org.h2.util.IOUtils;
import org.h2.util.LocalDateTimeUtils;
import org.h2.util.New; import org.h2.util.New;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
import org.h2.value.CompareMode; import org.h2.value.CompareMode;
...@@ -57,6 +58,7 @@ import org.h2.value.ValueShort; ...@@ -57,6 +58,7 @@ import org.h2.value.ValueShort;
import org.h2.value.ValueString; import org.h2.value.ValueString;
import org.h2.value.ValueTime; import org.h2.value.ValueTime;
import org.h2.value.ValueTimestamp; import org.h2.value.ValueTimestamp;
import org.h2.value.ValueTimestampTimeZone;
import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.Geometry;
...@@ -77,6 +79,7 @@ import com.vividsolutions.jts.geom.Geometry; ...@@ -77,6 +79,7 @@ import com.vividsolutions.jts.geom.Geometry;
* </p> * </p>
*/ */
public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultSetBackwardsCompat { public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultSetBackwardsCompat {
private final boolean closeStatement; private final boolean closeStatement;
private final boolean scrollable; private final boolean scrollable;
private final boolean updatable; private final boolean updatable;
...@@ -3779,6 +3782,16 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS ...@@ -3779,6 +3782,16 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
return type.cast(value.getObject()); return type.cast(value.getObject());
} else if (type.isAssignableFrom(Geometry.class)) { } else if (type.isAssignableFrom(Geometry.class)) {
return type.cast(value.getObject()); return type.cast(value.getObject());
} else if (LocalDateTimeUtils.isLocalDate(type)) {
return type.cast(LocalDateTimeUtils.valueToLocalDate(value));
} else if (LocalDateTimeUtils.isLocalTime(type)) {
return type.cast(LocalDateTimeUtils.valueToLocalTime(value));
} else if (LocalDateTimeUtils.isLocalDateTime(type)) {
return type.cast(LocalDateTimeUtils.valueToLocalDateTime(
(ValueTimestamp) value));
} else if (LocalDateTimeUtils.isOffsetDateTime(type) && value instanceof ValueTimestampTimeZone) {
return type.cast(LocalDateTimeUtils.valueToOffsetDateTime(
(ValueTimestampTimeZone) value));
} else { } else {
throw unsupported(type.getClass().getName()); throw unsupported(type.getClass().getName());
} }
......
/*
* Copyright 2016 H2 Group. Multiple-Licensed under the MPL 2.0, and the
* EPL 1.0 (http://h2database.com/html/license.html). Initial Developer: H2
* Group Iso8601: Initial Developer: Philippe Marschall (firstName dot lastName
* at gmail dot com)
*/
package org.h2.util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Date;
import java.sql.SQLException;
import java.sql.Time;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.h2.api.TimestampWithTimeZone;
import org.h2.message.DbException;
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;
/**
* This utility class contains time conversion functions for Java 8
* Date and Time API classes.
*
* <p>This class is implemented using reflection so that it compiles on
* Java 7 as well.</p>
*
* <p>For LocalDate and LocalTime the conversion methods provided by
* the JDK are used. For OffsetDateTime a custom conversion method is
* used because it has no equivalent in JDBC. For LocalDateTime a
* custom conversion method is used instead of the one provided by the
* JDK as well.</p>
*
* <p>Using the JDK provided conversion method for LocalDateTime would
* introduces some errors in edge cases. Consider the following case:
* at 2016-03-27 02:00 in Europe/Berlin the clocks were set to
* 2016-03-27 03:00. This means that 2016-03-27 02:15 does not exist in
* Europe/Berlin. Unfortunately java.sql.Timestamp is in the the time
* zone of the JVM. That means if you run a JVM with the time zone
* Europe/Berlin then the SQL value 'TIMESTAMP 2016-03-27 02:15:00' can
* not be represented. java.time.LocalDateTime does not have these
* limitations but if we convert through java.sql.Timestamp we inherit
* its limitations. Therefore that conversion must be avoided.</p>
*
* <p>Once the driver requires Java 8 all the reflection can be removed.</p>
*/
public class LocalDateTimeUtils {
// Class<java.time.LocalDate>
private static Class<?> LOCAL_DATE;
// Class<java.time.LocalTime>
private static Class<?> LOCAL_TIME;
// Class<java.time.LocalDateTime>
private static Class<?> LOCAL_DATE_TIME;
// Class<java.time.OffsetDateTime>
private static Class<?> OFFSET_DATE_TIME;
// Class<java.time.ZoneOffset>
private static Class<?> ZONE_OFFSET;
// java.sql.Date#toLocalDate()
private static Method TO_LOCAL_DATE;
// java.sql.Time#toLocalTime()
private static Method TO_LOCAL_TIME;
// java.sql.Date#valueOf(LocalDate)
private static Method DATE_VALUE_OF;
// java.sql.Time#valueOf(LocalTime)
private static Method TIME_VALUE_OF;
// java.time.LocalDate#of(int, int, int)
private static Method LOCAL_DATE_OF_YEAR_MONTH_DAY;
// java.time.LocalDate#parse(CharSequence)
private static Method LOCAL_DATE_PARSE;
// java.time.LocalDate#getYear()
private static Method LOCAL_DATE_GET_YEAR;
// java.time.LocalDate#getMonthValue()
private static Method LOCAL_DATE_GET_MONTH_VALUE;
// java.time.LocalDate#getDayOfMonth()
private static Method LOCAL_DATE_GET_DAY_OF_MONTH;
// java.time.LocalDate#atStartOfDay()
private static Method LOCAL_DATE_AT_START_OF_DAY;
// java.time.LocalTime#parse(CharSequence)
private static Method LOCAL_TIME_PARSE;
// java.time.LocalDateTime#plusNanos(long)
private static Method LOCAL_DATE_TIME_PLUS_NANOS;
// java.time.LocalDateTime#toLocalDate()
private static Method LOCAL_DATE_TIME_TO_LOCAL_DATE;
// java.time.LocalDateTime#truncatedTo(TemporalUnit)
private static Method LOCAL_DATE_TIME_TRUNCATED_TO;
// java.time.LocalDateTime#parse(CharSequence)
private static Method LOCAL_DATE_TIME_PARSE;
// java.time.ZoneOffset#ofTotalSeconds(int)
private static Method ZONE_OFFSET_OF_TOTAL_SECONDS;
// java.time.OffsetDateTime#of(LocalDateTime, ZoneOffset)
private static Method OFFSET_DATE_TIME_OF_LOCAL_DATE_TIME_ZONE_OFFSET;
// java.time.OffsetDateTime#parse(CharSequence)
private static Method OFFSET_DATE_TIME_PARSE;
// java.time.OffsetDateTime#toLocalDateTime()
private static Method OFFSET_DATE_TIME_TO_LOCAL_DATE_TIME;
// java.time.OffsetDateTime#getOffset()
private static Method OFFSET_DATE_TIME_GET_OFFSET;
// java.time.ZoneOffset#getTotalSeconds()
private static Method ZONE_OFFSET_GET_TOTAL_SECONDS;
// java.time.Duration#between(Temporal, Temporal)
private static Method DURATION_BETWEEN;
// java.time.Duration#toNanos()
private static Method DURATION_TO_NANOS;
// java.time.temporal.ChronoUnit#DAYS
private static Object CHRONO_UNIT_DAYS;
private static final boolean IS_JAVA8_DATE_API_PRESENT;
static {
boolean isJava8DateApiPresent;
try {
LOCAL_DATE = Class.forName("java.time.LocalDate");
LOCAL_TIME = Class.forName("java.time.LocalTime");
LOCAL_DATE_TIME = Class.forName("java.time.LocalDateTime");
OFFSET_DATE_TIME = Class.forName("java.time.OffsetDateTime");
ZONE_OFFSET = Class.forName("java.time.ZoneOffset");
isJava8DateApiPresent = true;
} catch (ClassNotFoundException e) {
// older than Java 8
isJava8DateApiPresent = false;
}
IS_JAVA8_DATE_API_PRESENT = isJava8DateApiPresent;
if (IS_JAVA8_DATE_API_PRESENT) {
Class<?> temporalUnit = getClass("java.time.temporal.TemporalUnit");
Class<?> chronoUnit = getClass("java.time.temporal.ChronoUnit");
Class<?> duration = getClass("java.time.Duration");
Class<?> temporal = getClass("java.time.temporal.Temporal");
TO_LOCAL_DATE = getMethod(java.sql.Date.class, "toLocalDate");
TO_LOCAL_TIME = getMethod(java.sql.Time.class, "toLocalTime");
DATE_VALUE_OF = getMethod(java.sql.Date.class, "valueOf", LOCAL_DATE);
TIME_VALUE_OF = getMethod(java.sql.Time.class, "valueOf", LOCAL_TIME);
LOCAL_DATE_OF_YEAR_MONTH_DAY = getMethod(LOCAL_DATE, "of", int.class, int.class, int.class);
LOCAL_DATE_PARSE = getMethod(LOCAL_DATE, "parse", CharSequence.class);
LOCAL_DATE_GET_YEAR = getMethod(LOCAL_DATE, "getYear");
LOCAL_DATE_GET_MONTH_VALUE = getMethod(LOCAL_DATE, "getMonthValue");
LOCAL_DATE_GET_DAY_OF_MONTH = getMethod(LOCAL_DATE, "getDayOfMonth");
LOCAL_DATE_AT_START_OF_DAY = getMethod(LOCAL_DATE, "atStartOfDay");
LOCAL_TIME_PARSE = getMethod(LOCAL_TIME, "parse", CharSequence.class);
LOCAL_DATE_TIME_PLUS_NANOS = getMethod(LOCAL_DATE_TIME, "plusNanos", long.class);
LOCAL_DATE_TIME_TO_LOCAL_DATE = getMethod(LOCAL_DATE_TIME, "toLocalDate");
LOCAL_DATE_TIME_TRUNCATED_TO = getMethod(LOCAL_DATE_TIME, "truncatedTo", temporalUnit);
LOCAL_DATE_TIME_PARSE = getMethod(LOCAL_DATE_TIME, "parse", CharSequence.class);
ZONE_OFFSET_OF_TOTAL_SECONDS = getMethod(ZONE_OFFSET, "ofTotalSeconds", int.class);
OFFSET_DATE_TIME_TO_LOCAL_DATE_TIME = getMethod(OFFSET_DATE_TIME, "toLocalDateTime");
OFFSET_DATE_TIME_GET_OFFSET = getMethod(OFFSET_DATE_TIME, "getOffset");
OFFSET_DATE_TIME_OF_LOCAL_DATE_TIME_ZONE_OFFSET = getMethod(OFFSET_DATE_TIME, "of", LOCAL_DATE_TIME, ZONE_OFFSET);
OFFSET_DATE_TIME_PARSE = getMethod(OFFSET_DATE_TIME, "parse", CharSequence.class);
ZONE_OFFSET_GET_TOTAL_SECONDS = getMethod(ZONE_OFFSET, "getTotalSeconds");
DURATION_BETWEEN = getMethod(duration, "between", temporal, temporal);
DURATION_TO_NANOS = getMethod(duration, "toNanos");
CHRONO_UNIT_DAYS = getFieldValue(chronoUnit, "DAYS");
}
}
private LocalDateTimeUtils() {
// utility class
}
/**
* Checks if the Java 8 Date and Time API is present.
*
* <p>This is the case on Java 8 and later and not the case on
* Java 7. Versions older than Java 7 are not supported.</p>
*
* @return if the Java 8 Date and Time API is present
*/
public static boolean isJava8DateApiPresent() {
return IS_JAVA8_DATE_API_PRESENT;
}
/**
* Returns the class java.time.LocalDate.
*
* @return the class java.time.LocalDate, null on Java 7
*/
public static Class<?> getLocalDateClass() {
return LOCAL_DATE;
}
/**
* Returns the class java.time.LocalTime.
*
* @return the class java.time.LocalTime, null on Java 7
*/
public static Class<?> getLocalTimeClass() {
return LOCAL_TIME;
}
/**
* Returns the class java.time.LocalDateTime.
*
* @return the class java.time.LocalDateTime, null on Java 7
*/
public static Class<?> getLocalDateTimeClass() {
return LOCAL_DATE_TIME;
}
/**
* Returns the class java.time.OffsetDateTime.
*
* @return the class java.time.OffsetDateTime, null on Java 7
*/
public static Class<?> getOffsetDateTimeClass() {
return OFFSET_DATE_TIME;
}
/**
* Parses an ISO date string into a java.time.LocalDate.
*
* @param text the ISO date string
* @return the java.time.LocalDate instance
*/
public static Object parseLocalDate(CharSequence text) {
try {
return LOCAL_DATE_PARSE.invoke(null, text);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new IllegalArgumentException("error when parsing text '" + text + "'", e);
}
}
/**
* Parses an ISO time string into a java.time.LocalTime.
*
* @param text the ISO time string
* @return the java.time.LocalTime instance
*/
public static Object parseLocalTime(CharSequence text) {
try {
return LOCAL_TIME_PARSE.invoke(null, text);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new IllegalArgumentException("error when parsing text '" + text + "'", e);
}
}
/**
* Parses an ISO date string into a java.time.LocalDateTime.
*
* @param text the ISO date string
* @return the java.time.LocalDateTime instance
*/
public static Object parseLocalDateTime(CharSequence text) {
try {
return LOCAL_DATE_TIME_PARSE.invoke(null, text);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new IllegalArgumentException("error when parsing text '" + text + "'", e);
}
}
/**
* Parses an ISO date string into a java.time.OffsetDateTime.
*
* @param text the ISO date string
* @return the java.time.OffsetDateTime instance
*/
public static Object parseOffsetDateTime(CharSequence text) {
try {
return OFFSET_DATE_TIME_PARSE.invoke(null, text);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new IllegalArgumentException("error when parsing text '" + text + "'", e);
}
}
private static Class<?> getClass(String className) {
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Java 8 or later but class " + className + " is missing", e);
}
}
private static Method getMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
try {
return clazz.getMethod(methodName, parameterTypes);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Java 8 or later but method "
+ clazz.getName() + "#" + methodName + "(" + Arrays.toString(parameterTypes) + ") is missing", e);
}
}
private static Object getFieldValue(Class<?> clazz, String fieldName) {
try {
return clazz.getField(fieldName).get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new IllegalStateException("Java 8 or later but field " + clazz.getName()
+ "#" + fieldName + " is missing", e);
}
}
/**
* Checks if the given class is LocalDate.
*
* <p>This method can be called from Java 7.</p>
*
* @param clazz the class to check
* @return if the class is LocalDate
*/
public static boolean isLocalDate(Class<?> clazz) {
return LOCAL_DATE == clazz;
}
/**
* Checks if the given class is LocalTime.
*
* <p>This method can be called from Java 7.</p>
*
* @param clazz the class to check
* @return if the class is LocalTime
*/
public static boolean isLocalTime(Class<?> clazz) {
return LOCAL_TIME == clazz;
}
/**
* Checks if the given class is LocalDateTime.
*
* <p>This method can be called from Java 7.</p>
*
* @param clazz the class to check
* @return if the class is LocalDateTime
*/
public static boolean isLocalDateTime(Class<?> clazz) {
return LOCAL_DATE_TIME == clazz;
}
/**
* Checks if the given class is OffsetDateTime.
*
* <p>This method can be called from Java 7.</p>
*
* @param clazz the class to check
* @return if the class is OffsetDateTime
*/
public static boolean isOffsetDateTime(Class<?> clazz) {
return OFFSET_DATE_TIME == clazz;
}
/**
* Converts a value to a LocalDate.
*
* <p>This method should only called from Java 8 or later.</p>
*
* @param value the value to convert
* @return the LocalDate
*/
public static Object valueToLocalDate(Value value)
throws SQLException {
return dateToLocalDate(value.getDate());
}
/**
* Converts a value to a LocalTime.
*
* <p>This method should only called from Java 8 or later.</p>
*
* @param value the value to convert
* @return the LocalTime
*/
public static Object valueToLocalTime(Value value)
throws SQLException {
return timeToLocalTime(value.getTime());
}
private static Object dateToLocalDate(Date date)
throws SQLException {
try {
return TO_LOCAL_DATE.invoke(date);
} catch (IllegalAccessException e) {
throw DbException.convert(e);
} catch (InvocationTargetException e) {
throw DbException.convertInvocation(e, "date conversion failed");
}
}
private static Object timeToLocalTime(Time time)
throws SQLException {
try {
return TO_LOCAL_TIME.invoke(time);
} catch (IllegalAccessException e) {
throw DbException.convert(e);
} catch (InvocationTargetException e) {
throw DbException.convertInvocation(e, "time conversion failed");
}
}
/**
* Converts a value to a LocalDateTime.
*
* <p>This method should only called from Java 8 or later.</p>
*
* @param value the value to convert
* @return the LocalDateTime
*/
public static Object valueToLocalDateTime(ValueTimestamp value)
throws SQLException {
long dateValue = value.getDateValue();
long timeNanos = value.getTimeNanos();
try {
Object localDate = localDateFromDateValue(dateValue);
Object localDateTime = LOCAL_DATE_AT_START_OF_DAY.invoke(localDate);
return LOCAL_DATE_TIME_PLUS_NANOS.invoke(localDateTime, timeNanos);
} catch (IllegalAccessException e) {
throw DbException.convert(e);
} catch (InvocationTargetException e) {
throw DbException.convertInvocation(e, "timestamp conversion failed");
}
}
/**
* Converts a value to a OffsetDateTime.
*
* <p>This method should only called from Java 8 or later.</p>
*
* @param value the value to convert
* @return the OffsetDateTime
*/
public static Object valueToOffsetDateTime(ValueTimestampTimeZone value)
throws SQLException {
return timestampWithTimeZoneToOffsetDateTime((TimestampWithTimeZone) value.getObject());
}
private static Object timestampWithTimeZoneToOffsetDateTime(TimestampWithTimeZone timestampWithTimeZone)
throws SQLException {
long dateValue = timestampWithTimeZone.getYMD();
long timeNanos = timestampWithTimeZone.getNanosSinceMidnight();
try {
Object localDateTime = localDateTimeFromDateNanos(dateValue, timeNanos);
short timeZoneOffsetMins = timestampWithTimeZone.getTimeZoneOffsetMins();
int offsetSeconds = (int) TimeUnit.MINUTES.toSeconds(timeZoneOffsetMins);
Object offset = ZONE_OFFSET_OF_TOTAL_SECONDS.invoke(null, offsetSeconds);
return OFFSET_DATE_TIME_OF_LOCAL_DATE_TIME_ZONE_OFFSET.invoke(null, localDateTime, offset);
} catch (IllegalAccessException e) {
throw DbException.convert(e);
} catch (InvocationTargetException e) {
throw DbException.convertInvocation(e, "timestamp with time zone conversion failed");
}
}
/**
* Converts a LocalDate to a Value.
*
* @param localDate the LocalDate to convert, not {@code null}
* @return the value
*/
public static Value localDateToDateValue(Object localDate) {
try {
Date date = (Date) DATE_VALUE_OF.invoke(null, localDate);
return ValueDate.get(date);
} catch (IllegalAccessException e) {
throw DbException.convert(e);
} catch (InvocationTargetException e) {
throw DbException.convertInvocation(e, "date conversion failed");
}
}
/**
* Converts a LocalTime to a Value.
*
* @param localTime the LocalTime to convert, not {@code null}
* @return the value
*/
public static Value localTimeToTimeValue(Object localTime) {
try {
Time time = (Time) TIME_VALUE_OF.invoke(null, localTime);
return ValueTime.get(time);
} catch (IllegalAccessException e) {
throw DbException.convert(e);
} catch (InvocationTargetException e) {
throw DbException.convertInvocation(e, "time conversion failed");
}
}
/**
* Converts a LocalDateTime to a Value.
*
* @param localDateTime the LocalDateTime to convert, not {@code null}
* @return the value
*/
public static Value localDateTimeToValue(Object localDateTime) {
try {
Object localDate = LOCAL_DATE_TIME_TO_LOCAL_DATE.invoke(localDateTime);
long dateValue = dateValueFromLocalDate(localDate);
long timeNanos = timeNanosFromLocalDate(localDateTime);
return ValueTimestamp.fromDateValueAndNanos(dateValue, timeNanos);
} catch (IllegalAccessException e) {
throw DbException.convert(e);
} catch (InvocationTargetException e) {
throw DbException.convertInvocation(e, "local date time conversion failed");
}
}
/**
* Converts a OffsetDateTime to a Value.
*
* @param offsetDateTime the OffsetDateTime to convert, not {@code null}
* @return the value
*/
public static Value offsetDateTimeToValue(Object offsetDateTime) {
try {
Object localDateTime = OFFSET_DATE_TIME_TO_LOCAL_DATE_TIME.invoke(offsetDateTime);
Object localDate = LOCAL_DATE_TIME_TO_LOCAL_DATE.invoke(localDateTime);;
Object zoneOffset = OFFSET_DATE_TIME_GET_OFFSET.invoke(offsetDateTime);
long dateValue = dateValueFromLocalDate(localDate);
long timeNanos = timeNanosFromLocalDate(localDateTime);
short timeZoneOffsetMins = zoneOffsetToOffsetMinute(zoneOffset);
return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, timeNanos, timeZoneOffsetMins);
} catch (IllegalAccessException e) {
throw DbException.convert(e);
} catch (InvocationTargetException e) {
throw DbException.convertInvocation(e, "time conversion failed");
}
}
private static long dateValueFromLocalDate(Object localDate)
throws IllegalAccessException, InvocationTargetException {
int year = (Integer) LOCAL_DATE_GET_YEAR.invoke(localDate);
int month = (Integer) LOCAL_DATE_GET_MONTH_VALUE.invoke(localDate);
int day = (Integer) LOCAL_DATE_GET_DAY_OF_MONTH.invoke(localDate);
return DateTimeUtils.dateValue(year, month, day);
}
private static long timeNanosFromLocalDate(Object localDateTime)
throws IllegalAccessException, InvocationTargetException {
Object midnight = LOCAL_DATE_TIME_TRUNCATED_TO.invoke(localDateTime, CHRONO_UNIT_DAYS);
Object duration = DURATION_BETWEEN.invoke(null, midnight, localDateTime);
return (Long) DURATION_TO_NANOS.invoke(duration);
}
private static short zoneOffsetToOffsetMinute(Object zoneOffset)
throws IllegalAccessException, InvocationTargetException {
int totalSeconds = (Integer) ZONE_OFFSET_GET_TOTAL_SECONDS.invoke(zoneOffset);
return (short) TimeUnit.SECONDS.toMinutes(totalSeconds);
}
private static Object localDateFromDateValue(long dateValue)
throws IllegalAccessException, InvocationTargetException {
int year = DateTimeUtils.yearFromDateValue(dateValue);
int month = DateTimeUtils.monthFromDateValue(dateValue);
int day = DateTimeUtils.dayFromDateValue(dateValue);
return LOCAL_DATE_OF_YEAR_MONTH_DAY.invoke(null, year, month, day);
}
private static Object localDateTimeFromDateNanos(long dateValue, long timeNanos)
throws IllegalAccessException, InvocationTargetException {
Object localDate = localDateFromDateValue(dateValue);
Object localDateTime = LOCAL_DATE_AT_START_OF_DAY.invoke(localDate);
return LOCAL_DATE_TIME_PLUS_NANOS.invoke(localDateTime, timeNanos);
}
}
...@@ -23,6 +23,7 @@ import java.sql.Types; ...@@ -23,6 +23,7 @@ import java.sql.Types;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.UUID; import java.util.UUID;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.api.TimestampWithTimeZone; import org.h2.api.TimestampWithTimeZone;
import org.h2.engine.Constants; import org.h2.engine.Constants;
...@@ -34,6 +35,7 @@ import org.h2.jdbc.JdbcConnection; ...@@ -34,6 +35,7 @@ import org.h2.jdbc.JdbcConnection;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.tools.SimpleResultSet; import org.h2.tools.SimpleResultSet;
import org.h2.util.JdbcUtils; import org.h2.util.JdbcUtils;
import org.h2.util.LocalDateTimeUtils;
import org.h2.util.New; import org.h2.util.New;
import org.h2.util.Utils; import org.h2.util.Utils;
...@@ -957,6 +959,14 @@ public class DataType { ...@@ -957,6 +959,14 @@ public class DataType {
return Value.ARRAY; return Value.ARRAY;
} else if (isGeometryClass(x)) { } else if (isGeometryClass(x)) {
return Value.GEOMETRY; return Value.GEOMETRY;
} else if (LocalDateTimeUtils.isLocalDate(x)) {
return Value.DATE;
} else if (LocalDateTimeUtils.isLocalTime(x)) {
return Value.TIME;
} else if (LocalDateTimeUtils.isLocalDateTime(x)) {
return Value.TIMESTAMP;
} else if (LocalDateTimeUtils.isOffsetDateTime(x)) {
return Value.TIMESTAMP_TZ;
} else { } else {
return Value.JAVA_OBJECT; return Value.JAVA_OBJECT;
} }
...@@ -1065,11 +1075,20 @@ public class DataType { ...@@ -1065,11 +1075,20 @@ public class DataType {
return ValueStringFixed.get(((Character) x).toString()); return ValueStringFixed.get(((Character) x).toString());
} else if (isGeometry(x)) { } else if (isGeometry(x)) {
return ValueGeometry.getFromGeometry(x); return ValueGeometry.getFromGeometry(x);
} else if (LocalDateTimeUtils.isLocalDate(x.getClass())) {
return LocalDateTimeUtils.localDateToDateValue(x);
} else if (LocalDateTimeUtils.isLocalTime(x.getClass())) {
return LocalDateTimeUtils.localTimeToTimeValue(x);
} else if (LocalDateTimeUtils.isLocalDateTime(x.getClass())) {
return LocalDateTimeUtils.localDateTimeToValue(x);
} else if (LocalDateTimeUtils.isOffsetDateTime(x.getClass())) {
return LocalDateTimeUtils.offsetDateTimeToValue(x);
} else { } else {
return ValueJavaObject.getNoCopy(x, null, session.getDataHandler()); return ValueJavaObject.getNoCopy(x, null, session.getDataHandler());
} }
} }
/** /**
* Check whether a given class matches the Geometry class. * Check whether a given class matches the Geometry class.
* *
......
...@@ -27,6 +27,7 @@ import org.h2.test.TestBase; ...@@ -27,6 +27,7 @@ import org.h2.test.TestBase;
import org.h2.tools.SimpleResultSet; import org.h2.tools.SimpleResultSet;
import org.h2.util.IOUtils; import org.h2.util.IOUtils;
import org.h2.util.JdbcUtils; import org.h2.util.JdbcUtils;
import org.h2.util.LocalDateTimeUtils;
import org.h2.util.Utils; import org.h2.util.Utils;
/** /**
...@@ -170,17 +171,29 @@ public class TestCallableStatement extends TestBase { ...@@ -170,17 +171,29 @@ public class TestCallableStatement extends TestBase {
call.registerOutParameter(1, Types.DATE); call.registerOutParameter(1, Types.DATE);
call.execute(); call.execute();
assertEquals("2000-01-01", call.getDate(1).toString()); assertEquals("2000-01-01", call.getDate(1).toString());
if (LocalDateTimeUtils.isJava8DateApiPresent()) {
assertEquals("2000-01-01", call.getObject(1,
LocalDateTimeUtils.getLocalDateClass()).toString());
}
call.setTime(2, java.sql.Time.valueOf("01:02:03")); call.setTime(2, java.sql.Time.valueOf("01:02:03"));
call.registerOutParameter(1, Types.TIME); call.registerOutParameter(1, Types.TIME);
call.execute(); call.execute();
assertEquals("01:02:03", call.getTime(1).toString()); assertEquals("01:02:03", call.getTime(1).toString());
if (LocalDateTimeUtils.isJava8DateApiPresent()) {
assertEquals("01:02:03", call.getObject(1,
LocalDateTimeUtils.getLocalTimeClass()).toString());
}
call.setTimestamp(2, java.sql.Timestamp.valueOf( call.setTimestamp(2, java.sql.Timestamp.valueOf(
"2001-02-03 04:05:06.789")); "2001-02-03 04:05:06.789"));
call.registerOutParameter(1, Types.TIMESTAMP); call.registerOutParameter(1, Types.TIMESTAMP);
call.execute(); call.execute();
assertEquals("2001-02-03 04:05:06.789", call.getTimestamp(1).toString()); assertEquals("2001-02-03 04:05:06.789", call.getTimestamp(1).toString());
if (LocalDateTimeUtils.isJava8DateApiPresent()) {
assertEquals("2001-02-03T04:05:06.789", call.getObject(1,
LocalDateTimeUtils.getLocalDateTimeClass()).toString());
}
call.setBoolean(2, true); call.setBoolean(2, true);
call.registerOutParameter(1, Types.BIT); call.registerOutParameter(1, Types.BIT);
...@@ -264,10 +277,28 @@ public class TestCallableStatement extends TestBase { ...@@ -264,10 +277,28 @@ public class TestCallableStatement extends TestBase {
assertEquals("2001-02-03 10:20:30.0", call.getTimestamp(4).toString()); assertEquals("2001-02-03 10:20:30.0", call.getTimestamp(4).toString());
assertEquals("2001-02-03 10:20:30.0", call.getTimestamp("D").toString()); assertEquals("2001-02-03 10:20:30.0", call.getTimestamp("D").toString());
if (LocalDateTimeUtils.isJava8DateApiPresent()) {
assertEquals("2001-02-03T10:20:30", call.getObject(4,
LocalDateTimeUtils.getLocalDateTimeClass()).toString());
assertEquals("2001-02-03T10:20:30", call.getObject("D",
LocalDateTimeUtils.getLocalDateTimeClass()).toString());
}
assertEquals("10:20:30", call.getTime(4).toString()); assertEquals("10:20:30", call.getTime(4).toString());
assertEquals("10:20:30", call.getTime("D").toString()); assertEquals("10:20:30", call.getTime("D").toString());
if (LocalDateTimeUtils.isJava8DateApiPresent()) {
assertEquals("10:20:30", call.getObject(4,
LocalDateTimeUtils.getLocalTimeClass()).toString());
assertEquals("10:20:30", call.getObject("D",
LocalDateTimeUtils.getLocalTimeClass()).toString());
}
assertEquals("2001-02-03", call.getDate(4).toString()); assertEquals("2001-02-03", call.getDate(4).toString());
assertEquals("2001-02-03", call.getDate("D").toString()); assertEquals("2001-02-03", call.getDate("D").toString());
if (LocalDateTimeUtils.isJava8DateApiPresent()) {
assertEquals("2001-02-03", call.getObject(4,
LocalDateTimeUtils.getLocalDateClass()).toString());
assertEquals("2001-02-03", call.getObject("D",
LocalDateTimeUtils.getLocalDateClass()).toString());
}
assertEquals(100, call.getInt(1)); assertEquals(100, call.getInt(1));
assertEquals(100, call.getInt("A")); assertEquals(100, call.getInt("A"));
......
...@@ -24,9 +24,11 @@ import java.sql.Statement; ...@@ -24,9 +24,11 @@ import java.sql.Statement;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.sql.Types; import java.sql.Types;
import java.util.UUID; import java.util.UUID;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.api.Trigger; import org.h2.api.Trigger;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.util.LocalDateTimeUtils;
import org.h2.util.Task; import org.h2.util.Task;
/** /**
...@@ -71,6 +73,10 @@ public class TestPreparedStatement extends TestBase { ...@@ -71,6 +73,10 @@ public class TestPreparedStatement extends TestBase {
testCoalesce(conn); testCoalesce(conn);
testPreparedStatementMetaData(conn); testPreparedStatementMetaData(conn);
testDate(conn); testDate(conn);
testDate8(conn);
testTime8(conn);
testDateTime8(conn);
testOffsetDateTime8(conn);
testArray(conn); testArray(conn);
testUUIDGeneratedKeys(conn); testUUIDGeneratedKeys(conn);
testSetObject(conn); testSetObject(conn);
...@@ -589,6 +595,62 @@ public class TestPreparedStatement extends TestBase { ...@@ -589,6 +595,62 @@ public class TestPreparedStatement extends TestBase {
assertEquals(ts.toString(), ts2.toString()); assertEquals(ts.toString(), ts2.toString());
} }
private void testDate8(Connection conn) throws SQLException {
if (!LocalDateTimeUtils.isJava8DateApiPresent()) {
return;
}
PreparedStatement prep = conn.prepareStatement("SELECT ?");
Object localDate = LocalDateTimeUtils.parseLocalDate("2001-02-03");
prep.setObject(1, localDate);
ResultSet rs = prep.executeQuery();
rs.next();
Object localDate2 = rs.getObject(1, LocalDateTimeUtils.getLocalDateClass());
assertEquals(localDate, localDate2);
rs.close();
}
private void testTime8(Connection conn) throws SQLException {
if (!LocalDateTimeUtils.isJava8DateApiPresent()) {
return;
}
PreparedStatement prep = conn.prepareStatement("SELECT ?");
Object localTime = LocalDateTimeUtils.parseLocalTime("04:05:06");
prep.setObject(1, localTime);
ResultSet rs = prep.executeQuery();
rs.next();
Object localTime2 = rs.getObject(1, LocalDateTimeUtils.getLocalTimeClass());
assertEquals(localTime, localTime2);
rs.close();
}
private void testDateTime8(Connection conn) throws SQLException {
if (!LocalDateTimeUtils.isJava8DateApiPresent()) {
return;
}
PreparedStatement prep = conn.prepareStatement("SELECT ?");
Object localDateTime = LocalDateTimeUtils.parseLocalDateTime("2001-02-03T04:05:06");
prep.setObject(1, localDateTime);
ResultSet rs = prep.executeQuery();
rs.next();
Object localDateTime2 = rs.getObject(1, LocalDateTimeUtils.getLocalDateClass());
assertEquals(localDateTime, localDateTime2);
rs.close();
}
private void testOffsetDateTime8(Connection conn) throws SQLException {
if (!LocalDateTimeUtils.isJava8DateApiPresent()) {
return;
}
PreparedStatement prep = conn.prepareStatement("SELECT ?");
Object offsetDateTime = LocalDateTimeUtils.parseOffsetDateTime("2001-02-03T04:05:06+02:30");
prep.setObject(1, offsetDateTime);
ResultSet rs = prep.executeQuery();
rs.next();
Object offsetDateTime2 = rs.getObject(1, LocalDateTimeUtils.getOffsetDateTimeClass());
assertEquals(offsetDateTime, offsetDateTime2);
rs.close();
}
private void testPreparedSubquery(Connection conn) throws SQLException { private void testPreparedSubquery(Connection conn) throws SQLException {
Statement s = conn.createStatement(); Statement s = conn.createStatement();
s.executeUpdate("CREATE TABLE TEST(ID IDENTITY, FLAG BIT)"); s.executeUpdate("CREATE TABLE TEST(ID IDENTITY, FLAG BIT)");
......
...@@ -37,6 +37,7 @@ import java.util.TimeZone; ...@@ -37,6 +37,7 @@ import java.util.TimeZone;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.util.IOUtils; import org.h2.util.IOUtils;
import org.h2.util.LocalDateTimeUtils;
/** /**
* Tests for the ResultSet implementation. * Tests for the ResultSet implementation.
...@@ -1057,18 +1058,44 @@ public class TestResultSet extends TestBase { ...@@ -1057,18 +1058,44 @@ public class TestResultSet extends TestBase {
assertEquals("2002-02-02 02:02:02.0", ts.toString()); assertEquals("2002-02-02 02:02:02.0", ts.toString());
rs.next(); rs.next();
assertEquals("1800-01-01", rs.getDate("value").toString()); assertEquals("1800-01-01", rs.getDate("value").toString());
if (LocalDateTimeUtils.isJava8DateApiPresent()) {
assertEquals("1800-01-01", rs.getObject("value",
LocalDateTimeUtils.getLocalDateClass()).toString());
}
assertEquals("00:00:00", rs.getTime("value").toString()); assertEquals("00:00:00", rs.getTime("value").toString());
assertEquals("1800-01-01 00:00:00.0", if (LocalDateTimeUtils.isJava8DateApiPresent()) {
rs.getTimestamp("value").toString()); assertEquals("00:00", rs.getObject("value",
LocalDateTimeUtils.getLocalTimeClass()).toString());
}
assertEquals("1800-01-01 00:00:00.0", rs.getTimestamp("value").toString());
if (LocalDateTimeUtils.isJava8DateApiPresent()) {
assertEquals("1800-01-01T00:00", rs.getObject("value",
LocalDateTimeUtils.getLocalDateTimeClass()).toString());
}
rs.next(); rs.next();
assertEquals("9999-12-31", rs.getDate("Value").toString()); assertEquals("9999-12-31", rs.getDate("Value").toString());
if (LocalDateTimeUtils.isJava8DateApiPresent()) {
assertEquals("9999-12-31", rs.getObject("Value",
LocalDateTimeUtils.getLocalDateClass()).toString());
}
assertEquals("23:59:59", rs.getTime("Value").toString()); assertEquals("23:59:59", rs.getTime("Value").toString());
assertEquals("9999-12-31 23:59:59.0", if (LocalDateTimeUtils.isJava8DateApiPresent()) {
rs.getTimestamp("Value").toString()); assertEquals("23:59:59", rs.getObject("Value",
LocalDateTimeUtils.getLocalTimeClass()).toString());
}
assertEquals("9999-12-31 23:59:59.0", rs.getTimestamp("Value").toString());
if (LocalDateTimeUtils.isJava8DateApiPresent()) {
assertEquals("9999-12-31T23:59:59", rs.getObject("Value",
LocalDateTimeUtils.getLocalDateTimeClass()).toString());
}
rs.next(); rs.next();
assertTrue(rs.getDate("Value") == null && rs.wasNull()); assertTrue(rs.getDate("Value") == null && rs.wasNull());
assertTrue(rs.getTime("vALUe") == null && rs.wasNull()); assertTrue(rs.getTime("vALUe") == null && rs.wasNull());
assertTrue(rs.getTimestamp(2) == null && rs.wasNull()); assertTrue(rs.getTimestamp(2) == null && rs.wasNull());
if (LocalDateTimeUtils.isJava8DateApiPresent()) {
assertTrue(rs.getObject(2,
LocalDateTimeUtils.getLocalDateTimeClass()) == null && rs.wasNull());
}
assertTrue(!rs.next()); assertTrue(!rs.next());
rs = stat.executeQuery("SELECT DATE '2001-02-03' D, " + rs = stat.executeQuery("SELECT DATE '2001-02-03' D, " +
...@@ -1087,6 +1114,18 @@ public class TestResultSet extends TestBase { ...@@ -1087,6 +1114,18 @@ public class TestResultSet extends TestBase {
assertEquals("2001-02-03", date.toString()); assertEquals("2001-02-03", date.toString());
assertEquals("14:15:16", time.toString()); assertEquals("14:15:16", time.toString());
assertEquals("2007-08-09 10:11:12.141516171", ts.toString()); assertEquals("2007-08-09 10:11:12.141516171", ts.toString());
if (LocalDateTimeUtils.isJava8DateApiPresent()) {
assertEquals("2001-02-03", rs.getObject(1,
LocalDateTimeUtils.getLocalDateClass()).toString());
}
if (LocalDateTimeUtils.isJava8DateApiPresent()) {
assertEquals("14:15:16", rs.getObject(2,
LocalDateTimeUtils.getLocalTimeClass()).toString());
}
if (LocalDateTimeUtils.isJava8DateApiPresent()) {
assertEquals("2007-08-09T10:11:12.141516171",
rs.getObject(3, LocalDateTimeUtils.getLocalDateTimeClass()).toString());
}
stat.execute("DROP TABLE TEST"); stat.execute("DROP TABLE TEST");
} }
......
...@@ -11,6 +11,7 @@ import java.sql.SQLException; ...@@ -11,6 +11,7 @@ import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import org.h2.api.TimestampWithTimeZone; import org.h2.api.TimestampWithTimeZone;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.util.LocalDateTimeUtils;
import org.h2.value.ValueTimestampTimeZone; import org.h2.value.ValueTimestampTimeZone;
/** /**
...@@ -57,6 +58,10 @@ public class TestTimeStampWithTimeZone extends TestBase { ...@@ -57,6 +58,10 @@ public class TestTimeStampWithTimeZone extends TestBase {
assertEquals(1, ts.getDay()); assertEquals(1, ts.getDay());
assertEquals(15, ts.getTimeZoneOffsetMins()); assertEquals(15, ts.getTimeZoneOffsetMins());
assertEquals(new TimestampWithTimeZone(1008673L, 43200000000000L, (short) 15), ts); assertEquals(new TimestampWithTimeZone(1008673L, 43200000000000L, (short) 15), ts);
if (LocalDateTimeUtils.isJava8DateApiPresent()) {
assertEquals("1970-01-01T12:00+00:15", rs.getObject(1,
LocalDateTimeUtils.getOffsetDateTimeClass()).toString());
}
rs.next(); rs.next();
ts = (TimestampWithTimeZone) rs.getObject(1); ts = (TimestampWithTimeZone) rs.getObject(1);
assertEquals(2016, ts.getYear()); assertEquals(2016, ts.getYear());
...@@ -64,6 +69,10 @@ public class TestTimeStampWithTimeZone extends TestBase { ...@@ -64,6 +69,10 @@ public class TestTimeStampWithTimeZone extends TestBase {
assertEquals(24, ts.getDay()); assertEquals(24, ts.getDay());
assertEquals(1, ts.getTimeZoneOffsetMins()); assertEquals(1, ts.getTimeZoneOffsetMins());
assertEquals(1L, ts.getNanosSinceMidnight()); assertEquals(1L, ts.getNanosSinceMidnight());
if (LocalDateTimeUtils.isJava8DateApiPresent()) {
assertEquals("2016-09-24T00:00:00.000000001+00:01", rs.getObject(1,
LocalDateTimeUtils.getOffsetDateTimeClass()).toString());
}
rs.next(); rs.next();
ts = (TimestampWithTimeZone) rs.getObject(1); ts = (TimestampWithTimeZone) rs.getObject(1);
assertEquals(2016, ts.getYear()); assertEquals(2016, ts.getYear());
...@@ -71,16 +80,28 @@ public class TestTimeStampWithTimeZone extends TestBase { ...@@ -71,16 +80,28 @@ public class TestTimeStampWithTimeZone extends TestBase {
assertEquals(24, ts.getDay()); assertEquals(24, ts.getDay());
assertEquals(-1, ts.getTimeZoneOffsetMins()); assertEquals(-1, ts.getTimeZoneOffsetMins());
assertEquals(1L, ts.getNanosSinceMidnight()); assertEquals(1L, ts.getNanosSinceMidnight());
if (LocalDateTimeUtils.isJava8DateApiPresent()) {
assertEquals("2016-09-24T00:00:00.000000001-00:01", rs.getObject(1,
LocalDateTimeUtils.getOffsetDateTimeClass()).toString());
}
rs.next(); rs.next();
ts = (TimestampWithTimeZone) rs.getObject(1); ts = (TimestampWithTimeZone) rs.getObject(1);
assertEquals(2016, ts.getYear()); assertEquals(2016, ts.getYear());
assertEquals(1, ts.getMonth()); assertEquals(1, ts.getMonth());
assertEquals(1, ts.getDay()); assertEquals(1, ts.getDay());
if (LocalDateTimeUtils.isJava8DateApiPresent()) {
assertEquals("2016-01-01T05:00+10:00", rs.getObject(1,
LocalDateTimeUtils.getOffsetDateTimeClass()).toString());
}
rs.next(); rs.next();
ts = (TimestampWithTimeZone) rs.getObject(1); ts = (TimestampWithTimeZone) rs.getObject(1);
assertEquals(2015, ts.getYear()); assertEquals(2015, ts.getYear());
assertEquals(12, ts.getMonth()); assertEquals(12, ts.getMonth());
assertEquals(31, ts.getDay()); assertEquals(31, ts.getDay());
if (LocalDateTimeUtils.isJava8DateApiPresent()) {
assertEquals("2015-12-31T19:00-10:00", rs.getObject(1,
LocalDateTimeUtils.getOffsetDateTimeClass()).toString());
}
rs.close(); rs.close();
stat.close(); stat.close();
conn.close(); conn.close();
...@@ -92,7 +113,7 @@ public class TestTimeStampWithTimeZone extends TestBase { ...@@ -92,7 +113,7 @@ public class TestTimeStampWithTimeZone extends TestBase {
int c = a.compareTo(b, null); int c = a.compareTo(b, null);
assertEquals(c, 1); assertEquals(c, 1);
} }
private void test3() { private void test3() {
ValueTimestampTimeZone a = ValueTimestampTimeZone.parse("1970-01-02 00:00:02.00+01:15"); ValueTimestampTimeZone a = ValueTimestampTimeZone.parse("1970-01-02 00:00:02.00+01:15");
ValueTimestampTimeZone b = ValueTimestampTimeZone.parse("1970-01-01 23:00:01.00+00:15"); ValueTimestampTimeZone b = ValueTimestampTimeZone.parse("1970-01-01 23:00:01.00+00:15");
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论