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代码将检索您真实的日期时间值,不包括时区/偏移量。
需要符合JDBC 4.2或更高版本的JDBC驱动程序才能直接使用java.time类型。
LocalDateTime ldt = myResultSet.getObject( … , LocalDateTime.class )
插入/更新数据库:
myPreparedStatement.setObject( … , ldt )
带时区的时间戳
你讨论的时区涉及到时间轴上的实际时刻,所以你应该绝对使用带时区的时间戳(TIMESTAMP WITH TIME ZONE)
而不是不带时区的时间戳(TIMESTAMP WITHOUT TIME ZONE)
。你不应该去操纵夏令时(Daylight Saving Time, DST)的差异等问题。让Java.time和Postgres为你完成这项工作,用已经编写并测试过的更好的代码。
取回:
Instant instant = myResultSet.getObject( … , Instant.class ) ;
ZonedDateTime zdt = instant.atZone( ZoneId.of( "Africa/Tunis" ) ) ;
插入/更新数据库:
myPreparedStatement.setObject( … , zdt ) ; // Inserting/updating a `TIMESTAMP WITH TIME ZONE` column.
从数据库中检索:
Instant instant = myResultSet.getObject( … , Instant.class ) ;
不,不,不。不要这样操作。您在Java和Postgres的类型上
滥用了。如果用户输入
2015-03-29 02:30:00
以表示
Europe/London
时区中的某一时刻,则应将其解析为
LocalDateTime
并立即应用
ZoneId
以获取
ZonedDateTime
。
要进行解析,请使用
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时刻,请提取一个
Instant
。
Instant
类表示时间线上以
UTC为基准的瞬间,分辨率为
纳秒(最多九位小数)。
Instant instant = zdt.toInstant() ;
通过 JDBC 将时间戳存储到数据库中,使用 TIMESTAMP WITH TIME ZONE
类型。
myPreparedStatement.setObject( … , instant ) ;
使用对象,而非字符串
请注意,我在这里的所有代码都使用java.time对象与数据库交换数据。在交换日期时间值时,请始终使用这些对象,而不仅仅是字符串。
关于java.time
java.time框架内置于Java 8及以上版本中。这些类取代了老旧的遗留日期时间类,如java.util.Date
、Calendar
和SimpleDateFormat
。
Joda-Time项目现在处于维护模式,建议迁移到java.time类。
想要了解更多,请查看Oracle教程。并在Stack Overflow上搜索许多示例和解释。规范是JSR 310。
如何获取java.time类?
ThreeTen-Extra 项目通过添加额外的类扩展了 java.time。该项目是 java.time 可能未来增加内容的试验场。您可能会在这里找到一些有用的类,例如 Interval
, YearWeek
, YearQuarter
和 更多。
TIMESTAMP WITHOUT TIME ZONE
或TIMESTAMP WITH TIME ZONE
列类型时,请使用完整的正式名称。它们的含义和行为是完全不同的。 - Basil Bourque