【发布时间】:2019-02-03 09:24:57
【问题描述】:
假设满足以下条件:
- 两台主机(运行 JVM 和运行 MySQL 的主机)的硬件时钟都以 UTC 运行,并且这些时钟是同步的(例如:使用 NTP) .
-
MySQL 和 JVM 的时区不同(在我的示例中,MySQL 在
Europe/Moscow(+03:00) 时区和 JVM 正在使用GMT+14:00)。
在这种情况下,每天都会有一段时间,当前日期表示(yyyy-MM-dd 格式)将不同于 Java 和数据库的角度(数据库日期会滞后)。
我使用的是 MySQL Connector/J 8.0,默认情况下可以识别时区(与 5.1.46 不同),因此只需将 serverTimezone 连接属性设置为 @ 就足够了987654326@,以防驱动程序无法解析@@time_zone 和/或@@system_time_zone。
现在,考虑以下场景:
- 客户端将当前时间戳(作为
java.sql.Timestamp的实例)存储到数据库中。 - 然后,同一客户端仅将上述时间戳的日期部分(作为
java.sql.Date)读回 JVM。
预计读取的日期分数将转换回到 JVM 的时区(这是我观察到的 Oracle、PostgreSQL 和 MS SQL Server),i。 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. e. 与其他主流数据库不同,MySQL驱动可能会返回一个昨天的日期:如果我存储一个SQL TIMESTAMP并读回一个SQL DATE,日期部分将有数据库的时区,而不是 JVM。
我错过了什么吗?
如何配置 MySQL Connector/J 8.0 使其与其他 JDBC 驱动程序的行为一致?
【问题讨论】:
-
如果出于诊断目的,您使用 SimpleDateFormat 将两个值格式化为日期和时间(而不仅仅是日期),它们是否相隔 11 小时?另外,您是否尝试过 GMT+10:00 而不是 GMT+14:00 只是为了排除边缘情况? (GMT+14:00 有点模糊。我尝试将我的 MySQL 服务器时区设置为“+14:00”,但它不允许我这样做。)
-
rset.getDate(1)似乎有点奇怪。使用各种DEFAULT_TIME_ZONE值,我能够与rset.getTimestamp(1)进行适当的往返,但rset.getDate(1)给了我不同的结果。
标签: java mysql date jdbc timezone