提交 7c4bebd6 authored 作者: Thomas Mueller's avatar Thomas Mueller

The JDBC methods PreparedStatement.setTimestamp, setTime, and setDate with a…

The JDBC methods PreparedStatement.setTimestamp, setTime, and setDate with a calendar, and the methods ResultSet.getTimestamp, getTime, and getDate with a calendar converted the value in the wrong way, so that for some timestamps the converted value was wrong (where summertime starts, one hour per year).
上级 2e060308
......@@ -611,7 +611,7 @@ public class JdbcPreparedStatement extends JdbcStatement implements PreparedStat
if (x == null) {
setParameter(parameterIndex, ValueNull.INSTANCE);
} else {
setParameter(parameterIndex, DateTimeUtils.convertDateToUTC(x, calendar));
setParameter(parameterIndex, DateTimeUtils.convertDate(x, calendar));
}
} catch (Exception e) {
throw logAndConvert(e);
......@@ -635,7 +635,7 @@ public class JdbcPreparedStatement extends JdbcStatement implements PreparedStat
if (x == null) {
setParameter(parameterIndex, ValueNull.INSTANCE);
} else {
setParameter(parameterIndex, DateTimeUtils.convertTimeToUTC(x, calendar));
setParameter(parameterIndex, DateTimeUtils.convertTime(x, calendar));
}
} catch (Exception e) {
throw logAndConvert(e);
......@@ -659,7 +659,7 @@ public class JdbcPreparedStatement extends JdbcStatement implements PreparedStat
if (x == null) {
setParameter(parameterIndex, ValueNull.INSTANCE);
} else {
setParameter(parameterIndex, DateTimeUtils.convertTimestampToUTC(x, calendar));
setParameter(parameterIndex, DateTimeUtils.convertTimestamp(x, calendar));
}
} catch (Exception e) {
throw logAndConvert(e);
......
......@@ -806,8 +806,7 @@ public class JdbcResultSet extends TraceObject implements ResultSet {
if (isDebugEnabled()) {
debugCode("getDate(" + columnIndex + ", calendar)");
}
Date x = get(columnIndex).getDate();
return DateTimeUtils.convertDateToCalendar(x, calendar);
return DateTimeUtils.convertDate(get(columnIndex), calendar);
} catch (Exception e) {
throw logAndConvert(e);
}
......@@ -828,8 +827,7 @@ public class JdbcResultSet extends TraceObject implements ResultSet {
if (isDebugEnabled()) {
debugCode("getDate(" + StringUtils.quoteJavaString(columnLabel) + ", calendar)");
}
Date x = get(columnLabel).getDate();
return DateTimeUtils.convertDateToCalendar(x, calendar);
return DateTimeUtils.convertDate(get(columnLabel), calendar);
} catch (Exception e) {
throw logAndConvert(e);
}
......@@ -850,8 +848,7 @@ public class JdbcResultSet extends TraceObject implements ResultSet {
if (isDebugEnabled()) {
debugCode("getTime(" + columnIndex + ", calendar)");
}
Time x = get(columnIndex).getTime();
return DateTimeUtils.convertTimeToCalendar(x, calendar);
return DateTimeUtils.convertTime(get(columnIndex), calendar);
} catch (Exception e) {
throw logAndConvert(e);
}
......@@ -872,8 +869,7 @@ public class JdbcResultSet extends TraceObject implements ResultSet {
if (isDebugEnabled()) {
debugCode("getTime(" + StringUtils.quoteJavaString(columnLabel) + ", calendar)");
}
Time x = get(columnLabel).getTime();
return DateTimeUtils.convertTimeToCalendar(x, calendar);
return DateTimeUtils.convertTime(get(columnLabel), calendar);
} catch (Exception e) {
throw logAndConvert(e);
}
......@@ -894,8 +890,8 @@ public class JdbcResultSet extends TraceObject implements ResultSet {
if (isDebugEnabled()) {
debugCode("getTimestamp(" + columnIndex + ", calendar)");
}
Timestamp x = get(columnIndex).getTimestamp();
return DateTimeUtils.convertTimestampToCalendar(x, calendar);
Value value = get(columnIndex);
return DateTimeUtils.convertTimestamp(value, calendar);
} catch (Exception e) {
throw logAndConvert(e);
}
......@@ -915,8 +911,8 @@ public class JdbcResultSet extends TraceObject implements ResultSet {
if (isDebugEnabled()) {
debugCode("getTimestamp(" + StringUtils.quoteJavaString(columnLabel) + ", calendar)");
}
Timestamp x = get(columnLabel).getTimestamp();
return DateTimeUtils.convertTimestampToCalendar(x, calendar);
Value value = get(columnLabel);
return DateTimeUtils.convertTimestamp(value, calendar);
} catch (Exception e) {
throw logAndConvert(e);
}
......
......@@ -20,6 +20,7 @@ import org.h2.constant.ErrorCode;
import org.h2.message.DbException;
import org.h2.value.Value;
import org.h2.value.ValueDate;
import org.h2.value.ValueNull;
import org.h2.value.ValueTime;
import org.h2.value.ValueTimestamp;
......@@ -75,96 +76,124 @@ public class DateTimeUtils {
/**
* Convert the date to the specified time zone.
*
* @param x the date
* @param value the date (might be ValueNull)
* @param calendar the calendar
* @return the date using the correct time zone
*/
public static Date convertDateToCalendar(Date x, Calendar calendar) {
return x == null ? null : new Date(convertToLocal(x, calendar));
public static Date convertDate(Value value, Calendar calendar) {
if (value == ValueNull.INSTANCE) {
return null;
}
ValueDate d = (ValueDate) value.convertTo(Value.DATE);
Calendar cal = (Calendar) calendar.clone();
cal.clear();
cal.setLenient(true);
long dateValue = d.getDateValue();
setCalendarFields(cal, yearFromDateValue(dateValue),
monthFromDateValue(dateValue),
dayFromDateValue(dateValue),
0, 0, 0, 0);
long ms = cal.getTimeInMillis();
return new Date(ms);
}
/**
* Convert the time to the specified time zone.
*
* @param x the time
* @param value the time (might be ValueNull)
* @param calendar the calendar
* @return the time using the correct time zone
*/
public static Time convertTimeToCalendar(Time x, Calendar calendar) {
return x == null ? null : new Time(convertToLocal(x, calendar));
public static Time convertTime(Value value, Calendar calendar) {
if (value == ValueNull.INSTANCE) {
return null;
}
ValueTime t = (ValueTime) value.convertTo(Value.TIME);
Calendar cal = (Calendar) calendar.clone();
cal.clear();
cal.setLenient(true);
long nanos = t.getNanos();
long millis = nanos / 1000000;
nanos -= millis * 1000000;
long s = millis / 1000;
millis -= s * 1000;
long m = s / 60;
s -= m * 60;
long h = m / 60;
m -= h * 60;
setCalendarFields(cal, 1970, 1, 1,
(int) h, (int) m, (int) s, (int) millis);
long ms = cal.getTimeInMillis();
return new Time(ms);
}
/**
* Convert the timestamp to the specified time zone.
*
* @param x the timestamp
* @param value the timestamp (might be ValueNull)
* @param calendar the calendar
* @return the timestamp using the correct time zone
*/
public static Timestamp convertTimestampToCalendar(Timestamp x, Calendar calendar) {
if (x != null) {
Timestamp y = new Timestamp(convertToLocal(x, calendar));
// fix the nano seconds
y.setNanos(x.getNanos());
x = y;
public static Timestamp convertTimestamp(Value value, Calendar calendar) {
if (value == ValueNull.INSTANCE) {
return null;
}
ValueTimestamp ts = (ValueTimestamp) value.convertTo(Value.TIMESTAMP);
Calendar cal = (Calendar) calendar.clone();
cal.clear();
cal.setLenient(true);
long dateValue = ts.getDateValue();
long nanos = ts.getNanos();
long millis = nanos / 1000000;
nanos -= millis * 1000000;
long s = millis / 1000;
millis -= s * 1000;
long m = s / 60;
s -= m * 60;
long h = m / 60;
m -= h * 60;
setCalendarFields(cal, yearFromDateValue(dateValue),
monthFromDateValue(dateValue),
dayFromDateValue(dateValue),
(int) h, (int) m, (int) s, (int) millis);
long ms = cal.getTimeInMillis();
Timestamp x = new Timestamp(ms);
x.setNanos((int) nanos);
return x;
}
/**
* Convert the date from the specified time zone to UTC.
* Convert the date using the specified calendar.
*
* @param x the date
* @param source the calendar
* @return the date in UTC
*/
public static Value convertDateToUTC(Date x, Calendar source) {
return ValueDate.get(new Date(convertToUTC(x, source)));
}
/**
* Convert the time from the specified time zone to UTC.
*
* @param x the time
* @param source the calendar
* @return the time in UTC
* @param calendar the calendar
* @return the date
*/
public static Value convertTimeToUTC(Time x, Calendar source) {
return ValueTime.get(new Time(convertToUTC(x, source)));
public static ValueDate convertDate(Date x, Calendar calendar) {
if (calendar == null) {
throw DbException.getInvalidValueException("calendar", null);
}
Calendar cal = (Calendar) calendar.clone();
cal.setTimeInMillis(x.getTime());
long dateValue = dateValueFromCalendar(cal);
return ValueDate.fromDateValue(dateValue);
}
/**
* Convert the timestamp from the specified time zone to UTC.
* Convert the time using the specified calendar.
*
* @param x the time
* @param source the calendar
* @return the timestamp in UTC
*/
public static Value convertTimestampToUTC(Timestamp x, Calendar source) {
Timestamp y = new Timestamp(convertToUTC(x, source));
// fix the nano seconds
y.setNanos(x.getNanos());
return ValueTimestamp.get(y);
}
/**
* Convert the date value to UTC using the given calendar.
*
* @param x the date
* @param source the source calendar
* @return the UTC number of milliseconds.
* @param calendar the calendar
* @return the time
*/
private static long convertToUTC(java.util.Date x, Calendar source) {
if (source == null) {
public static ValueTime convertTime(Time x, Calendar calendar) {
if (calendar == null) {
throw DbException.getInvalidValueException("calendar", null);
}
source = (Calendar) source.clone();
Calendar universal = getCalendar();
synchronized (universal) {
source.setTime(x);
convertTime(source, universal);
return universal.getTime().getTime();
}
Calendar cal = (Calendar) calendar.clone();
cal.setTimeInMillis(x.getTime());
long nanos = nanosFromCalendar(cal);
return ValueTime.fromNanos(nanos);
}
/**
......@@ -197,6 +226,25 @@ public class DateTimeUtils {
to.set(Calendar.SECOND, from.get(Calendar.SECOND));
to.set(Calendar.MILLISECOND, from.get(Calendar.MILLISECOND));
}
/**
* Convert the timestamp using the specified calendar.
*
* @param x the time
* @param calendar the calendar
* @return the timestamp
*/
public static ValueTimestamp convertTimestamp(Timestamp x, Calendar calendar) {
if (calendar == null) {
throw DbException.getInvalidValueException("calendar", null);
}
Calendar cal = (Calendar) calendar.clone();
cal.setTimeInMillis(x.getTime());
long dateValue = dateValueFromCalendar(cal);
long nanos = nanosFromCalendar(cal);
nanos += x.getNanos() % 1000000;
return ValueTimestamp.fromDateValueAndNanos(dateValue, nanos);
}
/**
* Parse a date string. The format is: [+|-]year-month-day
......@@ -335,24 +383,29 @@ public class DateTimeUtils {
synchronized (c) {
c.clear();
c.setLenient(lenient);
if (year <= 0) {
c.set(Calendar.ERA, GregorianCalendar.BC);
c.set(Calendar.YEAR, 1 - year);
} else {
c.set(Calendar.ERA, GregorianCalendar.AD);
c.set(Calendar.YEAR, year);
}
// january is 0
c.set(Calendar.MONTH, month - 1);
c.set(Calendar.DAY_OF_MONTH, day);
c.set(Calendar.HOUR_OF_DAY, hour);
c.set(Calendar.MINUTE, minute);
c.set(Calendar.SECOND, second);
c.set(Calendar.MILLISECOND, millis);
setCalendarFields(c, year, month, day, hour, minute, second, millis);
return c.getTime().getTime();
}
}
private static void setCalendarFields(Calendar cal, int year, int month, int day,
int hour, int minute, int second, int millis) {
if (year <= 0) {
cal.set(Calendar.ERA, GregorianCalendar.BC);
cal.set(Calendar.YEAR, 1 - year);
} else {
cal.set(Calendar.ERA, GregorianCalendar.AD);
cal.set(Calendar.YEAR, year);
}
// january is 0
cal.set(Calendar.MONTH, month - 1);
cal.set(Calendar.DAY_OF_MONTH, day);
cal.set(Calendar.HOUR_OF_DAY, hour);
cal.set(Calendar.MINUTE, minute);
cal.set(Calendar.SECOND, second);
cal.set(Calendar.MILLISECOND, millis);
}
/**
* Get the specified field of a date, however with years normalized to
* positive or negative, and month starting with 1.
......@@ -678,34 +731,54 @@ public class DateTimeUtils {
synchronized (cal) {
cal.clear();
cal.setTimeInMillis(ms);
int year, month, day;
year = getYear(cal);
month = cal.get(Calendar.MONTH) + 1;
day = cal.get(Calendar.DAY_OF_MONTH);
return ((long) year << SHIFT_YEAR) | (month << SHIFT_MONTH) | day;
return dateValueFromCalendar(cal);
}
}
/**
* Calculate the date value from a given calendar.
*
* @param cal the calendar
* @return the date value
*/
private static long dateValueFromCalendar(Calendar cal) {
int year, month, day;
year = getYear(cal);
month = cal.get(Calendar.MONTH) + 1;
day = cal.get(Calendar.DAY_OF_MONTH);
return ((long) year << SHIFT_YEAR) | (month << SHIFT_MONTH) | day;
}
/**
* Calculate the nanoseconds since midnight (in the default timezone) from a
* given time in milliseconds in UTC.
*
* @param ms the milliseconds
* @return the date value
* @return the nanoseconds
*/
public static long nanosFromDate(long ms) {
Calendar cal = getCalendar();
synchronized (cal) {
cal.clear();
cal.setTimeInMillis(ms);
int h = cal.get(Calendar.HOUR_OF_DAY);
int m = cal.get(Calendar.MINUTE);
int s = cal.get(Calendar.SECOND);
int millis = cal.get(Calendar.MILLISECOND);
return ((((((h * 60L) + m) * 60) + s) * 1000) + millis) * 1000000;
return nanosFromCalendar(cal);
}
}
/**
* Calculate the nanoseconds since midnight from a given calendar.
*
* @param cal the calendar
* @return the nanoseconds
*/
private static long nanosFromCalendar(Calendar cal) {
int h = cal.get(Calendar.HOUR_OF_DAY);
int m = cal.get(Calendar.MINUTE);
int s = cal.get(Calendar.SECOND);
int millis = cal.get(Calendar.MILLISECOND);
return ((((((h * 60L) + m) * 60) + s) * 1000) + millis) * 1000000;
}
/**
* Calculate the normalized timestamp.
*
......
......@@ -11,18 +11,25 @@ import java.io.IOException;
import java.io.StringReader;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.Date;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.SimpleTimeZone;
import java.util.TimeZone;
import java.util.UUID;
import org.h2.api.Trigger;
import org.h2.constant.ErrorCode;
import org.h2.test.TestBase;
import org.h2.util.DateTimeUtils;
import org.h2.util.Task;
/**
......@@ -44,6 +51,7 @@ public class TestPreparedStatement extends TestBase {
public void test() throws Exception {
deleteDb("preparedStatement");
Connection conn = getConnection("preparedStatement");
testDateTimeTimestampWithCalendar(conn);
testCallTablePrepared(conn);
testValues(conn);
testToString(conn);
......@@ -81,6 +89,95 @@ public class TestPreparedStatement extends TestBase {
deleteDb("preparedStatement");
}
private void testDateTimeTimestampWithCalendar(Connection conn) throws SQLException {
Statement stat = conn.createStatement();
stat.execute("create table ts(x timestamp primary key)");
stat.execute("create table t(x time primary key)");
stat.execute("create table d(x date)");
Calendar utcCalendar = new GregorianCalendar(new SimpleTimeZone(0, "Z"));
TimeZone old = TimeZone.getDefault();
DateTimeUtils.resetCalendar();
TimeZone.setDefault(TimeZone.getTimeZone("PST"));
try {
Timestamp ts1 = Timestamp.valueOf("2010-03-13 18:15:00");
Time t1 = new Time(ts1.getTime());
Date d1 = new Date(ts1.getTime());
// when converted to UTC, this is 03:15, which doesn't actually exist
// because of summer time change at that day
Timestamp ts2 = Timestamp.valueOf("2010-03-13 19:15:00");
Time t2 = new Time(ts2.getTime());
Date d2 = new Date(ts2.getTime());
PreparedStatement prep;
ResultSet rs;
prep = conn.prepareStatement("insert into ts values(?)");
prep.setTimestamp(1, ts1, utcCalendar);
prep.execute();
prep.setTimestamp(1, ts2, utcCalendar);
prep.execute();
prep = conn.prepareStatement("insert into t values(?)");
prep.setTime(1, t1, utcCalendar);
prep.execute();
prep.setTime(1, t2, utcCalendar);
prep.execute();
prep = conn.prepareStatement("insert into d values(?)");
prep.setDate(1, d1, utcCalendar);
prep.execute();
prep.setDate(1, d2, utcCalendar);
prep.execute();
rs = stat.executeQuery("select * from ts order by x");
rs.next();
assertEquals("2010-03-14 02:15:00.0", rs.getString(1));
assertEquals("2010-03-13 18:15:00.0", rs.getTimestamp(1, utcCalendar).toString());
assertEquals("2010-03-14 03:15:00.0", rs.getTimestamp(1).toString());
assertEquals("2010-03-14 02:15:00.0", rs.getString("x"));
assertEquals("2010-03-13 18:15:00.0", rs.getTimestamp("x", utcCalendar).toString());
assertEquals("2010-03-14 03:15:00.0", rs.getTimestamp("x").toString());
rs.next();
assertEquals("2010-03-14 03:15:00.0", rs.getString(1));
assertEquals("2010-03-13 19:15:00.0", rs.getTimestamp(1, utcCalendar).toString());
assertEquals("2010-03-14 03:15:00.0", rs.getTimestamp(1).toString());
assertEquals("2010-03-14 03:15:00.0", rs.getString("x"));
assertEquals("2010-03-13 19:15:00.0", rs.getTimestamp("x", utcCalendar).toString());
assertEquals("2010-03-14 03:15:00.0", rs.getTimestamp("x").toString());
rs = stat.executeQuery("select * from t order by x");
rs.next();
assertEquals("02:15:00", rs.getString(1));
assertEquals("18:15:00", rs.getTime(1, utcCalendar).toString());
assertEquals("02:15:00", rs.getTime(1).toString());
assertEquals("02:15:00", rs.getString("x"));
assertEquals("18:15:00", rs.getTime("x", utcCalendar).toString());
assertEquals("02:15:00", rs.getTime("x").toString());
rs.next();
assertEquals("03:15:00", rs.getString(1));
assertEquals("19:15:00", rs.getTime(1, utcCalendar).toString());
assertEquals("03:15:00", rs.getTime(1).toString());
assertEquals("03:15:00", rs.getString("x"));
assertEquals("19:15:00", rs.getTime("x", utcCalendar).toString());
assertEquals("03:15:00", rs.getTime("x").toString());
rs = stat.executeQuery("select * from d order by x");
rs.next();
assertEquals("2010-03-14", rs.getString(1));
assertEquals("2010-03-13", rs.getDate(1, utcCalendar).toString());
assertEquals("2010-03-14", rs.getDate(1).toString());
assertEquals("2010-03-14", rs.getString("x"));
assertEquals("2010-03-13", rs.getDate("x", utcCalendar).toString());
assertEquals("2010-03-14", rs.getDate("x").toString());
rs.next();
assertEquals("2010-03-14", rs.getString(1));
assertEquals("2010-03-13", rs.getDate(1, utcCalendar).toString());
assertEquals("2010-03-14", rs.getDate(1).toString());
assertEquals("2010-03-14", rs.getString("x"));
assertEquals("2010-03-13", rs.getDate("x", utcCalendar).toString());
assertEquals("2010-03-14", rs.getDate("x").toString());
} finally {
TimeZone.setDefault(old);
DateTimeUtils.resetCalendar();
}
stat.execute("drop table ts");
stat.execute("drop table t");
stat.execute("drop table d");
}
private void testCallTablePrepared(Connection conn) throws SQLException {
PreparedStatement prep = conn.prepareStatement("call table(x int = (1))");
prep.executeQuery();
......
......@@ -463,10 +463,10 @@ public class TestDate extends TestBase {
assertEquals("19999-08-07 13:14:15.16", ts2.getString());
assertEquals("19999-08-07", d2.getString());
assertEquals("13:14:15.16", t2.getString());
java.sql.Timestamp ts1a = DateTimeUtils.convertTimestampToCalendar(ts1.getTimestamp(), Calendar.getInstance());
java.sql.Timestamp ts2a = DateTimeUtils.convertTimestampToCalendar(ts2.getTimestamp(), Calendar.getInstance());
assertEquals("-999-08-07 13:14:15.16", ValueTimestamp.get(ts1a).getString());
assertEquals("19999-08-07 13:14:15.16", ValueTimestamp.get(ts2a).getString());
ValueTimestamp ts1a = DateTimeUtils.convertTimestamp(ts1.getTimestamp(), Calendar.getInstance());
ValueTimestamp ts2a = DateTimeUtils.convertTimestamp(ts2.getTimestamp(), Calendar.getInstance());
assertEquals("-999-08-07 13:14:15.16", ts1a.getString());
assertEquals("19999-08-07 13:14:15.16", ts2a.getString());
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论