【问题标题】:Date one day backwards after select from MySQL DB从 MySQL DB 中选择后的日期向后一天
【发布时间】:2019-07-07 01:42:21
【问题描述】:

我有时区相关的问题。

我在 JAVA 和 MySql 数据库中有一个程序。 我不使用任何 ORM,但我有自定义 jdbc 库,它使用 mysql 连接器 java v 6.0.4

我将设置作为连接字符串的一部分发送为:

 serverTimezone - America/Chicago
 useTimezone - true
 useJDBCCompliantTimezoneShift - false
 useLegacyDatetimeCode - false

我尝试从数据库中选择一个日期,例如2019-02-13

数据库位于美国/东部时区,运行 java 程序的服务器位于美国/芝加哥时区。

无法更改任何服务器的位置/时区。

在 Java 中,我得到一个倒退一天的日期 (2019-02-12)。 问题是由于时间戳引起的 -

1550034000 在美国/东部是 2019-02-13 00:00:00

但是

1550034000 在美国/芝加哥是 2019-02-12 23:00:00

所以结果我有java.sql.Date 对象,日期为2019-02-12。 添加时区偏移量没有任何帮助,因为时间信息已从日期中截断。

您能否提出一些解决方案,说明如何在不改变时区的情况下获得正确的日期?

编辑:我正在使用serverTimezone 设置,但我不确定该值是否应该是数据库正在使用的时区,或者它只是覆盖运行应用程序的 JVM/服务器的时区。

【问题讨论】:

  • 显示代码(插入和选择)?显示表结构,明确日期对象的类型。
  • Check this out 因为它可以给你一些线索。
  • 它可能需要修改您的自定义 JDBC 库,但如果列数据类型是 date,则检索到 LocalDate,而不是 java.sql.Date。后者存在设计问题(正如您所遇到的,但不限于此)并且早已过时。 LocalDate 来自 java.time, the modern Java date and time API。现代 API 更易于使用。
  • @danblack 我能够通过简单的选择来模拟问题 - SELECT CURDATE() as 'date';
  • 您是否能够修改您的自定义 JDBC 库以返回 java.time.LocalDate 而不是(或除了)java.sql.Timestamp 吗?

标签: java mysql date jdbc timezone


【解决方案1】:

首先,我了解到您的情况不能,但对于其他读者,我想说一般建议是在 UTC 中运行所有内容,至少当您跨越多个时区时。所以这将是解决您的问题的最佳方法。

其次,正如我和 Gord Thompson 在 cmets 中提到的,第二个最佳解决方案是将日期处理为 LocalDate,而不是 java.sql.Date。虽然后者只是假装没有时间,但它确实存在设计问题,难以解决您的问题。 LocalDate 真的一个没有时间和时区的日期,所以应该是一个更安全的赌注(除了数据库驱动程序与LocalDate 之间的转换不正确)听说过;我一直祈祷;再次在 UTC 中运行所有内容也将消除这些错误)。 编辑:假设您可以修改自定义 JDBC 库,以下是如何从 ResultSet 获取 LocalDate

    LocalDate correctDateDirectlyFromDatabase
            = yourResultSet.getObject("yourDateColumn", LocalDate.class);

它至少需要 JDBC 4.2,你可能有。

如果您无法获得以上任何一项,那么可以通过以下方法来修正您从数据库中获得的不正确的Date。这有点小技巧,但会奏效。

import java.sql.Date;

// …

    // Modern ID of the time zone previously known as US/Eastern
    ZoneId datebaseTimeZone = ZoneId.of("America/New_York");

    Date dateFromDatabase = new Date(TimeUnit.SECONDS.toMillis(1_550_034_000));
    System.out.println("Date as retrieved from database (or pretending): " + dateFromDatabase);

    long epochMillis = dateFromDatabase.getTime();
    ZonedDateTime dateTime = Instant.ofEpochMilli(epochMillis)
            .atZone(datebaseTimeZone);
    LocalDate realDate = dateTime.toLocalDate();
    // Sanity check
    if (! realDate.atStartOfDay(datebaseTimeZone).equals(dateTime)) {
        throw new IllegalStateException("Failed to convert date correctly from " + datebaseTimeZone + " time zone");
    }

    System.out.println("Date is " + realDate);

当我在美国/芝加哥时区运行它时,它打印:

Date as retrieved from database (or pretending): 2019-02-12
Date is 2019-02-13

我已经尝试在其他时区运行它。在某些时区,第一行打印2019-02-12,在其他时区打印2019-02-13。最后一行在我尝试过的所有时区打印2019-02-13

现在我给了你一个LocalDate。很好,这是您在进一步处理中应该使用的。如果您需要 java.sql.Date 用于另一个您暂时不想更改的旧 API,请通过以下方式转换回 正确 java.sql.Date

    Date oldfashionedJavaSqlDate = Date.valueOf(realDate);
    System.out.println("Date converted back to " + oldfashionedJavaSqlDate);

日期转换回 2019-02-13

当我说正确时,它要求没有人篡改你的 JVM 的默认时区,这对于任何运行在 JVM 中的程序来说都很容易做到。

链接: Oracle tutorial: Date Time 解释如何使用 java.time。

【讨论】:

  • 我想到的另一种方法是将SELECT CAST('1970-01-01 00:00:00' AS DATETIME) 作为Timestamp 检索,然后使用getTime() 确定用于ZoneOffset 的(毫秒)秒。它不需要事先了解服务器配置,尽管 DST/Summer_Time 可能会给事情带来麻烦。
  • 是的,我担心夏季时间/夏令时会使这种方法过于冒险。当然,如果数据库时区未知或可以更改,它会更加灵活。
  • 按照您的建议,我创建了将 java.sql.date 从 db 转换为 LocalDate 的函数,后来我使用了 java.time API。或者有什么方法可以告诉驱动程序使用 java.time 自动转换日期结果?后来我创建了比较这两个时区的偏移量的函数,并在它们之间添加了以秒为单位的偏移量以获得所需的结果,但决定使用您的解决方案进行转换,因为它更优雅:)
  • 是的,@peter.cambal,如果您的 JDBC 驱动程序符合 JDBC 4.2 或更高版本,它会直接给您一个LocalDate。看看我的小编辑。
  • @peter.cambal - 您可能需要将 MySQL 连接器/J 升级到比 6.0.4 更新的版本才能使 .getObject("yourDateColumn", LocalDate.class) 可靠地工作。 MySQL Connector/J 有(和still has)一些关于java.time 对象的怪癖。
【解决方案2】:

我正在使用serverTimezone 设置,但我不确定该值是否应该是数据库正在使用的时区,或者它只是覆盖运行应用程序的 JVM/服务器的时区。

serverTimezone=America/Chicago 的意思是“解释来自服务器的结果,就像服务器使用 America/Chicago 时区一样,而不管服务器配置为使用的默认时区如何”。因此,如果您在连接字符串中使用该设置,即使服务器的默认时区显然是 America/New_York,您也会将 Timestamp 值转换为 America/Chicago

但是,在采用这种方法之前,您需要确认服务器确实在使用America/New_York(可能会在东部标准时间和东部夏令时间之间来回切换),而不是像 UTC 这样的固定偏移量- 5(将始终保持在“东部标准时间”)。

【讨论】:

  • 感谢您的回答。这有助于我更多地了解这个问题
猜你喜欢
  • 1970-01-01
  • 2023-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-15
  • 1970-01-01
相关资源
最近更新 更多