提交 cecf30f0 authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Implement conversions for TIMESTAMP WITH TIME ZONE as described in standard

上级 525cc9ac
......@@ -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.
*
......
......@@ -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
......
......@@ -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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论