Java LocalDateTime到ZoneDateTime

Java LocalDateTime到ZoneDateTime,java,spring-data-jpa,postgresql-9.3,java-time,Java,Spring Data Jpa,Postgresql 9.3,Java Time,我有Java8SpringWeb应用程序,它将支持多个区域。我需要为客户地点制作日历事件。假设我的web和Postgres服务器托管在MST时区(但我想如果我们使用云计算,它可能在任何地方)。但客户在EST。所以,根据我读到的一些最佳实践,我想我应该以UTC格式存储所有日期时间。数据库中的所有日期时间字段都声明为时间戳 下面是我如何获取LocalDateTime并转换为UTC: ZonedDateTime startZonedDT = ZonedDateTime.ofLocal(dto.getS

我有Java8SpringWeb应用程序,它将支持多个区域。我需要为客户地点制作日历事件。假设我的web和Postgres服务器托管在MST时区(但我想如果我们使用云计算,它可能在任何地方)。但客户在EST。所以,根据我读到的一些最佳实践,我想我应该以UTC格式存储所有日期时间。数据库中的所有日期时间字段都声明为时间戳

下面是我如何获取LocalDateTime并转换为UTC:

ZonedDateTime startZonedDT = ZonedDateTime.ofLocal(dto.getStartDateTime(), ZoneOffset.UTC, null);
//appointment startDateTime is a LocalDateTime
appointment.setStartDateTime( startZonedDT.toLocalDateTime() );
现在,例如,当搜索日期时,我必须将请求的日期时间转换为UTC,获取结果并转换为用户时区(存储在数据库中):

现在,我不确定这是正确的方法。我还想知道是否因为我从LocalDateTime转到ZoneDateTime,反之亦然,我可能会丢失任何时区信息

这就是我所看到的,我觉得它不正确。当我从UI收到LocalDateTime时,我得到以下信息:

2016-04-04T08:00
分区时间=

dateTime=2016-04-04T08:00
offset="Z"
zone="Z"
然后,当我将转换后的值分配给我的约会LocalDateTime时,我存储:

2016-04-04T08:00
我觉得因为我存储在LocalDateTime中,我丢失了转换为ZoneDateTime的时区

我是否应该让我的实体(约会)使用ZonedDateTime而不是LocalDateTime,这样Postgres就不会丢失这些信息

----------------编辑


在Basils出色的回答之后,我意识到我有不关心用户时区的特权-所有约会都针对特定位置,因此我可以将所有日期时间存储为UTC,然后在检索时将其转换为位置时区。我做了以下跟进

Postgres没有时间戳这样的数据类型。Postgres对日期和时间有:
带时区的时间戳
不带时区的时间戳
。这些类型在时区信息方面有非常不同的行为

  • 带有类型的
    使用任何偏移或时区信息来调整日期时间,然后处理该偏移或时区;Postgres从不保存偏移/区域信息
  • 这种类型表示一个时刻,时间线上的一个特定点
  • 不带类型的
    忽略可能存在的任何偏移或分区信息
  • 这种类型不代表一个时刻。它代表了大约26-27小时(全球时区范围)内潜在时刻的模糊概念
正如专家大卫·E·惠勒(David E.Wheeler)所说,您几乎总是希望
带有
类型。只有当你对日期时间而不是时间表上的固定点有模糊的概念时,没有
才有意义。例如,“今年的圣诞节从2016-12-25T00:00:00开始”将存储在
中,而不存储在
中,因为它适用于任何时区,尚未应用于任何单个时区以获取时间线上的实际时刻。如果圣诞老人的精灵们正在追踪美国俄勒冈州尤金的开始时间,那么他们将使用带有
类型的
,以及包含偏移量或时区的输入,例如
2016-12-25T00:00:00-08:00
,该输入保存到Postgres中,作为
2016-12-25T08:00.00Z
(其中
Z
表示祖鲁或UTC)

在java.TIME中,Postgres的
时间戳(不带时区)的等价物是
java.TIME.LocalDateTime
。因为您打算在UTC工作(一件好事),所以不应该使用
LocalDateTime
(一件坏事)。这可能是你困惑和麻烦的主要原因。您一直在考虑使用
LocalDateTime
ZoneDateTime
,但这两种方法都不应该使用;相反,您应该使用
Instant
(下面讨论)

我还想知道是否因为我从LocalDateTime转到ZoneDateTime,反之亦然,我可能会丢失任何时区信息

你确实是LocalDateTime的全部要点是丢失时区信息。因此我们很少在大多数应用程序中使用此类。再说一次,圣诞节的例子。或者另一个例子,“公司政策:我们在世界各地的所有工厂在下午12:30吃午餐”。这将是
LocalTime
,对于特定日期,
LocalDateTime
。但这没有真正的意义,也不是时间表上的一个实际点,除非你应用时区来获得一个时间。德里工厂的午休时间与杜塞尔多夫工厂的午休时间不同,底特律工厂的午休时间也不同

LocalDateTime
中的“Local”一词可能与直觉相反,因为它表示没有特定的位置。当你读到类名中的“Local”时,想想“不是一刻……不是在时间线上……只是关于某个日期时间的模糊概念”

您的服务器在其操作系统时区中几乎应始终设置为UTC。但是您的编程永远不应该依赖于这种外部性,因为系统管理员更改它或任何其他Java应用程序更改JVM中当前的默认时区都太容易了。因此,请始终指定所需/预期的时区。(顺便说一句,
Locale
也是如此。)

结果:

  • 你工作太辛苦了
  • 程序员/系统管理员必须学会“思考全局,呈现局部”
在工作日,戴着极客安全帽,思考UTC。只有在一天结束时,当你转到你的外行的had时,你才应该回到你所在城市的当地时间

您的业务逻辑应该关注UTC。您的数据库存储、业务逻辑、数据交换、序列化、日志记录和您自己的想法都应该在UTC时区(顺便说一下,还有24小时制)内完成。当向用户呈现数据时,仅应用特定时区。将分区日期时间视为外部事物,而不是工作部分
2016-04-04T08:00
Instant now = Instant.now();
java.sql.TimeStamp ts = java.sql.TimeStamp.valueOf( instant );
Instant instant = ts.toInstant();
ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );
ZonedDateTime zdtKolkata = zdt.withZoneSameInstant( ZoneId.of( "Asia/Kolkata" ) );
Instant instant = zdt.toInstant();
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.
myPreparedStatement.setObject( … , instant.atOffset( ZoneOffset.UTC ) ) ;  // The JDBC spec requires support for `OffsetDateTime`. 
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 ) ;
LocalDateTime localDateTime = LocalDateTime.parse( "2016-04-04T08:00" );
ZoneId zoneId = ZoneId.of( "UTC" ); // Or "Asia/Kolkata" etc.
ZonedDateTime zdt = localDateTime.atZone( zoneId );