将Joda LocalDateTime存储在PostgreSQL'timestamp'列中的DST问题

问题描述 投票:3回答:2

在我们的应用程序中,我们存储属于许多不同时区的日期时间。我们决定使用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代表它们存储日期时间? (除了将它们存储为字符串;))

postgresql datetime timezone jodatime dst
2个回答
3
投票

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

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


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 zone / offset。

需要符合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

要解析,请使用T替换中间的SPACE,以符合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对象与数据库交换数据。始终使用这些对象而不仅仅是字符串来交换日期时间值。


About java.time

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

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

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

从哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展了java.time。该项目是未来可能添加到java.time的试验场。你可能会在这里找到一些有用的类,如IntervalYearWeekYearQuartermore

© www.soinside.com 2019 - 2024. All rights reserved.