提交 a7b804e6 authored 作者: Thomas Mueller's avatar Thomas Mueller

There is a problem when opening a database file in a timezone that has different…

There is a problem when opening a database file in a timezone that has different daylight saving rules.
上级 843796ef
...@@ -18,7 +18,17 @@ Change Log ...@@ -18,7 +18,17 @@ Change Log
<h1>Change Log</h1> <h1>Change Log</h1>
<h2>Next Version (unreleased)</h2> <h2>Next Version (unreleased)</h2>
<ul><li>- <ul><li>There is a problem when opening a database file in a timezone that has different
daylight saving rules: the time part of dates where the daylight saving doesn't match
will differ. This is not a problem within regions that use the same rules (such as, within
USA, or within Europe), even if the timezone itself is different. As a workaround, export the
database to a SQL script using the old timezone, and create a new database in the new
timezone. There is a new system property "h2.storeLocalTime" that takes daylight time
saving into account when storing and reading, however database files that are created or
modified with this flag set can not be opened with older versions of H2. The new storage
format is already supported for reading however, so that this version is forward compatible.
The same problem occurs when accessing a database over TCP/IP if the client and server
timezones use different rules, if an old server or client is used.
</li></ul> </li></ul>
<h2>Version 1.3.156 (2011-06-17)</h2> <h2>Version 1.3.156 (2011-06-17)</h2>
......
...@@ -24,6 +24,7 @@ See also <a href="build.html#providing_patches">Providing Patches</a>. ...@@ -24,6 +24,7 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
<h2>Version 1.4.x: Planned Changes</h2> <h2>Version 1.4.x: Planned Changes</h2>
<ul><li>Build the jar file for Java 6 by default (JDBC API 4.1). <ul><li>Build the jar file for Java 6 by default (JDBC API 4.1).
</li><li>Enable the new storage format for dates (system property "h2.storeLocalTime").
</li><li>Remove the old connection pool logic (system property "h2.fastConnectionPool"). </li><li>Remove the old connection pool logic (system property "h2.fastConnectionPool").
</li></ul> </li></ul>
......
...@@ -348,6 +348,13 @@ public class SysProperties { ...@@ -348,6 +348,13 @@ public class SysProperties {
*/ */
public static final long SPLIT_FILE_SIZE_SHIFT = Utils.getProperty("h2.splitFileSizeShift", 30); public static final long SPLIT_FILE_SIZE_SHIFT = Utils.getProperty("h2.splitFileSizeShift", 30);
/**
* System property <code>h2.storeLocalTime</code> (default: false).<br />
* Store the local time in milliseconds since 1970 in the database file. If
* disabled, the daylight saving offset is not taken into account.
*/
public static final boolean STORE_LOCAL_TIME = Utils.getProperty("h2.storeLocalTime", false);
/** /**
* System property <code>h2.syncMethod</code> (default: sync).<br /> * System property <code>h2.syncMethod</code> (default: sync).<br />
* What method to call when closing the database, on checkpoint, and on * What method to call when closing the database, on checkpoint, and on
......
...@@ -56,6 +56,11 @@ public class Constants { ...@@ -56,6 +56,11 @@ public class Constants {
*/ */
public static final int TCP_PROTOCOL_VERSION_8 = 8; public static final int TCP_PROTOCOL_VERSION_8 = 8;
/**
* The TCP protocol version number 9.
*/
public static final int TCP_PROTOCOL_VERSION_9 = 9;
/** /**
* The major version of this database. * The major version of this database.
*/ */
......
...@@ -172,6 +172,7 @@ public class Database implements DataHandler { ...@@ -172,6 +172,7 @@ public class Database implements DataHandler {
private int defaultTableType = Table.TYPE_CACHED; private int defaultTableType = Table.TYPE_CACHED;
private final DbSettings dbSettings; private final DbSettings dbSettings;
private final int reconnectCheckDelay; private final int reconnectCheckDelay;
private int logMode;
public Database(ConnectionInfo ci, String cipher) { public Database(ConnectionInfo ci, String cipher) {
String name = ci.getName(); String name = ci.getName();
...@@ -205,6 +206,7 @@ public class Database implements DataHandler { ...@@ -205,6 +206,7 @@ public class Database implements DataHandler {
this.mode = Mode.getInstance(modeName); this.mode = Mode.getInstance(modeName);
} }
this.multiVersion = ci.getProperty("MVCC", false); this.multiVersion = ci.getProperty("MVCC", false);
this.logMode = ci.getProperty("LOG", PageStore.LOG_MODE_SYNC);
boolean closeAtVmShutdown = dbSettings.dbCloseOnExit; boolean closeAtVmShutdown = dbSettings.dbCloseOnExit;
int traceLevelFile = ci.getIntProperty(SetTypes.TRACE_LEVEL_FILE, TraceSystem.DEFAULT_TRACE_LEVEL_FILE); int traceLevelFile = ci.getIntProperty(SetTypes.TRACE_LEVEL_FILE, TraceSystem.DEFAULT_TRACE_LEVEL_FILE);
int traceLevelSystemOut = ci.getIntProperty(SetTypes.TRACE_LEVEL_SYSTEM_OUT, int traceLevelSystemOut = ci.getIntProperty(SetTypes.TRACE_LEVEL_SYSTEM_OUT,
...@@ -2039,6 +2041,7 @@ public class Database implements DataHandler { ...@@ -2039,6 +2041,7 @@ public class Database implements DataHandler {
if (!readOnly && fileLockMethod == FileLock.LOCK_FS) { if (!readOnly && fileLockMethod == FileLock.LOCK_FS) {
pageStore.setLockFile(true); pageStore.setLockFile(true);
} }
pageStore.setLogMode(logMode);
pageStore.open(); pageStore.open();
} }
return pageStore; return pageStore;
...@@ -2267,6 +2270,7 @@ public class Database implements DataHandler { ...@@ -2267,6 +2270,7 @@ public class Database implements DataHandler {
// disabling a dangerous mode // disabling a dangerous mode
trace.error(null, "log {0}", log); trace.error(null, "log {0}", log);
} }
this.logMode = log;
pageStore.setLogMode(log); pageStore.setLogMode(log);
} }
} }
......
...@@ -94,7 +94,7 @@ public class SessionRemote extends SessionWithState implements DataHandler { ...@@ -94,7 +94,7 @@ public class SessionRemote extends SessionWithState implements DataHandler {
trans.setSSL(ci.isSSL()); trans.setSSL(ci.isSSL());
trans.init(); trans.init();
trans.writeInt(Constants.TCP_PROTOCOL_VERSION_6); trans.writeInt(Constants.TCP_PROTOCOL_VERSION_6);
trans.writeInt(Constants.TCP_PROTOCOL_VERSION_8); trans.writeInt(Constants.TCP_PROTOCOL_VERSION_9);
trans.writeString(db); trans.writeString(db);
trans.writeString(ci.getOriginalURL()); trans.writeString(ci.getOriginalURL());
trans.writeString(ci.getUserName()); trans.writeString(ci.getUserName());
......
...@@ -71,12 +71,12 @@ public class TcpServerThread implements Runnable { ...@@ -71,12 +71,12 @@ public class TcpServerThread implements Runnable {
int minClientVersion = transfer.readInt(); int minClientVersion = transfer.readInt();
if (minClientVersion < Constants.TCP_PROTOCOL_VERSION_6) { if (minClientVersion < Constants.TCP_PROTOCOL_VERSION_6) {
throw DbException.get(ErrorCode.DRIVER_VERSION_ERROR_2, "" + clientVersion, "" + Constants.TCP_PROTOCOL_VERSION_6); throw DbException.get(ErrorCode.DRIVER_VERSION_ERROR_2, "" + clientVersion, "" + Constants.TCP_PROTOCOL_VERSION_6);
} else if (minClientVersion > Constants.TCP_PROTOCOL_VERSION_8) { } else if (minClientVersion > Constants.TCP_PROTOCOL_VERSION_9) {
throw DbException.get(ErrorCode.DRIVER_VERSION_ERROR_2, "" + clientVersion, "" + Constants.TCP_PROTOCOL_VERSION_8); throw DbException.get(ErrorCode.DRIVER_VERSION_ERROR_2, "" + clientVersion, "" + Constants.TCP_PROTOCOL_VERSION_8);
} }
int maxClientVersion = transfer.readInt(); int maxClientVersion = transfer.readInt();
if (maxClientVersion >= Constants.TCP_PROTOCOL_VERSION_8) { if (maxClientVersion >= Constants.TCP_PROTOCOL_VERSION_9) {
clientVersion = Constants.TCP_PROTOCOL_VERSION_8; clientVersion = Constants.TCP_PROTOCOL_VERSION_9;
} else { } else {
clientVersion = minClientVersion; clientVersion = minClientVersion;
} }
......
...@@ -75,6 +75,9 @@ public class Data { ...@@ -75,6 +75,9 @@ public class Data {
private static final int LONG_NEG = 67; private static final int LONG_NEG = 67;
private static final int STRING_0_31 = 68; private static final int STRING_0_31 = 68;
private static final int BYTES_0_31 = 100; private static final int BYTES_0_31 = 100;
private static final int LOCAL_TIME = 132;
private static final int LOCAL_DATE = 133;
private static final int LOCAL_TIMESTAMP = 134;
private static final long MILLIS_PER_MINUTE = 1000 * 60; private static final long MILLIS_PER_MINUTE = 1000 * 60;
...@@ -466,19 +469,35 @@ public class Data { ...@@ -466,19 +469,35 @@ public class Data {
break; break;
} }
case Value.TIME: case Value.TIME:
writeByte((byte) type); if (SysProperties.STORE_LOCAL_TIME) {
writeVarLong(DateTimeUtils.getTimeLocal(v.getTimeNoCopy())); writeByte((byte) LOCAL_TIME);
writeVarLong(DateTimeUtils.getTimeLocal(v.getTimeNoCopy()));
} else {
writeByte((byte) type);
writeVarLong(DateTimeUtils.getTimeLocalWithoutDst(v.getTimeNoCopy()));
}
break; break;
case Value.DATE: { case Value.DATE: {
writeByte((byte) type); if (SysProperties.STORE_LOCAL_TIME) {
long x = DateTimeUtils.getTimeLocal(v.getDateNoCopy()); writeByte((byte) LOCAL_DATE);
writeVarLong(x / MILLIS_PER_MINUTE); long x = DateTimeUtils.getTimeLocal(v.getDateNoCopy());
writeVarLong(x / MILLIS_PER_MINUTE);
} else {
writeByte((byte) type);
long x = DateTimeUtils.getTimeLocalWithoutDst(v.getDateNoCopy());
writeVarLong(x / MILLIS_PER_MINUTE);
}
break; break;
} }
case Value.TIMESTAMP: { case Value.TIMESTAMP: {
writeByte((byte) type);
Timestamp ts = v.getTimestampNoCopy(); Timestamp ts = v.getTimestampNoCopy();
writeVarLong(DateTimeUtils.getTimeLocal(ts)); if (SysProperties.STORE_LOCAL_TIME) {
writeByte((byte) LOCAL_TIMESTAMP);
writeVarLong(DateTimeUtils.getTimeLocal(ts));
} else {
writeByte((byte) type);
writeVarLong(DateTimeUtils.getTimeLocalWithoutDst(ts));
}
writeVarInt(ts.getNanos()); writeVarInt(ts.getNanos());
break; break;
} }
...@@ -659,18 +678,30 @@ public class Data { ...@@ -659,18 +678,30 @@ public class Data {
BigInteger b = new BigInteger(buff); BigInteger b = new BigInteger(buff);
return ValueDecimal.get(new BigDecimal(b, scale)); return ValueDecimal.get(new BigDecimal(b, scale));
} }
case Value.DATE: { case LOCAL_DATE: {
long x = readVarLong() * MILLIS_PER_MINUTE; long x = readVarLong() * MILLIS_PER_MINUTE;
return ValueDate.getNoCopy(new Date(DateTimeUtils.getTimeGMT(x))); return ValueDate.getNoCopy(new Date(DateTimeUtils.getTimeGMT(x)));
} }
case Value.TIME: case Value.DATE: {
long x = readVarLong() * MILLIS_PER_MINUTE;
return ValueDate.getNoCopy(new Date(DateTimeUtils.getTimeGMTWithoutDst(x)));
}
case LOCAL_TIME:
// need to normalize the year, month and day // need to normalize the year, month and day
return ValueTime.get(new Time(DateTimeUtils.getTimeGMT(readVarLong()))); return ValueTime.get(new Time(DateTimeUtils.getTimeGMT(readVarLong())));
case Value.TIMESTAMP: { case Value.TIME:
// need to normalize the year, month and day
return ValueTime.get(new Time(DateTimeUtils.getTimeGMTWithoutDst(readVarLong())));
case LOCAL_TIMESTAMP: {
Timestamp ts = new Timestamp(DateTimeUtils.getTimeGMT(readVarLong())); Timestamp ts = new Timestamp(DateTimeUtils.getTimeGMT(readVarLong()));
ts.setNanos(readVarInt()); ts.setNanos(readVarInt());
return ValueTimestamp.getNoCopy(ts); return ValueTimestamp.getNoCopy(ts);
} }
case Value.TIMESTAMP: {
Timestamp ts = new Timestamp(DateTimeUtils.getTimeGMTWithoutDst(readVarLong()));
ts.setNanos(readVarInt());
return ValueTimestamp.getNoCopy(ts);
}
case Value.BYTES: { case Value.BYTES: {
int len = readVarInt(); int len = readVarInt();
byte[] b = Utils.newBytes(len); byte[] b = Utils.newBytes(len);
...@@ -861,14 +892,25 @@ public class Data { ...@@ -861,14 +892,25 @@ public class Data {
return 1 + getVarIntLen(scale) + getVarIntLen(bytes.length) + bytes.length; return 1 + getVarIntLen(scale) + getVarIntLen(bytes.length) + bytes.length;
} }
case Value.TIME: case Value.TIME:
return 1 + getVarLongLen(DateTimeUtils.getTimeLocal(v.getTimeNoCopy())); if (SysProperties.STORE_LOCAL_TIME) {
return 1 + getVarLongLen(DateTimeUtils.getTimeLocal(v.getTimeNoCopy()));
}
return 1 + getVarLongLen(DateTimeUtils.getTimeLocalWithoutDst(v.getTimeNoCopy()));
case Value.DATE: { case Value.DATE: {
long x = DateTimeUtils.getTimeLocal(v.getDateNoCopy()); if (SysProperties.STORE_LOCAL_TIME) {
long x = DateTimeUtils.getTimeLocal(v.getDateNoCopy());
return 1 + getVarLongLen(x / MILLIS_PER_MINUTE);
}
long x = DateTimeUtils.getTimeLocalWithoutDst(v.getDateNoCopy());
return 1 + getVarLongLen(x / MILLIS_PER_MINUTE); return 1 + getVarLongLen(x / MILLIS_PER_MINUTE);
} }
case Value.TIMESTAMP: { case Value.TIMESTAMP: {
if (SysProperties.STORE_LOCAL_TIME) {
Timestamp ts = v.getTimestampNoCopy();
return 1 + getVarLongLen(DateTimeUtils.getTimeLocal(ts)) + getVarIntLen(ts.getNanos());
}
Timestamp ts = v.getTimestampNoCopy(); Timestamp ts = v.getTimestampNoCopy();
return 1 + getVarLongLen(DateTimeUtils.getTimeLocal(ts)) + getVarIntLen(ts.getNanos()); return 1 + getVarLongLen(DateTimeUtils.getTimeLocalWithoutDst(ts)) + getVarIntLen(ts.getNanos());
} }
case Value.JAVA_OBJECT: { case Value.JAVA_OBJECT: {
byte[] b = v.getBytesNoCopy(); byte[] b = v.getBytesNoCopy();
......
...@@ -230,7 +230,7 @@ public class FileLock implements Runnable { ...@@ -230,7 +230,7 @@ public class FileLock implements Runnable {
transfer.setSocket(socket); transfer.setSocket(socket);
transfer.init(); transfer.init();
transfer.writeInt(Constants.TCP_PROTOCOL_VERSION_6); transfer.writeInt(Constants.TCP_PROTOCOL_VERSION_6);
transfer.writeInt(Constants.TCP_PROTOCOL_VERSION_8); transfer.writeInt(Constants.TCP_PROTOCOL_VERSION_9);
transfer.writeString(null); transfer.writeString(null);
transfer.writeString(null); transfer.writeString(null);
transfer.writeString(id); transfer.writeString(id);
......
...@@ -32,24 +32,30 @@ public class DateTimeUtils { ...@@ -32,24 +32,30 @@ public class DateTimeUtils {
private static final int DEFAULT_MONTH = 1; private static final int DEFAULT_MONTH = 1;
private static final int DEFAULT_DAY = 1; private static final int DEFAULT_DAY = 1;
private static final int DEFAULT_HOUR = 0; private static final int DEFAULT_HOUR = 0;
private static final int ZONE_OFFSET = Calendar.getInstance().get(Calendar.ZONE_OFFSET);
private static Calendar cachedCalendar = Calendar.getInstance(); private static int zoneOffset;
private static Calendar cachedCalendar;
private DateTimeUtils() { private DateTimeUtils() {
// utility class // utility class
} }
static {
getCalendar();
}
/** /**
* Reset the calendar, for example after changing the default timezone. * Reset the calendar, for example after changing the default timezone.
*/ */
public static void resetCalendar() { public static void resetCalendar() {
cachedCalendar = null; cachedCalendar = null;
getCalendar();
} }
private static Calendar getCalendar() { private static Calendar getCalendar() {
if (cachedCalendar == null) { if (cachedCalendar == null) {
cachedCalendar = Calendar.getInstance(); cachedCalendar = Calendar.getInstance();
zoneOffset = cachedCalendar.get(Calendar.ZONE_OFFSET);
} }
return cachedCalendar; return cachedCalendar;
} }
...@@ -425,7 +431,22 @@ public class DateTimeUtils { ...@@ -425,7 +431,22 @@ public class DateTimeUtils {
* @return the milliseconds * @return the milliseconds
*/ */
public static long getTimeLocal(java.util.Date d) { public static long getTimeLocal(java.util.Date d) {
return d.getTime() + ZONE_OFFSET; Calendar c = getCalendar();
synchronized (c) {
c.setTime(d);
return c.getTimeInMillis() + c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET);
}
}
/**
* Get the number of milliseconds since 1970-01-01 in the local timezone, but
* without daylight saving time into account.
*
* @param d the date
* @return the milliseconds
*/
public static long getTimeLocalWithoutDst(java.util.Date d) {
return d.getTime() + zoneOffset;
} }
/** /**
...@@ -436,7 +457,23 @@ public class DateTimeUtils { ...@@ -436,7 +457,23 @@ public class DateTimeUtils {
* @return the number of milliseconds in GMT * @return the number of milliseconds in GMT
*/ */
public static long getTimeGMT(long millis) { public static long getTimeGMT(long millis) {
return millis - ZONE_OFFSET; Calendar c = getCalendar();
synchronized (c) {
c.setTimeInMillis(millis);
return c.getTime().getTime() - c.get(Calendar.ZONE_OFFSET) - c.get(Calendar.DST_OFFSET);
}
}
/**
* Convert the number of milliseconds since 1970-01-01 in the local timezone
* to GMT, but
* without daylight saving time into account.
*
* @param millis the number of milliseconds in the local timezone
* @return the number of milliseconds in GMT
*/
public static long getTimeGMTWithoutDst(long millis) {
return millis - zoneOffset;
} }
/** /**
......
...@@ -322,23 +322,29 @@ public class Transfer { ...@@ -322,23 +322,29 @@ public class Transfer {
writeByte(v.getByte()); writeByte(v.getByte());
break; break;
case Value.TIME: case Value.TIME:
if (version >= Constants.TCP_PROTOCOL_VERSION_7) { if (version >= Constants.TCP_PROTOCOL_VERSION_9) {
writeLong(DateTimeUtils.getTimeLocal(v.getTimeNoCopy())); writeLong(DateTimeUtils.getTimeLocal(v.getTimeNoCopy()));
} else if (version >= Constants.TCP_PROTOCOL_VERSION_7) {
writeLong(DateTimeUtils.getTimeLocalWithoutDst(v.getTimeNoCopy()));
} else { } else {
writeLong(v.getTimeNoCopy().getTime()); writeLong(v.getTimeNoCopy().getTime());
} }
break; break;
case Value.DATE: case Value.DATE:
if (version >= Constants.TCP_PROTOCOL_VERSION_7) { if (version >= Constants.TCP_PROTOCOL_VERSION_9) {
writeLong(DateTimeUtils.getTimeLocal(v.getDateNoCopy())); writeLong(DateTimeUtils.getTimeLocal(v.getTimeNoCopy()));
} else if (version >= Constants.TCP_PROTOCOL_VERSION_7) {
writeLong(DateTimeUtils.getTimeLocalWithoutDst(v.getDateNoCopy()));
} else { } else {
writeLong(v.getDateNoCopy().getTime()); writeLong(v.getDateNoCopy().getTime());
} }
break; break;
case Value.TIMESTAMP: { case Value.TIMESTAMP: {
Timestamp ts = v.getTimestampNoCopy(); Timestamp ts = v.getTimestampNoCopy();
if (version >= Constants.TCP_PROTOCOL_VERSION_7) { if (version >= Constants.TCP_PROTOCOL_VERSION_9) {
writeLong(DateTimeUtils.getTimeLocal(ts)); writeLong(DateTimeUtils.getTimeLocal(ts));
} else if (version >= Constants.TCP_PROTOCOL_VERSION_7) {
writeLong(DateTimeUtils.getTimeLocalWithoutDst(ts));
} else { } else {
writeLong(ts.getTime()); writeLong(ts.getTime());
} }
...@@ -462,20 +468,28 @@ public class Transfer { ...@@ -462,20 +468,28 @@ public class Transfer {
case Value.BYTE: case Value.BYTE:
return ValueByte.get(readByte()); return ValueByte.get(readByte());
case Value.DATE: case Value.DATE:
if (version >= Constants.TCP_PROTOCOL_VERSION_7) { if (version >= Constants.TCP_PROTOCOL_VERSION_9) {
return ValueDate.getNoCopy(new Date(DateTimeUtils.getTimeGMT(readLong()))); return ValueDate.getNoCopy(new Date(DateTimeUtils.getTimeGMT(readLong())));
} else if (version >= Constants.TCP_PROTOCOL_VERSION_7) {
return ValueDate.getNoCopy(new Date(DateTimeUtils.getTimeGMTWithoutDst(readLong())));
} }
return ValueDate.getNoCopy(new Date(readLong())); return ValueDate.getNoCopy(new Date(readLong()));
case Value.TIME: case Value.TIME:
if (version >= Constants.TCP_PROTOCOL_VERSION_7) { if (version >= Constants.TCP_PROTOCOL_VERSION_9) {
return ValueTime.getNoCopy(new Time(DateTimeUtils.getTimeGMT(readLong()))); return ValueTime.getNoCopy(new Time(DateTimeUtils.getTimeGMT(readLong())));
} else if (version >= Constants.TCP_PROTOCOL_VERSION_7) {
return ValueTime.getNoCopy(new Time(DateTimeUtils.getTimeGMTWithoutDst(readLong())));
} }
return ValueTime.getNoCopy(new Time(readLong())); return ValueTime.getNoCopy(new Time(readLong()));
case Value.TIMESTAMP: { case Value.TIMESTAMP: {
if (version >= Constants.TCP_PROTOCOL_VERSION_7) { if (version >= Constants.TCP_PROTOCOL_VERSION_9) {
Timestamp ts = new Timestamp(DateTimeUtils.getTimeGMT(readLong())); Timestamp ts = new Timestamp(DateTimeUtils.getTimeGMT(readLong()));
ts.setNanos(readInt()); ts.setNanos(readInt());
return ValueTimestamp.getNoCopy(ts); return ValueTimestamp.getNoCopy(ts);
} else if (version >= Constants.TCP_PROTOCOL_VERSION_7) {
Timestamp ts = new Timestamp(DateTimeUtils.getTimeGMTWithoutDst(readLong()));
ts.setNanos(readInt());
return ValueTimestamp.getNoCopy(ts);
} }
Timestamp ts = new Timestamp(readLong()); Timestamp ts = new Timestamp(readLong());
ts.setNanos(readInt()); ts.setNanos(readInt());
......
...@@ -23,6 +23,7 @@ import org.h2.test.db.TestCheckpoint; ...@@ -23,6 +23,7 @@ import org.h2.test.db.TestCheckpoint;
import org.h2.test.db.TestCluster; import org.h2.test.db.TestCluster;
import org.h2.test.db.TestCompatibility; import org.h2.test.db.TestCompatibility;
import org.h2.test.db.TestCsv; import org.h2.test.db.TestCsv;
import org.h2.test.db.TestDateStorage;
import org.h2.test.db.TestDeadlock; import org.h2.test.db.TestDeadlock;
import org.h2.test.db.TestEncryptedDb; import org.h2.test.db.TestEncryptedDb;
import org.h2.test.db.TestExclusive; import org.h2.test.db.TestExclusive;
...@@ -550,6 +551,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -550,6 +551,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestCluster().runTest(this); new TestCluster().runTest(this);
new TestCompatibility().runTest(this); new TestCompatibility().runTest(this);
new TestCsv().runTest(this); new TestCsv().runTest(this);
new TestDateStorage().runTest(this);
new TestDeadlock().runTest(this); new TestDeadlock().runTest(this);
new TestEncryptedDb().runTest(this); new TestEncryptedDb().runTest(this);
new TestExclusive().runTest(this); new TestExclusive().runTest(this);
......
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.db;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.TimeZone;
import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
import org.h2.test.TestBase;
import org.h2.test.unit.TestDate;
import org.h2.util.DateTimeUtils;
import org.h2.value.Value;
/**
* Tests the date transfer and storage.
*/
public class TestDateStorage extends TestBase {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
System.setProperty("h2.storeLocalTime", "true");
TestBase.createCaller().init().test();
}
public void test() throws SQLException {
deleteDb("date");
testMoveDatabaseToAnotherTimezone();
testAllTimeZones();
testCurrentTimeZone();
}
private void testMoveDatabaseToAnotherTimezone() throws SQLException {
if (config.memory) {
return;
}
if (!SysProperties.STORE_LOCAL_TIME) {
return;
}
String db = "date;LOG=0;FILE_LOCK=NO";
Connection conn = getConnection(db);
Statement stat;
stat = conn.createStatement();
stat.execute("create table date_list(tz varchar, t varchar, ts timestamp)");
conn.close();
TimeZone defaultTimeZone = TimeZone.getDefault();
ArrayList<TimeZone> distinct = TestDate.getDistinctTimeZones();
try {
for (TimeZone tz : distinct) {
TimeZone.setDefault(tz);
DateTimeUtils.resetCalendar();
conn = getConnection(db);
PreparedStatement prep = conn.prepareStatement("insert into date_list values(?, ?, ?)");
prep.setString(1, tz.getID());
for (int m = 1; m < 10; m++) {
String s = "2000-0" + m + "-01 15:00:00";
prep.setString(2, s);
prep.setTimestamp(3, Timestamp.valueOf(s));
prep.execute();
}
conn.close();
}
printTime("inserted");
for (TimeZone target : distinct) {
if ("Pacific/Kiritimati".equals(target)) {
// there is a problem with this time zone, but it seems
// unrelated to this database (possibly wrong timezone
// information?)
continue;
}
TimeZone.setDefault(target);
DateTimeUtils.resetCalendar();
conn = getConnection(db);
stat = conn.createStatement();
ResultSet rs = stat.executeQuery("select * from date_list order by t");
while (rs.next()) {
String source = rs.getString(1);
String a = rs.getString(2);
String b = rs.getString(3);
b = b.substring(0, a.length());
if (!a.equals(b)) {
assertEquals(source + ">" + target, a, b);
}
}
conn.close();
}
} finally {
TimeZone.setDefault(defaultTimeZone);
DateTimeUtils.resetCalendar();
}
printTime("done");
conn = getConnection(db);
stat = conn.createStatement();
stat.execute("drop table date_list");
conn.close();
}
private static void testCurrentTimeZone() {
for (int year = 1970; year < 2050; year += 3) {
for (int month = 1; month <= 12; month++) {
for (int day = 1; day < 29; day++) {
for (int hour = 0; hour < 24; hour++) {
test(year, month, day, hour);
}
}
}
}
}
private static void test(int year, int month, int day, int hour) {
DateTimeUtils.parseDateTime(year + "-" + month + "-" + day + " " + hour + ":00:00", Value.TIMESTAMP, ErrorCode.TIMESTAMP_CONSTANT_2);
}
private void testAllTimeZones() throws SQLException {
Connection conn = getConnection("date");
TimeZone defaultTimeZone = TimeZone.getDefault();
PreparedStatement prep = conn.prepareStatement("CALL CAST(? AS DATE)");
try {
String[] ids = TimeZone.getAvailableIDs();
for (int i = 0; i < ids.length; i++) {
TimeZone.setDefault(TimeZone.getTimeZone(ids[i]));
DateTimeUtils.resetCalendar();
for (int d = 101; d < 129; d++) {
test(prep, d);
}
}
} finally {
TimeZone.setDefault(defaultTimeZone);
DateTimeUtils.resetCalendar();
}
conn.close();
deleteDb("date");
}
private void test(PreparedStatement prep, int d) throws SQLException {
String s = "2040-10-" + ("" + d).substring(1);
// some dates don't work in some versions of Java
// java.sql.Date date = java.sql.Date.valueOf(s);
// long time = date.getTime();
// int plus = 0;
// while (true) {
// date = new java.sql.Date(time);
// String x = date.toString();
// if (x.equals(s)) {
// break;
// }
// time += 1000;
// plus += 1000;
// }
prep.setString(1, s);
ResultSet rs = prep.executeQuery();
rs.next();
String t = rs.getString(1);
assertEquals(s, t);
}
}
...@@ -6,25 +6,26 @@ ...@@ -6,25 +6,26 @@
*/ */
package org.h2.test.unit; package org.h2.test.unit;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.TimeZone; import java.util.TimeZone;
import org.h2.constant.SysProperties;
import org.h2.constant.ErrorCode; import org.h2.store.Data;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.util.DateTimeUtils; import org.h2.util.DateTimeUtils;
import org.h2.util.New;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueDate; import org.h2.value.ValueDate;
import org.h2.value.ValueNull;
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;
/** /**
* Tests the data parsing. The problem is that some dates are not allowed * Tests the date parsing. The problem is that some dates are not allowed
* because of the summer time change. Most countries change at 2 o'clock in the * because of the summer time change. Most countries change at 2 o'clock in the
* morning to 3 o'clock, but some (for example Chile) change at midnight. * morning to 3 o'clock, but some (for example Chile) change at midnight.
* Non-lenient parsing would not work in this case. * Non-lenient parsing would not work in this case.
...@@ -37,13 +38,89 @@ public class TestDate extends TestBase { ...@@ -37,13 +38,89 @@ public class TestDate extends TestBase {
* @param a ignored * @param a ignored
*/ */
public static void main(String... a) throws Exception { public static void main(String... a) throws Exception {
System.setProperty("h2.storeLocalTime", "true");
TestBase.createCaller().init().test(); TestBase.createCaller().init().test();
} }
public void test() throws SQLException { public void test() throws SQLException {
testTimeOperationsAcrossTimeZones();
testDateTimeUtils(); testDateTimeUtils();
testAllTimeZones(); }
testCurrentTimeZone();
private void testTimeOperationsAcrossTimeZones() {
if (!SysProperties.STORE_LOCAL_TIME) {
return;
}
TimeZone defaultTimeZone = TimeZone.getDefault();
ArrayList<TimeZone> distinct = TestDate.getDistinctTimeZones();
Data d = Data.create(null, 10240);
try {
for (TimeZone tz : distinct) {
TimeZone.setDefault(tz);
DateTimeUtils.resetCalendar();
d.reset();
for (int m = 1; m <= 12; m++) {
for (int h = 0; h <= 23; h++) {
if (h == 0 || h == 2 || h == 3) {
// those hours may not exist for all days in all
// timezones because of daylight saving
continue;
}
String s = "2000-" + (m < 10 ? "0" + m : m) + "-01 " + (h < 10 ? "0" + h : h) + ":00:00.0";
d.writeValue(ValueString.get(s));
d.writeValue(ValueTimestamp.get(Timestamp.valueOf(s)));
}
}
d.writeValue(ValueNull.INSTANCE);
d.reset();
for (TimeZone target : distinct) {
if ("Pacific/Kiritimati".equals(target)) {
// there is a problem with this time zone, but it seems
// unrelated to this database (possibly wrong timezone
// information?)
continue;
}
TimeZone.setDefault(target);
DateTimeUtils.resetCalendar();
while (true) {
Value v = d.readValue();
if (v == ValueNull.INSTANCE) {
break;
}
String a = v.getString();
String b = d.readValue().getString();
if (!a.equals(b)) {
assertEquals("source: " + tz.getID() + " target: " + target.getID(), a, b);
}
}
}
}
} finally {
TimeZone.setDefault(defaultTimeZone);
DateTimeUtils.resetCalendar();
}
}
/**
* Get the list of timezones with distinct rules.
*
* @return the list
*/
public static ArrayList<TimeZone> getDistinctTimeZones() {
ArrayList<TimeZone> distinct = New.arrayList();
for (String id : TimeZone.getAvailableIDs()) {
TimeZone t = TimeZone.getTimeZone(id);
for (TimeZone d : distinct) {
if (t.hasSameRules(d)) {
t = null;
break;
}
}
if (t != null) {
distinct.add(t);
}
}
return distinct;
} }
private void testDateTimeUtils() { private void testDateTimeUtils() {
...@@ -70,63 +147,4 @@ public class TestDate extends TestBase { ...@@ -70,63 +147,4 @@ public class TestDate extends TestBase {
assertEquals("19999-08-07 13:14:15.16", ValueTimestamp.get(ts2a).getString()); assertEquals("19999-08-07 13:14:15.16", ValueTimestamp.get(ts2a).getString());
} }
private static void testCurrentTimeZone() {
for (int year = 1970; year < 2050; year += 3) {
for (int month = 1; month <= 12; month++) {
for (int day = 1; day < 29; day++) {
for (int hour = 0; hour < 24; hour++) {
test(year, month, day, hour);
}
}
}
}
}
private static void test(int year, int month, int day, int hour) {
DateTimeUtils.parseDateTime(year + "-" + month + "-" + day + " " + hour + ":00:00", Value.TIMESTAMP, ErrorCode.TIMESTAMP_CONSTANT_2);
}
private void testAllTimeZones() throws SQLException {
Connection conn = getConnection("date");
TimeZone defaultTimeZone = TimeZone.getDefault();
PreparedStatement prep = conn.prepareStatement("CALL CAST(? AS DATE)");
try {
String[] ids = TimeZone.getAvailableIDs();
for (int i = 0; i < ids.length; i++) {
TimeZone.setDefault(TimeZone.getTimeZone(ids[i]));
DateTimeUtils.resetCalendar();
for (int d = 101; d < 129; d++) {
test(prep, d);
}
}
} finally {
TimeZone.setDefault(defaultTimeZone);
DateTimeUtils.resetCalendar();
}
conn.close();
deleteDb("date");
}
private void test(PreparedStatement prep, int d) throws SQLException {
String s = "2040-10-" + ("" + d).substring(1);
// some dates don't work in some versions of Java
// java.sql.Date date = java.sql.Date.valueOf(s);
// long time = date.getTime();
// int plus = 0;
// while (true) {
// date = new java.sql.Date(time);
// String x = date.toString();
// if (x.equals(s)) {
// break;
// }
// time += 1000;
// plus += 1000;
// }
prep.setString(1, s);
ResultSet rs = prep.executeQuery();
rs.next();
String t = rs.getString(1);
assertEquals(s, t);
}
} }
...@@ -684,5 +684,5 @@ diagnostics checkout somewhat icu delegation classifications karlsson applet ...@@ -684,5 +684,5 @@ diagnostics checkout somewhat icu delegation classifications karlsson applet
litailang springsource eccn springframework spr growth teams gigabytes europe litailang springsource eccn springframework spr growth teams gigabytes europe
mcleod decade experience travel willing scjp himself routinely tsi retrieving mcleod decade experience travel willing scjp himself routinely tsi retrieving
multiplied ross judson closeable watcher enqueued referent refs watch tracked multiplied ross judson closeable watcher enqueued referent refs watch tracked
preserving disallowed restrictive preserving disallowed restrictive dst regions kiritimati
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论