Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NumberFormatException while parsing date with SimpleDateFormat.parse()

Tags:

java

date

Have a function that creates a time-only Date object. (why this is required is a long story which is irrelevant in this context but I need to compare to some stuff in XML world where TIME (i.e. time-only) is a valid concept).

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; } 

There are probably at least a couple other ways to create a time-only Date in Java (or more precisely one where the date part is 1970-01-01) but my question is really not about that.

My question is that this piece of code starts randomly throwing NumberFormatException on line #8 after having run in production for long time. Technically I would say that this should be impossible, right ?

Here's an extract of random NumberFormatExceptions that come from above piece of code:

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" 

First of all I hope we can agree that formally this should be impossible? The code uses the same format (DF_TIMEONLY) as output and then input. Let me know if you disagree that it should be impossible.

I haven't been able to re-produce the problem in a standalone environment. The problem seems to come when the JVM has run for a long time (>1 week). I cannot find a pattern to the problem, i.e. summer time / winter time, AM/PM, etc. The error is sporadic, meaning that one minute it will throw NumberFormatException and the next minute it will run fine.

I suspect that there's some kind of arithmetic malfunction somewhere in either the JVM or perhaps even in the CPU. The above exceptions suggests that there's floating point numbers involved but I fail to see where they would come from. As far as I know Java's Date object is a wrapper around a long which holds the number of millis since the epoch.

I'm guessing what is happening is that there's an unexpected string onlyTimeStr created in line #5 so the problem really lies here rather than in line #8.

Here's an example of a full stacktrace:

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) 

Environment: Java 7

like image 442
peterh Avatar asked Jan 09 '14 10:01

peterh


People also ask

What is parse in SimpleDateFormat parse do?

The parse() Method of SimpleDateFormat class is used to parse the text from a string to produce the Date. The method parses the text starting at the index given by a start position.

How do you parse a date?

parse() The Date. parse() method parses a string representation of a date, and returns the number of milliseconds since January 1, 1970, 00:00:00 UTC or NaN if the string is unrecognized or, in some cases, contains illegal date values (e.g. 2015-02-31). Only the ISO 8601 format ( YYYY-MM-DDTHH:mm:ss.

What does SimpleDateFormat return?

SimpleDateFormat format() Method in Java with Examples Return Value: The method returns Date or time in string format of mm/dd/yyyy.


1 Answers

The likely cause is the fact that SimpleDateFormat isn't threadsafe, and you're referencing it from multiple threads. While extremely difficult to prove (and about as hard to test for), there is some evidence this is the case:

  1. .11331133EE22 - notice how everything is doubled
  2. 880044E.3880044E3 - same here

You probably have at least two threads interleaving. The E was throwing me, I was thinking it was attempting to deal with scientific notation (1E10, etc), but it's likely part of the time zone.

Thankfully, the (formatting) basic fix is simple:

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); } 

There's a couple of other things you could be doing here, too, with a few caveats:

1 - If the timezone is UTC (or any without DST), this is trivial

public static Date getCurrentTimeOnly() {      Date time = new Date();      time.setTime(time.getTime() % (24 * 60 * 60 * 1000));      return time; } 

2 - You're going to have trouble testing this method, because you can't safely pause the clock (you can change the timezone/locale). For a better time dealing with date/time in Java, use something like JodaTime. Note that LocalTime doesn't have a timezone attached, but Date only returns an offset in integer hours (and there are zones not on the hour); for safety, you need to either return a Calendar (with the full timezone), or just return something without it:

// 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; } 
like image 187
Clockwork-Muse Avatar answered Sep 28 '22 06:09

Clockwork-Muse