有一个创建仅限时间的Date对象的函数.(为什么这是必需的是一个长篇故事,在这种情况下是无关紧要的,但我需要与XML世界中的一些东西进行比较,其中TIME(即仅时间)是一个有效的概念).
private static final SimpleDateFormat DF_TIMEONLY = new SimpleDateFormat("HH:mm:ss.SSSZ"); public static Date getCurrentTimeOnly() { String onlyTimeStr = DF_TIMEONLY.format(new Date()); // line #5 Date onlyTimeDt = null; try { onlyTimeDt = DF_TIMEONLY.parse(onlyTimeStr); // line #8 } catch (ParseException ex) { // can never happen (you would think!) } return onlyTimeDt; }
可能至少有几种方法可以在Java中创建一个仅限日期的日期(或者更确切地说,日期部分是1970-01-01),但我的问题实际上并非如此.
我的问题是这段代码在生产运行很长时间后开始在#8行上随机抛出NumberFormatException.从技术上讲,我会说这应该是不可能的,对吧?
以下是来自上面一段代码的随机NumberFormatExceptions的摘录:
java.lang.NumberFormatException: multiple points java.lang.NumberFormatException: For input string: ".11331133EE22" java.lang.NumberFormatException: For input string: "880044E.3880044" java.lang.NumberFormatException: For input string: "880044E.3880044E3"
首先,我希望我们能够正式认为这应该是不可能的吗?代码使用相同的format(DF_TIMEONLY
)作为输出然后输入.如果你不同意它应该是不可能的,请告诉我.
我无法在独立环境中重新生成问题.当JVM运行很长时间(> 1周)时,问题似乎就出现了.我无法找到问题的模式,即夏令时/冬令时,上午/下午等.错误是零星的,这意味着一分钟它将抛出NumberFormatException,下一分钟它将运行正常.
我怀疑在JVM或者甚至在CPU中某处存在某种算术故障.上述例外情况表明涉及浮点数,但我没有看到它们来自何处.据我所知,Java的Date对象是一个包装器,long
它包含了自纪元以来的millis数.
我猜测正在发生的事情是onlyTimeStr
在第5行中创建了一个意外的字符串,所以这个问题确实存在于这里而不是第8行.
以下是完整堆栈跟踪的示例:
java.lang.NumberFormatException: For input string: "880044E.3880044E3" at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1241) at java.lang.Double.parseDouble(Double.java:540) at java.text.DigitList.getDouble(DigitList.java:168) at java.text.DecimalFormat.parse(DecimalFormat.java:1321) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2086) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1455) at java.text.DateFormat.parse(DateFormat.java:355) at org.mannmann.zip.Tanker.getCurrentTimeOnly(Tanker.java:746)
环境:Java 7
可能的原因是SimpleDateFormat
不是线程安全的事实,并且您从多个线程引用它.虽然极难证明(并且难以测试),但有证据表明情况如此:
.11331133EE22
- 注意一切都变了一倍
880044E.3880044E3
- 同样在这里
您可能至少有两个线程交错.将E
被扔我,我想它试图处理科学记数法(1E10等),但它是对可能部分时区.
值得庆幸的是,(格式化)基本修复很简单:
private static final String FORMAT_STRING = "HH:mm:ss.SSSZ"; public static Date getCurrentTimeOnly() { SimpleDateFormat formatter = new SimpleDateFormat(FORMAT_STRING); String onlyTimeStr = formatter.format(new Date()); return formatter.parse(onlyTimeStr); }
你可以在这里做一些其他的事情,但有一些注意事项:
1 - 如果时区是UTC(或任何没有DST的时区),这是微不足道的
public static Date getCurrentTimeOnly() { Date time = new Date(); time.setTime(time.getTime() % (24 * 60 * 60 * 1000)); return time; }
2 - 您将无法测试此方法,因为您无法安全地暂停时钟(您可以更改时区/区域设置).为了更好地处理Java中的日期/时间,请使用JodaTime之类的东西.请注意,LocalTime
没有附加时区,但Date
只返回整数小时的偏移量(并且小时内没有区域); 为了安全起见,你需要返回一个Calendar
(带有完整的时区),或者只返回没有它的东西:
// This method is now more testable. Note this is only safe for non-DST zones public static Calendar getCurrentTimeOnly() { Calendar cal = new Calendar(); // DateTimeUtils is part of JodaTime, and is a class allowing you to pause time! cal.setTimeInMillis(DateTimeUtils.currentTimeMillis() % (24 * 60 * 60 * 1000)); return cal; }