Java 8 LocalDateTime和Strange Zone Behavior

时间:2017-01-18 09:22:29

标签: java excel date time format

使用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")工作正常,但我想避免指定时区来使用系统{{1} }。在这种情况下 Europe / Paris 。但后来我得到了 22:39 。我可以很容易地理解1小时的偏移,但这对我来说听起来很奇怪。

在原始日期使用ZoneId.systemDefault()工作正常,但代码可能会在多线程环境中运行,所以我需要一个线程安全的解决方案。

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

编辑:

这个问题与年份(1889年)有关,这一年很久,但它是Excel中用于时间表示的年份。 要查看问题的代码示例:http://ideone.com/kaHaAz

2 个答案:

答案 0 :(得分:3)

我还没有找到这方面的来源,但看起来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

答案 1 :(得分:2)

分析和解决方案

@shmosel的答案正确地提到了1889年巴黎不同的历史tz偏移。A good original source来证实这一点:

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-lines在内的所有条目。 LMT表示"本地平均时间&#34 ;当没有确切的历史时区偏移可用时,它是TZDB维护者的任意发明。那些LMT条目仅表示相关城市/区域的基于经度的偏移计算,并不代表任何真实的历史时区偏移。所以请小心处理这些补偿:通常不是当时人们如何看待当地时间。还要记住,大多数人(或所有人)在古代都没有足够精确的时间测量方法。 因此,在过去的时间里使用时区计算的一般方法值得怀疑。

  

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

但是,您说您从java.util.Date类型的对象开始。由于此类型不代表本地日历日期或墙上时间,而是表示时刻,因此无法避免时区计算以获得所需的本地时间戳。现在的问题是使用哪种类型的时区。如果是java.util.Date,则自然和最可能的选择不是ZoneId,而是旧班java.util.TimeZone。特别是如果您通过其他传统API(如Apache POI(允许访问Excel文件的库))获得Date - 实例。

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

  1. 在1889年,java.util.TimeZone的偏移量为+01:00(当前 2017年冬季时间)。
  2. 在1900年至1911年,偏移量为+00:09:21。
  3. 1911年至1916年6月之后,抵消为零。
  4. 因此,我建议您放弃课程ZoneId并使用课程TimeZone,以便保留每个相关课程的原始时区计算。实际上,您使用与构造Date - 实例时相同的tz规则。 解决方案示例:

    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使用大约十年后的日期作为参考点,即1899-12-31。因此,没有任何日期的时钟时间将相对于1899年末的这个时代进行建模。请记住,Excel模型的日期和时间与Excel时期相比只有两倍。

    另一个细节表明1899而不是1889:1889-12-31的工作日是星期二,但1899-12-31的工作日是星期天,所以你的输入"太阳12月31日23:30:00 CET 1889"不一致。请尝试检查您获取Date - 实例的准确程度,并检查您是否在此处输入了打字错误。