【问题标题】:DST problems with storing Joda LocalDateTime in a PostgreSQL 'timestamp' column将 Joda LocalDateTime 存储在 PostgreSQL 'timestamp' 列中的 DST 问题
【发布时间】:2014-11-28 16:08:35
【问题描述】:

在我们的应用中,我们存储了属于许多不同时区的日期时间。 我们决定使用 Joda LocalDateTime 类型——这样用户总是能得到他们一开始输入的任何内容。这正是我们所需要的。

在内部,我们知道用户属于哪个时区 - 所以当他们输入日期时间时,我们会进行如下检查:

dateTimeZone.isLocalDateTimeGap(localDateTime)

如果该日期时间在他们的时区中不存在(它在夏令时间隙中),我们会显示日期不正确的错误消息,从而防止不正确的日期时间存储在数据库中。

为了存储,我们使用 timestamp 列。当用户输入的日期时间存在于他们的时区但不存在于数据库时区(欧洲/柏林)时,问题就开始了。例如。当我从欧洲/伦敦时区存储 LocalDateTime 2015-03-29 02:30:00 时(这是有效的 - 在伦敦,差距在 01:00 和 02 之间:00),PostgreSQL 将小时移动 1 并保存为 2015-03-29 03:30:00

怎么办?有没有办法告诉 PostgreSQL 不对时区做任何事情,而只是按照 Joda 的代表来存储日期时间? (除了将它们存储为字符串;))

【问题讨论】:

  • 在 Postgres 中,timestamp 不知道时区。所以 Postgres 应该没有理由改变时间。您可以发布代码以了解如何将值保存到数据库中吗?另外 - 你正在运行什么版本的 Postgres?
  • 至少确保运行 PostgreSQL 的服务器使用 GMT+00:00 作为固定偏移量,因此不能对本地时间戳进行任何与间隙相关的操作。此外,您的 jdbc-driver 是否对本地时区敏感?如果是,那么可能有一些使用固定偏移量的配置选项。
  • @MattJohnson 和 Meno Hochschild - 谢谢你们两位 cmets :) 马特的评论“时间戳不是时区感知”帮助我解决了这个问题。这显然是由我的编辑器(SQLWorkbench)引起的,它正在改变时间戳。当我在 pgAdmin / psql 中显示时间戳时,它没有移动。马特,如果您将此评论添加为答案,我会接受。
  • 为清楚起见,在描述 Postgres 或任何其他符合 SQL 标准的数据库中的 TIMESTAMP WITHOUT TIME ZONETIMESTAMP WITH TIME ZONE 列类型时,请使用完整的正式名称非常具体。它们的含义和行为完全不同。

标签: postgresql datetime timezone jodatime dst


【解决方案1】:

在 PostgreSQL 7.3 及更高版本中,timestamp 等同于 timestamp without time zone。该数据类型不支持时区。它仅存储日期和时间。如果您发现它发生了变化,那么它可能与您用于存储或检索数据的代码或工具有关。

请注意,在 7.3 版之前,timestamp 等同于 timestamp with timezone。这在第一个注释框in the documentation here中提到。

【讨论】:

    【解决方案2】:

    Postgres 根据 SQL 标准提供两种日期时间类型。不幸的是,该标准几乎没有涉及该主题,因此此处描述的行为特定于 Postgres。其他数据库的行为可能不同。

    • TIMESTAMP WITHOUT TIME ZONE
      仅存储日期和时间。忽略传递的任何时区或与 UTC 的偏移量。
    • TIMESTAMP WITH TIME ZONE
      首先使用其传递的区域/偏移量调整传递的日期+时间,以获取 UTC 值。然后在进行调整后丢弃通过的区域/偏移量;如果需要,您必须自己将原始区域/偏移信息存储在单独的列中。

    注意TIMESTAMP WITHOUT TIME ZONE 代表一个实际的时刻,在时间轴上存储一个点。如果没有区域或偏移的上下文,它就没有真正的意义。它代表了大约 26-27 小时跨度内的一系列可能时刻。适用于诸如将约会存储在将来足够远以致时区规则可能在其到达之前更改的问题。也适用于诸如“圣诞节在今年 12 月 25 日午夜之后开始”之类的问题,您的意思是每个区域的时间不同,每个区域向西的时间越来越晚。

    在记录实际时刻、时间轴上的特定点时,请使用TIMESTAMP WITH TIME ZONE

    Java 中的现代方法使用 java.time 类,而不是 Joda-Time 库或与 Java 的最早版本捆绑在一起的麻烦的旧旧日期时间类。

    TIMESTAMP WITHOUT TIME ZONE

    对于TIMESTAMP WITHOUT TIME ZONE,java.time 中的等效类是LocalDateTime,用于没有任何偏移或区域的日期和时间。

    正如其他人指出的那样,一些工具可能会以一种误导和混淆的方式动态地将时区应用于检索到的值,尽管这是出于善意的反功能。以下 Java 代码将检索您的真实日期时间值 sans 区域/偏移量。

    需要符合 JDBC 4.2 或更高版本的 JDBC 驱动程序才能直接使用 java.time 类型。

    LocalDateTime ldt = myResultSet.getObject( … , LocalDateTime.class ) ;  // Retrieving a `TIMESTAMP WITHOUT TIME ZONE` value.
    

    插入/更新数据库:

    myPreparedStatement.setObject( … , ldt ) ;  // Inserting/updating a `TIMESTAMP WITHOUT TIME ZONE` column.
    

    TIMESTAMP WITH TIME ZONE

    您对时区的讨论表明您关注时间线上的实际时刻。所以你绝对应该使用TIMESTAMP WITH TIME ZONE 而不是TIMESTAMP WITHOUT TIME ZONE。你不应该搞乱夏令时 (DST) 间隔等。让 java.time 和 Postgres 为您完成这项工作,已经编写和测试了更好的代码。

    检索:

    Instant instant = myResultSet.getObject( … , Instant.class ) ;  // Retrieving a `TIMESTAMP WITH TIME ZONE` value in UTC.
    ZonedDateTime zdt = instant.atZone( ZoneId.of( "Africa/Tunis" ) ) ;  // Adjusting from a UTC value to a specific time zone.
    

    插入/更新数据库:

    myPreparedStatement.setObject( … , zdt ) ;  // Inserting/updating a `TIMESTAMP WITH TIME ZONE` column.
    

    从数据库中检索:

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

    例如当我从欧洲/伦敦时区存储 LocalDateTime 2015-03-29 02:30:00 时

    不,不,不。不要以这种方式工作。您滥用了 Java 和 Postgres 的类型

    如果用户输入的2015-03-29 02:30:00 旨在表示Europe/London 时区中的时刻,则解析为LocalDateTime 并立即应用ZoneId 以获取ZonedDateTime

    要解析,请将中间的 SPACE 替换为 T,以符合 java.time 类中默认使用的 ISO 8601 标准格式。

    String input = "2015-03-29 02:30:00".replace( " " , "T" ) ;
    LocalDateTime ldt = LocalDateTime.parse( input ) ;
    ZoneId z = ZoneId.of( "Europe/London" ) ;
    ZonedDateTime zdt = ldt.atZone( z ) ; 
    

    要在 UTC 中查看同一时刻,请提取 InstantInstant 类代表UTC 时间线上的时刻,分辨率为nanoseconds(最多九 (9) 位小数)。

    Instant instant = zdt.toInstant() ;
    

    通过 JDBC 传递即时信息以存储在数据库中的 TIMESTAMP WITH TIME ZONE 中。

    myPreparedStatement.setObject( … , instant ) ;
    

    使用对象,而不是字符串

    请注意,我在这里的所有代码都使用 java.time 对象与数据库交换数据。始终使用这些对象而不是单纯的字符串来交换日期时间值。


    关于java.time

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

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

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

    从哪里获得 java.time 类?

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

    【讨论】:

      猜你喜欢
      • 2018-01-23
      • 2020-06-13
      • 2021-11-26
      • 2014-11-16
      • 2020-12-03
      • 2020-05-09
      • 1970-01-01
      • 2017-05-23
      相关资源
      最近更新 更多