2014-01-09 5 views
15

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

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 on line # 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. Вышеизложенные исключения показывают, что есть числа с плавающей запятой, но я не вижу, откуда они будут. Насколько я знаю, объект Date Java является оберткой вокруг long, которая содержит количество миллисов с эпохи.

Я предполагаю, что происходит непредвиденная строка onlyTimeStr, созданная в строке # 5, поэтому проблема действительно лежит здесь, а не в строке # 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

И что еще? Вы ловите ParseException или что? Каков ваш вопрос? Я не получаю никаких ошибок при запуске вашего фрагмента. – sp00m

+7

Что такое stacktrace? –

+0

Где проблема? – Sergi

ответ

28

Вероятной причиной является тот факт, что SimpleDateFormat не поточно, и вы ссылаться на него из нескольких потоков. Хотя крайне трудно доказать (и о том, как трудно проверить), есть некоторые доказательства того, что это так:

  1. .11331133EE22 - обратите внимание, как все удваивается
  2. 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 (или без ДСТ), это тривиально

public static Date getCurrentTimeOnly() { 

    Date time = new Date(); 

    time.setTime(time.getTime() % (24 * 60 * 60 * 1000)); 

    return time; 
} 

2 - Вы будете иметь проблемы тестирование этого метода, поскольку вы не можете безопасно приостановить часы (вы можете изменить часовой пояс/локаль). Для лучшего времени, связанного с датой/временем в Java, используйте что-то вроде JodaTime. Обратите внимание, что LocalTime не привязан к часовому поясу, но Date возвращает только смещение в целых часов (и есть zones not on the hour); для безопасности, вам необходимо либо возвращать 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; 
} 
+0

Nah, я не знаю, откуда приходит «E». Спецификатор формата часового пояса «Z», поэтому может быть только знак цифры, двоеточия и вилки. В любом случае, кто знает, что происходит, когда два потока чередуются – peterh

+0

Я принимаю это как правильный ответ. Мне следовало подумать: при работе со странными/спорадическими ошибками он почти всегда связан с проблемами параллелизма. Любой, кто имеет дело с 'SimpleDateFormat' (или просто' DateFormat'), должен делать Google поиск по этим классам вместе с «защитой от протектора». Судя по сообщениям, вы можете обнаружить, что я не первый, кто попадает в эту ловушку. Интересно, как многие разработчики Java НЕ знают об этом. – peterh

+0

Btw: Does not ваш 'milliSinceEpoch% (24 * 60 * 60 * 1000)' трюк предполагает, что ** всегда ** 86400000 мсек в день ?. Это неверно. – peterh

0

Хотя правильный ответ является один заводным-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); 
} 

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

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

+0

Ни в коем случае, juGregorianCalendar НЕ обрабатывает секунды прыжка. Просто изучите его реализацию. И j.u.Date также не осведомлен и никогда не считает секунды прыжка. –

+0

@MenoHochschild. Вы правы. Но 'GregorianCalendar' не нужно обрабатывать секунды прыжка для приведенного выше кода, чтобы вернуть то, что он должен, за исключением случая, упомянутого выше (прыжок в текущий день). – peterh

1

Joda время

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

LocalTime localTime = new LocalTime(); 

самосвала утешать ...

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

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

localTime: 16:26:28.065 

java.time

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

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

0

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

0

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

synchronized (lastUpdatedFormat) { 
      date = 
     lastUpdatedFormat.parse(lastUpdatedFormat.format(currentDate)); 
     } 
Смежные вопросы