将Joda LocalDateTime存储在PostgreSQL'时间戳中的DST问题'柱

时间:2014-10-03 15:09:27

标签: postgresql datetime timezone jodatime dst

在我们的应用中,我们会存储属于许多不同时区的日期时间。 我们决定使用Joda LocalDateTime类型 - 这样用户总是可以从字面上得到他们首先输入的内容。这正是我们所需要的。

在内部,我们知道用户属于哪个时区 - 所以当他们输入日期时间时,我们会做这样的检查:

dateTimeZone.isLocalDateTimeGap(localDateTime)

如果该时区中不存在该日期时间(它在日光节省间隙中),则会显示日期不正确的错误消息,从而防止将错误的日期时间存储在数据库中。

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

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

2 个答案:

答案 0 :(得分:3)

在PostgreSQL 7.3及更高版本中,timestamp相当于timestamp without time zone。该数据类型不是时区感知的。它只存储日期和时间。如果您发现它已移位,则可能与您用于存储或检索数据的代码或工具有关。

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

答案 1 :(得分:0)

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 ) ;
  

E.g。当我从欧洲/伦敦时区存储LocalDateTime 2015-03-29 02:30:00

不,不,不。不要这样工作。您滥用了Java和Postgres的类型

如果用户输入2015-03-29 02:30:00打算代表Europe/London时区中的片刻,则解析为LocalDateTime并立即应用ZoneId获取{{1} }}。

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

T

要在UTC中查看同一时刻,请提取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 ) ; Instant类代表UTC中时间轴上的一个时刻,分辨率为nanoseconds(小数部分最多九(9)位)。

Instant

通过JDBC传递即时消息,以便在Instant instant = zdt.toInstant() ;

中存储在数据库中
TIMESTAMP WITH TIME ZONE

使用对象,而不是字符串

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

关于java.time

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

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

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310

从哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如IntervalYearWeekYearQuartermore