O java.time não consegue analisar fração de segundo?

Com o primeiro lançamento do Java 8 (b132) no Mac OS X (Mavericks), este código usando o novo pacote java.time funciona:

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

Renderização:

 2011-12-03T12:34:56 

Mas quando eu adiciono “SS” por fração de segundo (e “55” como input), conforme especificado no documento de class DateTimeFormatter , uma exceção é lançada:

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

O documento diz que o modo Strict é usado por padrão e requer o mesmo número de caracteres de formato dos dígitos de input. Então estou confuso porque esse código falha:

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

Outro exemplo usando o exemplo da documentação (“978”) (falha):

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

Este exemplo funciona, adicionando um ponto decimal (mas não encontro esse requisito no documento):

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

Renders:

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

Omitir o caractere de período da cadeia de input ou do formato causa uma falha.

Falha:

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

Falha:

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

Bug – corrigido no Java 9

Esse problema já foi relatado no JDK-bug-log . Stephen Colebourne menciona como solução alternativa:

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

Nota: Esta solução alternativa não cobre seu caso de uso de apenas dois símbolos padrão SS. Um ajuste pode ser apenas para usar outros campos como MICRO_OF_SECOND (6 vezes SSSSSS) ou NANO_OF_SECOND (9 vezes SSSSSSSSS). Para dois dígitos de fração, veja minha atualização abaixo.

@PeterLawrey Sobre o significado do símbolo de padrão “S”, veja esta documentação :

Fração: Gera o campo nano de segundo como uma fração de segundo. O valor nano-de-segundo tem nove dígitos, portanto, a contagem de letras padrão é de 1 a 9. Se for menor que 9, o valor nano-de-segundo é truncado, com apenas os dígitos mais significativos sendo emitidos. Ao analisar no modo estrito, o número de dígitos analisados ​​deve corresponder à contagem de letras padrão. Ao analisar no modo leniente, o número de dígitos analisados ​​deve ser pelo menos a contagem de letras padrão, até 9 dígitos.

Então, vemos que S significa qualquer fração de segundo (incluindo nanossegundos), não apenas milissegundos. Além disso, a parte fracionária, no momento, não leva bem na análise de valor adjacente, infelizmente.

EDITAR:

Como pano de fundo aqui algumas observações sobre análise de valor adjacente. Contanto que os campos sejam separados por literais como um ponto decimal ou separadores de partes de tempo (dois pontos), a interpretação de campos em um texto a ser analisado não é difícil porque o analisador então sabe facilmente quando parar, ou seja, quando a parte do campo é finalizada e quando o próximo campo começa. Portanto, o analisador JSR-310 pode processar a seqüência de texto se você especificar um ponto decimal.

Mas se você tiver uma sequência de dígitos adjacentes abrangendo vários campos, algumas dificuldades de implementação surgirão. Para que o analisador saiba quando um campo para no texto, é necessário instruir o analisador previamente de que um determinado campo é representado por uma largura fixa de caracteres de dígito. Isso funciona com todos os appendValue(...) que assumem representações numéricas.

Infelizmente, o JSR-310 também não conseguiu fazer isso com a parte fracionária ( appendFraction(...) ). Se você procurar a palavra-chave “adjacente” no javadoc da class DateTimeFormatterBuilder , appendValue(...) que esse recurso é SOMENTE realizado por appendValue(...) -methods. Observe que a especificação da letra padrão S é um pouco diferente, mas internamente delega ao appendFraction() . Eu suponho que pelo menos teremos que ir embora até o Java 9 (como relatado no JDK-bug-log, ou mais tarde ???) até que partes fracionárias possam gerenciar a análise de valor adjacente também.


Atualização de 2015-11-25:

O código a seguir usando dois dígitos de fração só não funciona e lança um DateTimeParseException .

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

A solução alternativa

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

não funciona porque SimpleDateFormat interpreta a fração de maneira errada (veja output, 55 ms em vez de 550 ms).

O que resta como solução é aguardar um longo tempo até o Java 9 (ou posterior?) Ou escrever seu próprio hack ou usar bibliotecas de terceiros como solução.

Solução baseada em um hack sujo:

 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 

Solução usando o Joda-Time :

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

Solução usando minha biblioteca Time4J :

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

Atualização de 2016-04-29:

Como as pessoas podem ver através do problema do JDK mencionado acima, ele agora está marcado como resolvido – para o Java 9 .

Aqui está um algoritmo que ajusta a ordem dos zeros à direita que são retornados convencionalmente da data String formatada.

 /** * Takes a Date and provides the format whilst compensating for the mistaken representation of sub-second values. * ie 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; }