我最近一直在处理一个讨厌的heisenbug。它让我想起了著名的500英里电子邮件故事。我解析一个几乎是ISO8601
收到的日期,但是解析后的Date
对象有时是未来几分钟。然而,当我试图在单元测试中解析相同的数据时,结果日期总是正确的。
以下是我收到的格式:
2018-06-25T13:50:53.984771
我使用Jackson
从JSON
解析它:
new StdDateFormat().parse(timestamp);
以下是一些解析日期的示例:
2018-06-25T13:50:59.337828
Mon Jun 25 15:56:36 CEST 2018
2018-06-25T13:50:53.984771
Mon Jun 25 16:07:17 CEST 2018
两小时的差异很好,这只是时区,但是分钟呢?它们似乎是随机偏移的!
我在回答我自己的问题,希望有一天它能拯救某人的皮肤。
这个问题在单元测试中没有表现出来,所以我认为服务器可能有问题。我尝试了所有我认为可能出错的东西,包括研究偏移15分钟的奇怪时区,调查Java实际花费时间的地方等等。最后,我别无选择,只能对代码进行了几次修改,以平分问题所在。结果是解析器。
这是导致问题的bug:https://github.com/FasterXML/jackson-databind/issues/1668
当一个朋友注意到时间戳末尾的毫秒数对应于以分钟为单位的偏移量时,我才找到它:
Original: 2018-06-25T13:50:53.984771
Milliseconds: 984771 ms = 16 minutes 24.77 seconds
Result: Mon Jun 25 16:07:17 CEST 2018
问题是解析器将String的最后一部分计算为毫秒,并将它们静默添加到内部Calendar
对象中,该对象很乐意接受任意数量的毫秒并按照设计将它们添加到自身中。
这个问题在单元测试中没有表现出来的原因是,我创建的库使用了一个不再包含bug的较新版本的Jackson。然而,使用该库的应用程序已经包含了Jackson,并且由于Maven在解析依赖关系时选择了最接近正在构建的项目的库的定义,它在库中用一个在自己的pom. xml
中声明的较旧版本覆盖了我的较新版本的Jackson。所以最终这是一个自我造成的依赖地狱。
您正在使用Java最早版本中非常麻烦的旧日期时间类(Date
、Calendar
)。这些类现在是遗留的,几年前被java. time类取代。
我不是Jackson用户,所以我不知道Jackson是否添加了对java. time的内置支持。但我想这很可能。
如果没有,看起来您可以为此目的向Jackson添加一个支持模块。请参阅这篇文章、这个问题和这个GitHub站点。
java. time类使用纳秒分辨率。这意味着最多九(9)位小数秒的十进制数字。对于输入中的6位数字来说绰绰有余。
你还有另一个性质不同的问题。你在输入一个没有时区或UTC偏移量概念的日期时间。然后你把它保存到一个不同的类型中,一个有时区的类型。不好。你在没有价值的地方注入了价值。这就像取一个被认为是价格的通用数字,然后任意应用输入中没有指示的特定货币。
您应该将2018-06-25T13:50:53.984771
这样的输入解析为LocalDateTime
。这种类型故意缺少任何时区或UTC偏移量,就像您的输入一样。这种类型不代表特定的时刻,也不代表时间线上的一个点,因为在您应用区域/偏移量的上下文之前,它没有真正的意义。
有关更多讨论,请在Stack Overflow中搜索LocalDateTime
、ZonedDateTime
、OffsetDateTime
和Instant
。特别是对于我的一些答案,搜索“Santa”。
java. time框架内置于Java8及更高版本中。这些类取代了麻烦的旧日期时间类,如java.util.Date
、Calendar
、
Joda-Time项目现在处于维护模式,建议迁移到java. time类。
要了解更多信息,请参阅Oracle教程。并搜索Stack Overflow以获取许多示例和解释。规格是JSR 310。
您可以直接与您的数据库交换java. time对象。使用符合JDBC 4.2或更高版本的JDBC驱动程序。无需字符串,无需java.sql.*
类。
在哪里获取java. time类?
ThreeTen-Extra项目通过附加类扩展了java. time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的类,例如Interval
、YearYork
、YearQuarter
等。
使用jackson-mods-java8和Basil Bourque在他的回答中推荐的java. time
类型。我没有使用jackson-mods-java8的经验,但它绝对应该正确解析至少9个小数的几分之一秒,并且它确实支持现代日期时间类型。