假定满足以下条件:
Europe/Moscow
(+03:00
)时区,并且 JVM 正在使用GMT+14:00
)。在这种情况下,每天会有一段时间,当前日期表示(yyyy-MM-dd
格式)与Java和数据库角度(数据库日期将滞后)不同。
我使用的是 MySQL Connector / J 8.0,默认情况下它是时区感知的(与5.1.46相反),因此仅设置serverTimezone
连接属性就足够了到Europe/Moscow
,以防驱动程序无法解析@@time_zone
和/或@@system_time_zone
。
现在,请考虑以下情形:
java.sql.Timestamp
的实例)存储到数据库中。java.sql.Date
)读回到JVM。预计读取的日期分数会转换回到JVM的时区(这是我对 Oracle , PostgreSQL < / em>和 MS SQL Server ), e。以下测试应该成功:
import static java.lang.String.format;
import static java.lang.System.currentTimeMillis;
import static org.assertj.core.api.Assertions.assertThat;
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Properties;
import java.util.TimeZone;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
public final class TimeZoneTestPartial {
private static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getTimeZone("GMT+14:00");
private static final String URL = "jdbc:mysql://localhost:3306/sandbox";
private static final Properties CONNECTION_INFO = new Properties();
static {
CONNECTION_INFO.setProperty("user", "...");
CONNECTION_INFO.setProperty("password", "...");
CONNECTION_INFO.setProperty("useSSL", "false");
CONNECTION_INFO.setProperty("serverTimezone", "Europe/Moscow");
}
@BeforeClass
public static void setUpOnce() {
TimeZone.setDefault(DEFAULT_TIME_ZONE);
}
@Test
@SuppressWarnings("static-method")
public void testDate() throws SQLException {
try (final Connection conn = DriverManager.getConnection(URL, CONNECTION_INFO)) {
try (final Statement stmt = conn.createStatement()) {
final String tableName = "date_with_time_zone_test";
try {
stmt.executeUpdate(format("drop table %s",
tableName));
} catch (@SuppressWarnings("unused") final SQLException ignored) {
// ignore
}
stmt.executeUpdate(format("create table %s (value %s not null)",
tableName,
getTimestampType()));
final long clientTimeMillis = currentTimeMillis();
try (final PreparedStatement pstmt = conn.prepareStatement(format("insert into %s (value) values (?)",
tableName))) {
pstmt.setTimestamp(1, new Timestamp(clientTimeMillis));
pstmt.executeUpdate();
}
final String selectSql = format("select * from %s", tableName);
try (final ResultSet rset = stmt.executeQuery(selectSql)) {
assertThat(rset.next()).isTrue();
final Date date = rset.getDate(1);
assertThat(date).isNotNull();
assertThat(rset.next()).isFalse();
final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
format.setTimeZone(DEFAULT_TIME_ZONE);
assertThat(format.format(date))
.as("date fraction from the database")
.isEqualTo(format.format(new java.util.Date(clientTimeMillis)));
}
stmt.executeUpdate(format("drop table %s",
tableName));
}
}
}
private static String getTimestampType() {
return "datetime"; //"timestamp";
}
}
实际上, MySQL 的测试失败-i。例如,与其他主流数据库不同, MySQL 驱动程序可能会返回昨天的日期:如果我存储了SQL TIMESTAMP
并读回了SQL DATE
,则日期部分将具有时间数据库的区域,而不是JVM的区域。
我在这里想念东西吗?
如何配置 MySQL Connector / J 8.0,使其与其他JDBC驱动程序保持一致?
答案 0 :(得分:1)
SQL TIMESTAMP和DATE类型不包含时区信息。
类似地,java.util.Date,java.sql.Date和java.sql.Timestamp类型不包含时区信息。 java.util.Date和java.sql.Timestamp包含自1970年1月1日00:00:00 UTC以来的毫秒数。
他们的toString
方法使用了系统默认时区,但这并不影响它们的值。
由于时区信息在日期或时间戳数据中没有意义,因此您不应该在比较中使用它。
请勿使用字符串形式比较这些值。完全不要使用SimpleDateFormat。相反,请使用不受时区影响的比较来比较每个代表的实际,有意义的数据。
最简单的方法是将数据转换为模棱两可的LocalDate和LocalDateTime类型:
LocalDateTime localClientTime = new Timestamp(clientTimeMillis).toLocalDateTime();
assertThat(date.toLocalDate())
.as("date fraction from the database")
.isEqualTo(localClientTime.toLocalDate());