MySQL客户端与服务器时区

时间:2017-04-22 14:31:01

标签: java mysql datetime jdbc timezone

我遇到的问题是MySQL存储的日期时间值与客户端传递的不同。服务器以UTC运行,客户端在不同的时区运行。不管怎样,MySQL似乎在客户端和服务器时区之间转换日期时间值,即使SQL类型DATETIMETIMESTAMP都没有时区。到目前为止,我测试的其他数据库都没有这种行为。

以下代码可用于重现该问题。当服务器以UTC运行时,代码仅在客户端也以UTC运行时才有效。

try (Connection connection = this.dataSource.getConnection();
     PreparedStatement preparedStatement = connection.prepareStatement(
             "SELECT ? = DATE '1988-12-25', ? = TIME '15:09:02', ? = TIMESTAMP '1980-01-01 23:03:20'")) {
  preparedStatement.setDate(1, java.sql.Date.valueOf("1988-12-25"));
  preparedStatement.setTime(2, java.sql.Time.valueOf("15:09:02"));
  preparedStatement.setTimestamp(3, java.sql.Timestamp.valueOf("1980-01-01 23:03:20"));
  try (ResultSet resultSet = preparedStatement.executeQuery()) {
    while (resultSet.next()) {
      System.out.println(resultSet.getBoolean(1));
      System.out.println(resultSet.getBoolean(2));
      System.out.println(resultSet.getBoolean(3));
    }
  }

}

我正在使用

  • MySQL 5.7.14
  • mysql-connector-java 6.0.5
  • Oracle Java 1.8.0_131

我的JDBC网址只是jdbc:mysql://host:port/database

修改

我的理由为什么时区不应该在这里发挥作用,并且不应该发生时区转换是双重的。首先在SQL级TIMESTAMP上是TIMESTAMP WITHOUT TIME ZONE的别名,强烈暗示与TIMESTAMP WITH TIME ZONE不同,它的值没有时区。换句话说,值不是时间上的瞬间,而是本地日期时间值。

其次,java.sql.Timestamp在JVM时区只是作为java.util.Date的子类的人工制品。 (我知道java.util.Date没有时区)。 java.sql.Timestamp的Javadoc非常清楚表明关系仅用于实现目的。

我觉得Java SE 8 / JDBC 4.2 java.sql.Timestamp映射到java.time.LocalDateTime而不是java.time.ZonedDateTimejava.time.OffsetDateTime这一事实证实了这两个断言。< / p>

编辑2

我不明白为什么TIMESTAMP值受时区转换的影响。与TIMESTAMP WITH TIME ZOONE不同,这些是“本地”值,并且没有关联的时区,因此不应对其应用时区转换。

2 个答案:

答案 0 :(得分:3)

我同意评论MySQL JDBC下的时区处理可能令人困惑,但在这种情况下......

  

MySQL存储的日期时间值与客户端传递的日期时间值不同

......不太正确。它在服务器时区的上下文中解释和/或显示相同日期时间值字符串表示

首先,您需要了解java.sql.Timestamp#valueOf在运行Java虚拟机的本地时区中创建Timestamp值。因此,对于我的机器是在山区时间&#34;在加拿大(标准时间为UTC-7):

System.out.printf("Local (client) timezone is %s%n", TimeZone.getDefault().getID());

java.sql.Timestamp tStamp = java.sql.Timestamp.valueOf("1980-01-01 23:03:20");

SimpleDateFormat sdfLocal = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
SimpleDateFormat sdfUTC = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
sdfUTC.setCalendar(Calendar.getInstance(TimeZone.getTimeZone("UTC")));

System.out.println("tStamp = java.sql.Timestamp.valueOf(\"1980-01-01 23:03:20\")");
System.out.printf("                        ... which is %s%n", sdfLocal.format(tStamp));
System.out.printf("                        ... which is %s%n", sdfUTC.format(tStamp));

打印

Local (client) timezone is America/Edmonton
tStamp = java.sql.Timestamp.valueOf("1980-01-01 23:03:20")
                        ... which is 1980-01-01 23:03:20 MST
                        ... which is 1980-01-02 06:03:20 UTC

现在,当我们将Timestamp值传递给PreparedStatement并将其发送到使用UTC时区的MySQL服务器时,服务器会解释并显示该MySQL TIMESTAMP的字符串文字表示形式为UTC:

String connUrl = "jdbc:mysql://localhost/mydb";
try (Connection conn = DriverManager.getConnection(connUrl, myUid, myPwd)) {
    String sql = "SELECT CAST((TIMESTAMP ?) AS CHAR) AS foo";
    try (PreparedStatement ps = conn.prepareStatement(sql)) {
        ps.setTimestamp(1, tStamp);
        try (ResultSet rs = ps.executeQuery()) {
            rs.next();
            System.out.printf("String representation of TIMESTAMP value at server: %s%n", 
                    rs.getString(1));
        }
    }
}
制造

String representation of TIMESTAMP value at server: 1980-01-02 06:03:20

如果MySQL服务器在多伦多时间(标准时间为UTC-5)下运行,则输出将是......

String representation of TIMESTAMP value at server: 1980-01-02 01:03:20

...不是因为MySQL TIMESTAMP值不同,而是因为MySQL服务器时区上下文中该值字符串表示不同。

答案 1 :(得分:0)

我想这就像Gord Thompson和spencer7593所述,服务器的时区和客户端连接字符串的时区不同,必须在时区之间进行转换。

我在阅读您的编辑时也学到了一些东西,因为java.sql.Timestamp不包含时区。

正如spencer7593所述:&#34;服务器正在解释服务器时区&#34;中的文字。

我的两种方法是:

  1. 使用连接字符串中的时区连接到服务器(UTC)(例如&amp; serverTimezone = Europe / Zurich)。

  2. 或者将带有您的本地时区(Calendar.getInstance())的日历实例传递给PreparedStatement.setTimestamp