【问题标题】:Java 8 LocalDateTime and Strange Zone BehaviourJava 8 LocalDateTime 和奇怪的区域行为
【发布时间】:2017-06-02 14:24:18
【问题描述】:

在使用 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 和原始日期可以正常工作,但代码可能会在多线程环境中运行,所以我需要一个线程安全的解决方案。

有人可以解释一下这种行为吗?这是预期的还是我做错了什么(或者我不理解一些明显的东西:))? 提前感谢您帮助我。

编辑:

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

【问题讨论】:

  • 是的,我刚刚也尝试过使用这样的示例,它在我的计算机上运行。整个画面有点复杂。我正在创建一个axis2消息构建器,它正在使用apache POI读取一个excel文件。日期是单元格的内容。也许这个问题与此更相关,但我仍然不明白为什么
  • 我看不出有什么问题。仅供参考,您可以使用 TimeZone.setDefault(TimeZone.getTimeZone("Europe/Paris")); 在 ideone 中设置时区,就像我更新的测试一样。
  • 感谢您的提示,使用它您现在可以看到问题:ideone.com/kaHaAz
  • 真的是 1889 年还是 1899 年(因为 Excel 似乎使用 1899-12-30 作为纪元)?你如何得到你的Date-instance?通过哪个 API?

标签: java excel date time format


【解决方案1】:

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

【讨论】:

  • 那么,据我所知,这是一种预期的行为,然后可以选择移动当年的日期(因为在这种情况下我只需要时间)以保持通用的可能。
【解决方案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 行在内的所有条目。 LMT 的意思是“本地平均时间”,是当没有确切的历史时区偏移可用时,TZDB 维护者。这些 LMT 条目仅代表相关城市/地区的基于经度的偏移量计算,并不代表任何真实的历史时区偏移量。因此,请谨慎处理此类偏移:当时的人们通常不会看到他们的当地时间。还要记住,大多数人(或所有人)在那些古代都没有足够精确的时间测量方法。 因此,在过去使用时区计算的一般方法是值得怀疑的。

总结:ZoneId 使用 +00:09:21 作为 1889 年的偏移量。

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

您可能认为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 月,偏移量为零。

因此,我建议您放弃 ZoneId 类并使用 TimeZone 类,以保留相关年份的原始时区计算。实际上,您使用与构建Date-instance 相同的 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 的工作日是星期日,因此您的输入“Sun Dec 31 23:30:00 CET 1889”是不一致。请尝试检查您是如何获得Date-instance 的,并检查您是否只是在这里输入了印刷错误。

【讨论】:

  • 感谢您的完整解释。我在使用 DateTimeFormatter 之前尝试了 SimpleDateFormat,你说得对,它工作正常。但是在我的记忆中 SimpleDateFormat 不是线程安全的(这也是为什么 joda 时间被如此使用的原因),它在 Java 8 中是否发生了变化?
  • 关于 1889 年你也说得对,错字应该是 1899 年
  • @Nicolas 如果您担心线程安全,请不要在线程之间共享格式化程序。或在使用前同步。
  • @Nicolas SimpleDateFormat 永远不会是线程安全的(旧的设计选择),因此在多线程的情况下需要一个新实例(或通过 ThreadLocale 访问——性能方面的最佳方式) .我在这里选择它 - 主要是为了演示目的 - 以便使用 TimeZone 的规则直接创建 java.util.Date 的实例。如果您使用的是 Apache POI,那么该 API 将直接为您提供 java.util.Date 的实例。
猜你喜欢
  • 2012-07-01
  • 2011-08-08
  • 1970-01-01
  • 2020-05-19
  • 1970-01-01
  • 1970-01-01
  • 2020-07-21
  • 2015-12-19
  • 1970-01-01
相关资源
最近更新 更多