Postgres没有
TIMESTAMP
数据类型。Postgres有
两种日期和时间类型:
TIMESTAMP WITH TIME ZONE
和
TIMESTAMP WITHOUT TIME ZONE
。这些类型在时区信息方面有非常不同的行为。
WITH
类型使用任何偏移量或时区信息来调整日期时间到UTC,然后丢弃该偏移量或时区;Postgres从不保存偏移/区域信息。
WITHOUT
类型忽略可能存在的任何偏移或区域信息。
- 此类型不代表一个瞬间。它代表着大约26-27小时范围内(全球各地的时区范围)潜在时刻的模糊概念。
你几乎总是希望使用
WITH
类型,正如专家David E. Wheeler在
这里所解释的那样。只有当你对时间有模糊的概念而不是时间轴上的固定点时,
WITHOUT
才有意义。例如,“今年圣诞节从2016-12-25T00:00:00开始”将被存储在
WITHOUT
中,因为它适用于
任何时区,还没有应用于任何一个单一的时区来得到时间轴上的实际时刻。如果圣诞老人的精灵们正在跟踪 Eugene Oregon US 的开始时间,那么他们将使用
WITH
类型和包含偏移量或时区的输入,例如
2016-12-25T00:00:00-08:00
,该输入将保存为 Postgres 中的
2016-12-25T08:00.00Z
(其中
Z
表示 Zulu 或 UTC)。
Postgres中的在java.time中的等价物是。由于您的意图是使用UTC(一件好事),因此您不应该使用(一件坏事)。这可能是您困惑和遇到麻烦的主要原因。您一直在考虑使用或,但您都不应该使用;相反,您应该使用(下面会讨论)。
“我还想知道,因为我从转换为,反之亦然,是否会丢失任何时区信息。”
Indeed you are.
LocalDateTime
的整个目的就是失去时区信息。因此,在大多数应用程序中我们很少使用这个类。再举一个例子,圣诞节或者“公司规定:我们世界各地的工厂在中午12:30休息。”这将是
LocalTime
,对于特定日期,则是
LocalDateTime
。但是,直到你应用时区来获取
ZonedDateTime
之前,它没有实际意义,不是时间轴上的实际点。这次午休将在德里工厂、杜塞尔多夫工厂和底特律工厂的时间轴上的不同点。
LocalDateTime
中的“Local”可能反直觉,因为它表示没有特定的位置。当你在类名中读到“Local”时,请认为“不是某一时刻……没有在时间轴上……只是关于日期时间的模糊想法”。
您的服务器操作系统时区应该几乎总是设置为UTC。但是,您的编程不应依赖于此外部性,因为系统管理员很容易更改它或任何其他Java应用程序可以在JVM中更改当前默认时区。因此,请始终指定所需/预期的时区(顺便说一下,
Locale
也是如此)。
要点:
- 您正在过度劳累。
- 程序员/系统管理员必须学会“全球思考,本地呈现”。
在工作日戴上您的极客硬帽时,请以UTC为基础进行思考。只有在一天结束时,当切换到您的普通人模式时,才需要考虑您所在城镇的本地时间。
您的业务逻辑应专注于UTC。您的数据库存储、业务逻辑、数据交换、序列化、日志记录和自己的思考都应在UTC时区(而且是24小时制)完成。在向用户呈现数据时,然后才应用特定的时区。将分区日期时间视为外部事物,而不是您应用程序内部的工作部分。
在Java端,您需要在大部分业务逻辑中使用
java.time.Instant
(UTC时间轴上的一个时刻)。请注意保留HTML标签。
Instant now = Instant.now();
希望
JDBC驱动程序最终能够直接处理像
Instant
这样的java.time类型。在那之前,我们必须使用java.sql类型。旧的java.sql类有新的方法用于转换到/从java.time。
java.sql.TimeStamp ts = java.sql.TimeStamp.valueOf( instant );
现在,通过在Postgres中定义为
TIMESTAMP WITH TIME ZONE
的列上使用
setTimestamp
将
java.sql.TimeStamp
对象传递到
PreparedStatement
以保存。
要执行相反的操作:
Instant instant = ts.toInstant();
这很简单,从
Instant
转换为
java.sql.Timestamp
,再转换为
TIMESTAMP WITH TIME ZONE
,全部使用UTC时间,
没有涉及到时区。 服务器操作系统、JVM和客户端的当前默认时区都不相关。
为了向用户呈现,应用一个时区。使用
正确的时区名称,而不是如
EST
或
IST
等三至四个字母的代码。
ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );
您可以根据需要调整为不同的时区。
ZonedDateTime zdtKolkata = zdt.withZoneSameInstant( ZoneId.of( "Asia/Kolkata" ) );
要回到UTC时间线上的瞬间(即
Instant
),您可以从
ZonedDateTime
中提取。
Instant instant = zdt.toInstant();
这里没有使用LocalDateTime
。
如果你得到的数据没有任何与UTC偏移或时区相关的信息,例如2016-04-04T08:00
,那么这个数据对你来说是完全无用的(假设我们不是在讨论圣诞节或公司午餐等场景)。没有偏移/时区信息的日期时间就像没有指定货币的货币金额:142.70
甚至是$142.70
- 无用的。但是USD 142.70
、CAD 142.70
或MNX 142.70
……这些才是有用的。
如果你确信了预期的偏移/时区上下文,并且得到了2016-04-04T08:00
值,则:
- 将该字符串解析为
LocalDateTime
。
- 应用一个UTC偏移量来获取
OffsetDateTime
,或者(更好的方法)应用时区来获取ZonedDateTime
。
像这样的代码。
LocalDateTime ldt = LocalDateTime.parse( "2016-04-04T08:00" );
ZoneId zoneId = ZoneId.of( "Asia/Kolkata" ); // Or "America/Montreal" etc.
ZonedDateTime zdt = ldt.atZone( zoneId ); // Or atOffset( myZoneOffset ) if only an offset is known rather than a full time zone.
您的问题实际上是其他许多问题的重复。这些问题在其他问题和答案中已经被讨论了很多次。我建议您搜索并学习Stack Overflow以更深入地了解此主题。
JDBC 4.2
从JDBC 4.2开始,我们可以直接使用java.time对象与数据库交换。不再需要使用java.sql.Timestamp
或其相关类。
存储时,请使用JDBC规范中定义的OffsetDateTime
。
myPreparedStatement.setObject( … , instant.atOffset( ZoneOffset.UTC ) ) ; // The JDBC spec requires support for `OffsetDateTime`.
...或者直接使用Instant
,如果你的JDBC驱动支持的话。
myPreparedStatement.setObject( … , instant ) ; // Your JDBC driver may or may not support `Instant` directly, as it is not required by the JDBC spec.
检索中。
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
![Table of date-time types in Java (both legacy and modern) and in standard SQL](https://istack.dev59.com/G32JN.webp)
关于 java.time
java.time 框架内置于 Java 8 及更高版本中。这些类替代了老旧的、问题多多的遗留日期时间类,例如 java.util.Date
、Calendar
和 SimpleDateFormat
。
要了解更多信息,请参阅Oracle教程。并在Stack Overflow上搜索许多示例和解释。规范是JSR 310。
Joda-Time项目现在处于维护模式,建议迁移到java.time类。
您可以直接使用数据库交换 java.time 对象。使用符合 JDBC 4.2 或更高版本的 JDBC 驱动程序。无需字符串,无需 java.sql.*
类。Hibernate 5 和 JPA 2.2 支持 java.time。
如何获取 java.time 类?
![Table of which java.time library to use with which version of Java or Android](https://istack.dev59.com/Sksw9.webp)
LocalDateTime
来存储这个事实。意图是下午3点,但政治家可能会在那时重新定义这一时刻。对于时间表,您可以暂时将时区应用于潜在的LocalDateTime
,以生成一个实际的时刻作为ZonedDateTime
/Instant
。 - Basil Bourque