【问题标题】:Incorrect date fetched from Postgres从 Postgres 获取的日期不正确
【发布时间】:2018-07-02 09:25:46
【问题描述】:

我有一个名为 deadline_date 的字段,类型为 timestamp with time zone,值为 2018-05-03 19:00:00-05.

select deadline_date from invoice_details where file_name='BHDHSB.pdf';

     deadline_date      
------------------------
 2018-05-03 19:00:00-05
(1 row)

但是在从我的 java 代码中查询时,它返回的值是 2018-05-04 05:30:00+05:30

我认为它正在根据我的本地时区转换日期。

有什么方法可以获取存储在表中的值吗?

我正在为 Postgres 使用以下 maven 依赖项

<dependency>
     <groupId>org.postgresql</groupId>
     <artifactId>postgresql</artifactId>
     <version>9.4.1212</version>
</dependency>

Test.java

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class Test {
    public static Connection connection;
    public void createConnection()
    {
        try {
            if (connection==null) {
                Class.forName("org.postgresql.Driver");
                connection = DriverManager.getConnection("jdbc:postgresql://localhost:5432/abc", "abc", "Welcome");
                connection.setSchema("iiv_location");
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println(e.getClass().getName() + ": " + e.getMessage());
        }
    }

   public static void main(String[] args) {
        try {
            test object = new test();
            object.createConnection();
            Statement statement = connection.createStatement();
            ResultSet resultSet = statement.executeQuery("select iiv_prescan_invoice_details.deadline_date from iiv_prescan_invoice_details");
            while (resultSet.next()) {
                System.out.println(resultSet.getString("deadline_date"));
           }
        } catch (Exception e) {
            e.printStackTrace();
        }
   }
}

【问题讨论】:

  • 向我们展示您的 Java 代码
  • 是的,这将是一个时区问题...向我们展示代码...
  • BTW,值是一样的,只是表示在不同的时区...
  • @UsagiMiyamoto 是的。值是一样的。但我的要求是获取存储在表中的值。我在问题中添加了我的代码。请检查。
  • @a_horse_with_no_name 添加了代码。请检查

标签: java postgresql postgresql-9.4 jdbc-postgres


【解决方案1】:

不要使用getString() 来检索时间戳(实际上:将它用于字符串。不适用于数字、日期和时间戳)。

如果您正在处理可识别时区的时间戳,则应在 Java 中使用 OffsetDateTime

Postgres JDBC 驱动程序通过getObject() 方法直接支持这一点:

OffsetDateTime deadline = resultSet.getObject("deadline_date", OffsetDateTime.class);

【讨论】:

  • 请注意:这个OffsetDateTime 以及另一个答案中的LocalTDateTime 仅来自Java8 / YodaTime。
  • 不需要OffsetDateTimePostgres 仅将时刻存储在 UTC 类型为 TIMESTAMP WITH TIME ZONE 的列中,在 Java 中可以简单地使用 Instant。在TIMESTAMP WITHOUT TIME ZONE 类型的列中,根本没有区域或偏移量,不是UTC,也不是任何东西,在这种情况下,在Java 中使用LocalDateTime
  • @BasilBourque:这并不完全正确。如果你看到例如psql 显示这样一个值,它包括客户端的时区。如果您想要一个与此类似的值,则将时间戳作为 OffsetDateTime 检索对于保留 Postgres 在检索此类值时所做的调整确实有意义。
  • psql 固执己见,冒昧地应用该区域调整。在我看来,这是一个糟糕的设计选择,因为它造成了这种混乱,即 Postgres 使用偏移量/区域存储的错觉,而实际上 Postgres 仅存储在 UTC 中。该问题明确要求在TIMESTAMP WITH TIME ZONE 类型的列中存储数据。 Postgres 始终以 UTC 格式将值存储在该列中。
  • @a_horse_with_no_name 我尝试了以下方法: OffsetDateTime deadline = resultSet.getObject("deadline_date", OffsetDateTime.class); ,并得到日期为 2018-05-04T00:00Z 但在 DB 中日期是 2018-05-03 19:00:00-05。我需要在 DB 中获取日期。
【解决方案2】:

tl;博士

Postgres 总是将时刻存储在 UTC 类型为 TIMESTAMP WITH TIME ZONE 的列中。

  • 进入数据库,调整为 UTC。
  • 外出,UTC。

注意您的数据库访问工具:它可能会更改检索到的 UTC 值,应用区域/偏移调整。这会产生一种假象,即时刻已存储在该区域/偏移量中,而实际上它存储在 UTC 中。

使用对象而不是字符串与您的数据库交换日期时间值。

仅使用 java.time 类,绝不使用旧版 Date/Calendar。对于 UTC 时刻,请使用 java.time.Instant 类。

Instant instant = Instant.now() ;
myPreparedStatement.setObject( … , instant ) ;

检索。

Instant instant = myResultSet.getObject( … , Instant.class ) ;

智能对象,而不是哑字符串

建议您在 Java 和数据库之间交换日期时间值时使用对象而不是字符串是正确的。这就是JDBC 和您的JDBC drivermarshal data 在调解差异和细节时来回切换的重点。

但是,那些其他答案错误地建议在 Java 中使用错误的类。

UTC

Postgres 仅在 UTC 中保存时刻。与TIMESTAMP WITH TIME ZONE 类型的数据库列的输入相关的任何时区或偏移量信息用于将值调整为 UTC。然后将 UTC 值保存到数据库中,并忘记原始区域/偏移量。

同样,当从 TIMESTAMP WITH TIME ZONE 类型的数据库列中检索值时,您总是会得到一个 UTC 值。

但是,您可以选择在您和数据库之间使用的中间件或工具更改传输中的 UTC 值,以应用时区或偏移量,从而调整该值。我认为这是一个糟糕的设计选择,尽管我理解它背后的良好意图。但是这种行为会产生一种错觉,即存储的数据中没有区域/偏移量。

请注意,我在这里描述的是 Postgres 行为。 SQL 标准定义了我在这里讨论的数据类型,但没有定义它们的行为。不幸的是,SQL 标准几乎没有涉及日期时间问题。因此,各种数据库实现的行为不同。 Postgres 适应 UTC 的行为对我来说很有意义。但请注意,如果您关心日期时间值的原始区域/偏移量,则必须自己将其存储在单独的列中。

在 Java 端,只需使用 InstantTIMESTAMP WITH TIME ZONE 类型的数据库列中发送和检索时刻。

发送中。

Instant instant = Instant.now() ;  // Capture the current moment in UTC.
myPreparedStatement.setObject( … , instant ) ;

正在检索。

Instant instant = myResultSet.getObject( … , Instant.class );

您可以从 UTC 调整到某个特定时区。

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;

如果您使用OffsetDateTimeZonedDateTime 对象处理业务逻辑,请通过调用它们的toInstant 方法提取Instant

Instant instant = myZonedDateTime.toInstant() ;  // Extract an `Instant` object, effectively adjusting from some particular time zone to UTC.

未分区

另一种完全不同的日期时间是故意缺少任何时区或与 UTC 的偏移量的概念。因此,这种 not 代表一个特定的时刻,not 是时间轴上的一个点。此类型代表大约 26-27 小时(各个时区的范围)范围内的潜在时刻。

对于这种日期时间:

  • 在 Java 中,使用 LocalDateTime 类。
  • 在 Postgres 中,使用 TIMESTAMP WITHOUT TIME ZONE 类型。

当输入被发送到 Postgres 中这种类型的列时,任何伴随的区域/偏移信息都会被忽略。日期和时间按原样获取并直接存储在数据库中。检索后,您将获得相同的日期和时间,无需调整任何特定时区。


关于java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

您可以直接与您的数据库交换 java.time 对象。使用符合JDBC 4.2 或更高版本的JDBC driver。不需要字符串,不需要java.sql.* 类。

从哪里获得 java.time 类?

ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

【讨论】:

  • “走出去,UTC”是错误的。它将根据客户的时区进行调整。
  • @a_horse_with_no_name 不,该值以 UTC 格式存储和检索。您的介入访问工具(例如 psql 或数据库驱动程序)可能会选择动态应用区域/偏移调整。 不要将您的访问工具与数据库本身混为一谈。您可以通过调用myResultSet.getObject( … , Instant.class ) 来获取存储在数据库中的 UTC 值,从而在 JDBC 中轻松避免这种强制调整。
  • 看来我们都是对的,也是错的。如果客户端和服务器通过二进制协议发送数据,那么确实发送了 UTC 值。但是如果客户端和服务器以文本格式发送数据,则转换发生在服务器上。默认情况下,JDBC 驱动程序使用二进制格式。另外:getObject(...,Instant.class);(当前)不受 Postgres JDBC 驱动程序支持,它会导致“conversion to class java.time.Instant from 93 not supported
  • @BasilBourque 我尝试了以下方法: Instant instant = resultSet.getObject( "deadline_date" , Instant.class );并得到了这个异常:java.sql.SQLException: unsupported conversion to class java.time.Instant。
  • @BasilBourque 有没有办法像在 DB 中一样获取时区名称?我试过显示时区;在数据库中并获得时区:美国/中部。但是在我的 java 代码中执行相同的查询时,我得到了我的本地时区名称。
【解决方案3】:

你可以在 sql 中完成,你所要做的就是改变你的查询。

select to_char(iiv_prescan_invoice_details.deadline_date, "yyyy:MM:dd HH24:MI:SS") as deadline_date 
from iiv_prescan_invoice_details

或者你可以用java来做

LocalDateTime dateTime = resultSet.getTimestamp("deadline_date").toLocalDateTime();
System.out.println("Date Time: " + dateTime);

【讨论】:

  • LocalDateTime 正是用于TIMESTAMP WITH TIME ZONE 类型的数据库列的错误 类。该类缺乏任何时区或与 UTC 偏移的概念,因此它确实 代表一个时刻,并且 不是 时间线上的一个点。将该类用于TIMESTAMP WITHOUT TIME ZONE 类型的数据库列。 (“没有”与“有”)
猜你喜欢
  • 2020-05-18
  • 1970-01-01
  • 2022-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多