peterh peterh - 4 months ago 20
Java Question

NumberFormatException while parsing 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

Answer

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