Java 8 LocalDateTime和奇怪的区域行为

使用Java8的LocalDateTime时,我有点困惑。

我正在尝试做什么

  • 我有一个Date(java.util)对象
  • 我想将其转换为LocalDateTime
  • 我想格式化LocalDateTime以获得HH:mm的时间

我做了什么

LocalDateTime ldate2 = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); LocalDateTime ldate = LocalDateTime.ofInstant(date.toInstant(), ZoneId.of("CET")); System.out.println("Date : " + date); System.out.println("LocalDateTime CET : " + ldate); System.out.println("LocalDateTime " + ZoneId.systemDefault() + " : " + ldate2); 

结果是

 Date : Sun Dec 31 23:30:00 CET 1889 LocalDateTime CET : 1889-12-31T23:30 LocalDateTime Europe/Paris : 1889-12-31T22:39:21 

我的问题

我期待的最终结果是23:30 ,这与ZoneId.of("CET")正常工作,但我想避免指定时区使用系统一个ZoneId.systemDefault() 。 在这种情况下, 欧洲/巴黎 。 但是我得到了22:39 。 我可以很容易地理解1小时的偏移量,但是这对我来说听起来很奇怪。

使用SimpleDateFormat与原始date工作正常,但代码可能会在multithreading环境中运行,所以我需要一个线程安全的解决scheme。

有人能解释我这种行为吗? 这是预期的还是我做错了什么(或者我不明白的东西明显:))? 预先感谢帮助我。

编辑:

这个问题与年份(1889年)相关,这个年份相当古老,但这是Excel用于时间表示的一年。 查看问题的代码示例: http : //ideone.com/kaHaAz

分析和解决scheme

@shmosel的回答正确地提到了1889年巴黎不同的历史tz抵消。 一个很好的原始来源证实这一点:

 Zone Europe/Paris 0:09:21 - LMT 1891 Mar 15 0:01 0:09:21 - PMT 1911 Mar 11 0:01 # Paris MT 0:00 France WE%sT 1940 Jun 14 23:00 

但这只是整个事实的一小部分。 了解Java-8中引入的java.util.TimeZoneZoneId之间的区别很重要。

新类ZoneId解释底层tzdb-repository的规则,以便查询包括LMT行的所有条目。 LMT的意思是“本地平均时间”,当没有确切的历史时区偏移量可用时,TZDB维护人员的任意发明。 这些LMT条目仅代表相关城市/地区的基于经度的偏移计算,并不代表任何实际的历史时区偏移。 所以,请谨慎处理这些偏差:当时人们通常不会看当地时间。 还要记住,大多数人(或所有人)在那些远古时期没有足够准确的时间测量方法。 因此,迄今为止使用时区计算的一般方法是值得怀疑的。

摘要: ZoneId使用+00:09:21作为1889年的抵消。

但是,你说你从java.util.Datetypes的对象开始。 由于这种types不代表本地日历date或挂钟时间,而是一个时刻,所以无法避免时区计算来获得所希望的本地时间戳。 现在的问题是使用哪种types的时区。 在java.util.Date情况下,自然和最可能的select不是ZoneId ,而是旧的java.util.TimeZone类。 尤其是如果你已经通过其他遗留API(如Apache POI(一个允许访问Excel文件的库))获得Date -instance,那么尤其如此。

您可能认为java.util.TimeZoneZoneId的内部规则对于像“Europe / Paris”这样的给定tz标识符应该是相同的。 但是,不,这是不一样的。 传统的类TimeZone在1900年之前切断所有偏移转换,并使用当前的偏移量。

  1. 在1889年, java.util.TimeZone的偏移量是+01:00(2017年的当前冬季时间)。
  2. 在1900年到1911年,抵消是+00:09:21。
  3. 1911年以后直到1916年6月,抵消为零。

因此,我build议你放弃ZoneId类,并使用TimeZone类来保存每年的原始时区计算。 实际上,您可以使用与构buildDate -instance时相同的tz规则。 解决scheme示例:

 String input = "Sun Dec 31 23:30:00 CET 1889"; SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.ENGLISH); TimeZone tz = TimeZone.getTimeZone("Europe/Paris"); sdf.setTimeZone(tz); Date d = sdf.parse(input); ZoneOffset offset = ZoneOffset.ofTotalSeconds(tz.getOffset(d.getTime()) / 1000); System.out.println(d); // Tue Dec 31 23:30:00 CET 1889 LocalDateTime ldt = LocalDateTime.ofInstant(d.toInstant(), offset); System.out.println("ldt=" + ldt); // 1889-12-31T23:30 

关于你1889年的另一个话题是:

1889年对我来说是惊人的。 据我所知(我的怀疑),Excel使用约十年后的date作为参考点,即1899-12-31。 因此,在没有任何date的情况下,相对于1899年年底的这个时代,时钟时间将被模拟。请记住,Excel模型的date和时间与Excel时代相比是双倍的。

另一个细节表示1899年而不是1889年:1889-12-31的工作日是星期二,但是1899-12-31的工作日是星期天,所以你的input“1889年12月31日23:30:00 CET 1889”并不一致。 请尝试检查您是如何获得您的Date实例,并检查您是否刚刚在这里做了印刷错误。

我还没有find这方面的资料,但看起来1889年在巴黎的抵消可能确实是+00:09:21:

 System.out.println(LocalDateTime.parse("1889-12-03T10:15:30").atZone(ZoneId.of("Europe/Paris")).getOffset()); // +00:09:21 

另见https://stackoverflow.com/a/7232851/1553851