NumberFormatException при разборе даты с помощью SimpleDateFormat.parse()

У вас есть функция, которая создает объект Date. (почему это требуется, это длинная история, которая не имеет отношения к этому контексту, но мне нужно сравнить с некоторыми материалами в мире XML, где ВРЕМЯ (то есть время) является действительной концепцией).

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), но мой вопрос действительно не в этом.

Мой вопрос заключается в том, что этот кусок кода начинает беспорядочно бросать NumberFormatException на строку # 8 после долгого запуска. Технически я бы сказал, что это должно быть невозможно, верно?

Здесь выдержка из случайных 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"

Прежде всего, я надеюсь, что мы можем согласиться с тем, что формально это должно быть невозможно? Код использует тот же формат (DF_TIMEONLY) как вывод, а затем вводит. Дайте мне знать, если вы не согласны с тем, что это невозможно.

Я не смог повторно создать проблему в автономной среде. Проблема, похоже, возникает, когда JVM работает в течение длительного времени ( > 1 неделя). Я не могу найти шаблон проблемы, то есть летнее время/зима, AM/PM и т.д. Ошибка является спорадической, а это означает, что в течение одной минуты она выкинет NumberFormatException и в следующую минуту она будет работать нормально.

Я подозреваю, что существует какая-то арифметическая неисправность где-то в JVM или даже в CPU. Вышеизложенные исключения указывают на наличие числа с плавающей запятой, но я не вижу, откуда они будут. Насколько я знаю, объект Java Date - это обертка вокруг long, которая содержит количество миллисов с эпохи.

Я предполагаю, что происходит то, что в строке # 5 создается непредвиденная строка onlyTimeStr, поэтому проблема действительно лежит здесь, а не в строке # 8.

Вот пример полного 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)

Среда: Java 7

Ответ 1

Вероятная причина заключается в том, что 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;
}

Ответ 2

Joda времени

FYI, библиотека Joda-Time 2.3 предоставляет класс явно для вашей цели только время, без любая дата: LocalTime. И поточно-безопасный (неизменяемые экземпляры). Кажется намного лучшим вариантом, чем манипулирование неудобным классом java.util.Date.

LocalTime localTime = new LocalTime();

Дамп для консоли...

System.out.println( "localTime: " + localTime );

При запуске...

localTime: 16:26:28.065

java.time

Java 8 предоставляет новый пакет java.time, вдохновленный Joda-Time, определяемый JSR 310.

В java.time вы найдете класс LocalTime, аналогичный классу в Joda-Time.

Ответ 3

У меня есть тот же вопрос, причина в том, что SimpleDateFormat не является потокобезопасным, я просто добавляю синхронизацию в методе, и это не повторится.

Ответ 4

Вы можете использовать "синхронизированный" блок, чтобы сделать его потокобезопасным. Что-то вроде:

synchronized (lastUpdatedFormat) {
            date = 
         lastUpdatedFormat.parse(lastUpdatedFormat.format(currentDate));
        }

Ответ 5

В то время как правильный ответ - Clockwork-Muse (причиной проблем является тот факт, что SimpleDateFormat не является потокобезопасным). Я просто хотел доставить другой метод создания объекта Date только по времени:

public static Date getCurrentTimeOnly() {

    Calendar rightNow = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
    int hour = rightNow.get(Calendar.HOUR_OF_DAY);
    int minute = rightNow.get(Calendar.MINUTE);
    int second = rightNow.get(Calendar.SECOND);
    int msecond = rightNow.get(Calendar.MILLISECOND);

    long millisSinceMidnight
            = (hour * 60 * 60 * 1000)
            + (minute * 60 * 1000)
            + (second * 1000)
            + (msecond);
    return new Date(millisSinceMidnight);
}

Этот метод несколько более формально корректен, т.е. обрабатывает прыжок-секунды. Он не предполагает, как и другие методы, что все дни, прошедшие с эпохи, всегда имели в них 24 * 60 * 60 * 1000 миллисекунд.

Однако он не обрабатывает случай, когда секундный прыжок находится в текущий день.