Дали java.time не успява да анализира част от секундата?

С първото издание на Java 8 (b132) на Mac OS X (Mavericks), този код използва новия пакет java.time работи:

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

Изобразяване:

2011-12-03T12:34:56

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

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

В документа се казва, че строг режим се използва по подразбиране и изисква същия брой форматни знаци като входните цифри. Така че съм объркан защо този код е неуспешен:

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

person Basil Bourque    schedule 23.03.2014    source източник
comment
Тази част е нано секунда. Можете ли да опитате с 9 цифри? Въпреки че от четене на документите би трябвало да работи с по-малко от 9 цифри, стига шаблонът да съответства на броя на цифрите.   -  person anonymous    schedule 23.03.2014
comment
След като прочетете отново документите, можете ли да опитате с десетична запетая след полето за секунди? Като 20111203123456.978?   -  person anonymous    schedule 23.03.2014
comment
Добавянето на десетична точка го кара да работи. Вижте допълнението в долната част на въпроса. Но можете ли да ме насочите към такова изискване в документа? Опитвам се да определя дали трябва да подам доклад за грешка.   -  person Basil Bourque    schedule 23.03.2014
comment
Първо, хубаво е, че ти подейства. Второ, не го бях виждал конкретно написано, а по-скоро като моя интерпретация на част от секундата. Част от секундата трябва да се предхожда от десетична запетая. Няма смисъл да няма десетичен знак. За да направите сравнение с времето HH:mm:ss, двоеточие обикновено се използва като разделител, но не е част от полето. Следователно двоеточие не е задължително, защото не е част от поле. Но част от секундата е част от полето за секунди. Така че трябва да бъде разделено с десетична запетая. Аз така го тълкувам. Но не се колебайте да регистрирате доклад за грешка, ако все още смятате, че има грешка.   -  person anonymous    schedule 23.03.2014
comment
Защо част от секундата заслужава пунктуация (точка) повече от минута от часа заслужава двоеточие? Не се закачам.   -  person Basil Bourque    schedule 23.03.2014
comment
Както казах, защото част от секундата е ЧАСТ ОТ полето за секунди. Това не е просто разделител, както двоеточието разделя часовете, минутите и секундите. Въпреки това можете също да спорите, тъй като това е дроб, десетичната запетая се предполага. От друга страна, дизайнерите може също да смятат, че десетичната запетая е необходима, когато се посочва дроб. Какво можете да опитате, за да видите дали това все още работи. 20111203123456 .978 и да приложите yyyyMMddHHmmss SSS?   -  person anonymous    schedule 23.03.2014
comment
@анонимен (a) Разбирам вашата интуитивна логика за част от секундата, принадлежаща към секундата от минутата. Но документът ясно държи част от секундата като отделен обект като всички останали части. Така че мисля да продължа с доклада за грешки. (b) Опитах последното ви предложение, като пропуснах десетичната запетая или от входа, или от формататора. И двете причиняват неуспех.   -  person Basil Bourque    schedule 23.03.2014
comment
Мисля, че етикетът java-time може би не е толкова ясен. Под този етикет намираме също записи, които нямат нищо общо с новия API за дата и час в Java 8. Тук на SO етикетът jsr310 е много по-често срещан досега, въпреки че признавам, че името по-скоро говори на експертите.   -  person Meno Hochschild    schedule 24.03.2014
comment
@MenoHochschild благодаря за съвета. Ще добавя маркера jsr310 и ще помисля за създаване на нов етикет.   -  person Basil Bourque    schedule 24.03.2014
comment
Добавих нов етикет java.time с изчакващ извадка от wiki.   -  person Basil Bourque    schedule 24.03.2014
comment
@BasilBourque, моля, допринесете това wiki към съществуващия таг, вместо да създавате нов.   -  person Charles    schedule 25.03.2014
comment
@Charles Като се има предвид казаното от Meno за това, че java-time се използва исторически за други цели преди JSR 310 API, изглежда подходящо да се създаде нов таг, специално насочен към новия API и наречен по същия начин като новия API. Старият е използван само 7 пъти. Защо не изтриете/игнорирате стария и не създадете нов с име, което съвпада с името на пакета на API? Прочетох 3 записа в Meta StackExchange и не намерих указания за обратното. Като цяло разбирам загрижеността ви да не замърсявате пула от маркери, но тази ситуация с java.time JSR 310 изглежда уникална.   -  person Basil Bourque    schedule 25.03.2014
comment
@BasilBourque, един от големите проблеми е, че системата за маркери SE не взема предвид . по същия начин разглежда други препинателни знаци. Хората, които търсят java-time, няма да видят java.time и има ужасна комбинация от етикети на различни места, които използват един вместо друг. Ако смятате, че тагът с точката ще бъде по-полезен, тогава нека напълно заличим другия таг, за да премахнем неяснотата.   -  person Charles    schedule 25.03.2014


Отговори (3)


Грешка – коригирана в Java 9

Този проблем вече беше докладван в JDK-bug-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 вижте тази документация:

Фракция: Извежда полето нано-от секунда като част от секундата. Стойността на нано-от секундата има девет цифри, така че броят на буквите на модела е от 1 до 9. Ако е по-малко от 9, тогава стойността на нано-от секундата се съкращава, като се извеждат само най-значимите цифри. Когато анализирате в строг режим, броят на анализираните цифри трябва да съвпада с броя на буквите в шаблона. При синтаксичен анализ в снизходителен режим броят на анализираните цифри трябва да бъде най-малко броят на буквите на модела, до 9 цифри.

Така че виждаме, че S означава всяка част от секундата (включително наносекунда), а не само милисекунди. Освен това, за съжаление, дробната част в момента не се приема добре при анализиране на съседна стойност.

РЕДАКТИРАНЕ:

Като фон тук има някои забележки относно анализирането на съседни стойности. Докато полетата са разделени от литерали като десетична запетая или разделители на част от времето (двоеточие), интерпретацията на полетата в текст, който трябва да се анализира, не е трудна, тъй като след това анализаторът знае лесно кога да спре, т.е. когато частта от полето приключи и когато започне следващото поле. Следователно JSR-310 анализаторът може да обработи текстовата последователност, ако посочите десетична точка.

Но ако имате поредица от съседни цифри, обхващащи множество полета, тогава възникват някои трудности при изпълнението. За да уведомите анализатора, когато едно поле спира в текст, е необходимо предварително да инструктирате анализатора, че дадено поле е представено от цифри с фиксирана ширина. Това работи с всички appendValue(...)-методи, които приемат числени представяния.

За съжаление JSR-310 не успя да направи това и с дробната част (appendFraction(...)). Ако потърсите съседната ключова дума в javadoc на клас DateTimeFormatterBuilder, ще откриете, че тази функция се реализира САМО от appendValue(...)-методи. Обърнете внимание, че спецификацията за шаблонна буква S е малко по-различна, но вътрешно се делегира на appendFraction()-method. Предполагам, че поне ще трябва да изчакаме до Java 9 (както е съобщено в JDK-bug-log или по-късно???), докато дробните части не могат да управляват и анализирането на съседни стойности.


Актуализация от 2015-11-25:

Следният код, използващ само две дробни цифри, не работи и тълкува неправилно частта от милисекунди:

    DateTimeFormatter dtf =
        new DateTimeFormatterBuilder()
            .appendPattern("yyyyMMddHHmmss")
            .appendValue(ChronoField.MILLI_OF_SECOND, 2)
            .toFormatter();
    String input = "2011120312345655";
    LocalDateTime ldt = LocalDateTime.parse(input, dtf);
    System.out.println(ldt); // 2011-12-03T12:34:56.055

Заобиколното решение

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 ms вместо 550 ms).

Това, което остава като решение, е или да изчакате дълго време до 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.

person Meno Hochschild    schedule 24.03.2014
comment
Във вашата актуализация от 2015-11-25 казвате, че този форматиращ инструмент хвърля DateTimeParseException: new DateTimeFormatterBuilder().appendPattern("yyyyMMddHHmmssSS").appendValue(ChronoField.MILLI_OF_SECOND, 2). Но не хвърля изключение, ако премахнете SS в края на модела (което не трябва да е там, тъй като използвате appendValue() за милисекунди). Единственият проблем е, че интерпретира дроба неправилно, връщайки 55 вместо 550, като SimpleDateFormat. - person Etienne Neveu; 06.11.2020
comment
@EtienneNeveu Благодаря ви много. Сега коригирах твърдението си от 2015 г. според вашия коментар. - person Meno Hochschild; 07.11.2020

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

Нещо подобно ми помогна

person Andrei Amarfii    schedule 16.05.2019

Ето алгоритъм, който коригира реда на нулите в края, които обикновено се връщат от форматираната дата 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's 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;
}
person Mapsy    schedule 04.04.2017
comment
Отговорите на Stack Overflow са по-добри, когато включват дискусия или обяснение. - person Basil Bourque; 04.04.2017
comment
@BasilBourque Абсолютно си прав. Надявах се коментарите да говорят сами за себе си, но си прав, че най-малкото заслужава някакъв контекст. - person Mapsy; 04.04.2017