示例:1 月 23 日变为 24 日
你问:
当这种转换失败时,有人可以举个例子吗?
是的,我可以。
从 1 月 23 日开始。
LocalDate ld = LocalDate.of( 2020 , Month.JANUARY , 23 );
LocalTime lt = LocalTime.of( 23 , 0 );
ZoneId zMontreal = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.of( ld , lt , zMontreal );
Instant instant = zdt.toInstant();
zdt.toString() = 2020-01-23T23:00-05:00[美国/蒙特利尔]
instant.toString() = 2020-01-24T04:00:00Z
Instant 类代表 UTC 中的时刻。让我们使用添加到旧类的新转换方法转换为非常旧的类java.sql.Timestamp。
// Convert from modern class to troubled legacy class `Timestamp`.
java.sql.Timestamp ts = Timestamp.from( instant );
ts.toString() = 2020-01-23 20:00:00.0
不幸的是,Timestamp::toString 方法在生成文本时会动态应用 JVM 的当前默认时区。
ZoneOffset defaultOffset = ZoneId.systemDefault().getRules().getOffset( ts.toInstant() );
System.out.println( "JVM’s current default time zone: " + ZoneId.systemDefault() + " had an offset then of: " + defaultOffset );
JVM 当前的默认时区:America/Los_Angeles 当时的偏移量为:-08:00
所以Timestamp::toString 在从凌晨 4 点调回 8 小时到晚上 8 点后误报了对象的 UTC 值。这个反特性是这个设计不佳的类的几个严重问题之一。有关Timestamp 的诡异行为的更多讨论,请参阅正确的Answer by Ole V.V.
让我们运行您的代码。想象一下在运行时 JVM 当前的默认时区是Asia/Tokyo。
TimeZone.setDefault( TimeZone.getTimeZone( "Asia/Tokyo" ) );
LocalDate localDate = ts.toLocalDateTime().toLocalDate();
测试是否相等。哎呀!我们最终得到了 24 号而不是 23 号。
boolean sameDate = ld.isEqual( localDate );
System.out.println( "sameDate = " + sameDate + " | ld: " + ld + " localDate: " + localDate );
sameDate = 假 | ld: 2020-01-23 localDate: 2020-01-24
看到这个code run live at IdeOne.com。
那么你的代码有什么问题?
- 切勿使用
java.sql.Timestamp。它是 Java 最早版本附带的几个糟糕的日期时间类之一。 永远不要使用这些遗留类。它们已被 JSR 310 中定义的现代 java.time 类完全取代。
- 你打电话给
toLocalDateTime,它会删除重要信息。删除任何时区或与 UTC 的偏移量,只留下日期和时间。所以这个类不能用来表示一个时刻,不是时间轴上的一个点。例如:2020 年 12 月 25 日中午——是德里的中午、杜塞尔多夫的中午还是底特律的中午,三个不同的时刻相隔几个小时? LocalDateTime 本质上是模棱两可的。
- 您在确定日期时忽略了时区这一关键问题。对于任何给定的时刻,日期在全球范围内都会有所不同。在某一时刻,在澳大利亚可能是“明天”,而同时在墨西哥可能是“昨天”。