Подтвердить что ты не робот

Является ли java.time неспособным анализировать долю секунды?

С первым выпуском Java 8 (b132) в Mac OS X (Mavericks) этот код с использованием нового java.time package работает

String input = "20111203123456"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmss");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );

Рендеринг:

2011-12-03T12:34:56

Но когда я добавляю "SS" для доли секунды (и "55" в качестве ввода), как указано в DateTimeFormatter class doc, генерируется исключение:

java.time.format.DateTimeParseException: Text '2011120312345655' could not be parsed at index 0

Док говорит, что режим Strict используется по умолчанию и требует того же числа символов формата, что и входные цифры. Поэтому я смущен, почему этот код не работает:

String input = "2011120312345655"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmssSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );

Другой пример, использующий пример из документации ( "978" ) (сбой):

String input = "20111203123456978"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmssSSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );

В этом примере работает, добавив десятичную точку (но я не нашел такого требования в документе):

String input = "20111203123456.978"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmss.SSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );

Визуализирует:

localDateTime: 2011-12-03T12:34:56.978

Опускание символа периода либо из входной строки, либо из формата вызывает сбой.

Не удается:

String input = "20111203123456.978"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmssSSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );

Не удается:

String input = "20111203123456978"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyyMMddHHmmss.SSS");
LocalDateTime localDateTime = LocalDateTime.parse( input, formatter );
4b9b3361

Ответ 1

Ошибка - исправлено в Java 9

Эта проблема уже сообщалась в JDK-error-log. Стивен Коулборн упоминает об этом как решение:

DateTimeFormatter dtf = 
  new DateTimeFormatterBuilder()
  .appendPattern("yyyyMMddHHmmss")
  .appendValue(ChronoField.MILLI_OF_SECOND, 3)
  .toFormatter();

Примечание: Это обходное решение не охватывает ваш прецедент только двух символов шаблона SS. Настройкой может быть только использование других полей, таких как MICRO_OF_SECOND (6 раз SSSSSS) или NANO_OF_SECOND (9 раз SSSSSSSSS). Для двухзначных цифр см. Мое обновление ниже.

@PeterLawrey О значении символа шаблона "S" см. эта документация:

Фракция: выводит поле nano-of-second как долю секунды. Значение nano-of-second имеет девять цифр, поэтому количество шаблонов буквы от 1 до 9. Если оно меньше 9, то nano-of-second значение усекается, причем только самые значащие цифры являются вывод. При синтаксическом анализе в строгом режиме количество проанализированных цифр должно совпадают с количеством букв шаблонов. При разборе в режиме смягчения количество проанализированных цифр должно быть как минимум числом букв шаблонов, до 9 цифр.

Итак, мы видим, что S обозначает любую долю секунды (включая наносекунду), а не только миллисекунды. Кроме того, дробная часть в данный момент не очень хорошо подходит для смежного разбора значений, к сожалению.

EDIT:

В качестве фона можно привести некоторые замечания о смежном анализе значений. До тех пор, пока поля разделяются литералами, такими как десятичные точки или разделители временной части (двоеточие), интерпретация полей в анализируемом тексте не является сложной задачей, потому что синтаксический анализатор тогда легко знает, когда останавливаться, т.е. когда часть поля заканчивается и когда начинается следующее поле. Поэтому парсер JSR-310 может обрабатывать текстовую последовательность, если вы указываете десятичную точку.

Но если у вас есть последовательность смежных цифр, охватывающих несколько полей, возникают некоторые трудности с реализацией. Чтобы позволить парсеру знать, когда поле останавливается в тексте, необходимо заранее проинструктировать парсер, что данное поле представлено фиксированной шириной символов цифр. Это работает со всеми appendValue(...) -методами, которые принимают численные представления.

К сожалению, JSR-310 не сумел сделать это также с дробной частью (appendFraction(...)). Если вы ищете ключевое слово "смежное" в javadoc класса DateTimeFormatterBuilder, вы обнаружите, что эта функция ТОЛЬКО реализована с помощью appendValue(...) -methods. Обратите внимание, что спецификация для буквы шаблона S немного отличается, но внутренне делегируется методу appendFraction(). Я предполагаю, что мы, по крайней мере, должны будем связываться до Java 9 (как сообщается в JDK-журнале ошибок или позже) до тех пор, пока фракционные части не смогут управлять смежным анализом значений.


Обновление с 2015-11-25:

Следующий код, использующий две цифры разряда, не работает и выдает DateTimeParseException.

DateTimeFormatter dtf = 
  new DateTimeFormatterBuilder()
  .appendPattern("yyyyMMddHHmmssSS")
  .appendValue(ChronoField.MILLI_OF_SECOND, 2)
  .toFormatter();
String input = "2011120312345655"; 
LocalDateTime.parse(input, dtf); // abort

Обходной путь

String input = "2011120312345655"; 
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSS");
Date d = sdf.parse(input);
System.out.println(d.toInstant()); // 2011-12-03T12:34:56.055Z

не работает, потому что SimpleDateFormat интерпретирует дробь неправильно (см. вывод, 55 мс вместо 550 мс).

Что остается в качестве решения - либо ждать долгое время, дольше, чем Java 9 (или позже?), или писать собственный взлом или использовать сторонние библиотеки в качестве решения.

Решение, основанное на грязном взломе:

String input = "2011120312345655"; 
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
int len = input.length();
LocalDateTime ldt = LocalDateTime.parse(input.substring(0, len - 2),  dtf);
int millis = Integer.parseInt(input.substring(len - 2)) * 10;
ldt = ldt.plus(millis, ChronoUnit.MILLIS);
System.out.println(ldt); // 2011-12-03T12:34:56.550

Решение с использованием Joda-Time:

String input = "2011120312345655"; 
DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyyMMddHHmmssSS");
System.out.println(dtf.parseLocalDateTime(input)); // 2011-12-03T12:34:56.550

Решение с использованием моей библиотеки Time4J:

String input = "2011120312345655"; 
ChronoFormatter<PlainTimestamp> f = 
  ChronoFormatter.ofTimestampPattern("yyyyMMddHHmmssSS", PatternType.CLDR, Locale.ROOT);
System.out.println(f.parse(input)); // 2011-12-03T12:34:56.550

Обновление с 2016-04-29:

Как люди могут видеть через проблему JDK, упомянутую выше, теперь она помечена как разрешенная - для Java 9.

Ответ 2

Здесь приведен алгоритм, который корректирует порядок конечных нулей, которые обычно возвращаются из форматированной даты String.

/**
 * Takes a Date and provides the format whilst compensating for the mistaken representation of sub-second values.
 * i.e. 2017-04-03-22:46:19.000991 -> 2017-04-03-22:46:19.991000
 * @param pDate Defines the Date object to format.
 * @param pPrecision Defines number of valid subsecond characters contained in the system response.
 * */
private static final String subFormat(final Date pDate, final SimpleDateFormat pSimpleDateFormat, final int pPrecision) throws ParseException {
    // Format as usual.
    final String lString        = pSimpleDateFormat.format(pDate);
    // Count the number of characters.
    final String lPattern       = pSimpleDateFormat.toLocalizedPattern();
    // Find where the SubSeconds are.
    final int    lStart         = lPattern.indexOf('S');
    final int    lEnd           = lPattern.lastIndexOf('S');
    // Ensure they're in the expected format.
    for(int i = lStart; i <= lEnd; i++) { if(lPattern.charAt(i) != 'S') {
        // Throw an Exception; the date has been provided in the wrong format.
       throw new ParseException("Unable to process subseconds in the provided form. (" + lPattern + ").", i);
    } }
    // Calculate the number of Subseconds. (Account for zero indexing.)
    final int lNumSubSeconds = (lEnd - lStart) + 1;
    // Fetch the original quantity.
    String lReplaceString = lString.substring(lStart + (lNumSubSeconds - pPrecision), lStart + lNumSubSeconds);
    // Append trailing zeros.
    for(int i = 0; i < lNumSubSeconds - pPrecision; i++) { lReplaceString += "0"; }
    // Return the String.
    return lString.substring(0, lStart) + lReplaceString;
}

Ответ 3

DateTimeFormatterBuilder#appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true)

Как-то так мне помогло