web-dev-qa-db-fra.com

Java.time ne parvient-il pas à analyser une fraction de seconde?

Avec la première version de Java 8 (b132) sur Mac OS X (Mavericks), ce code utilisant le nouveau package Java.time fonctionne:

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

Le rendu:

2011-12-03T12:34:56

Mais lorsque j'ajoute "SS" pour une fraction de seconde (et "55" en entrée), comme spécifié dans le doc de la classe DateTimeFormatter , une exception est levée:

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

Le document indique que le mode Strict est utilisé par défaut et nécessite le même nombre de caractères de format que les chiffres d'entrée. Je suis donc confus pourquoi ce code échoue:

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

Un autre exemple utilisant l'exemple de la documentation ("978") (échoue):

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

Cet exemple fonctionne, en ajoutant un point décimal (mais je ne trouve aucune telle exigence dans le document):

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

Rendu:

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

L'omission du caractère point dans la chaîne d'entrée ou le format provoque un échec.

Échoue:

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

Échoue:

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

Bug - Corrigé dans Java 9

Ce problème a déjà été signalé dans JDK-bug-log . Stephen Colebourne mentionne comme solution de contournement la solution suivante:

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

Remarque: Cette solution de contournement ne couvre pas votre cas d'utilisation de seulement deux symboles de modèle SS. Un ajustement peut n'être que pour utiliser d'autres champs comme MICRO_OF_SECOND (6 fois SSSSSS) ou NANO_OF_SECOND (9 fois SSSSSSSSS). Pour deux chiffres de fraction, voir ma mise à jour ci-dessous.

@PeterLawrey À propos de la signification du symbole de motif "S", voir cette documentation :

Fraction: génère le champ de nano-seconde sous forme de fraction de seconde. La valeur de la nano de seconde a neuf chiffres, donc le nombre de lettres de modèle va de 1 à 9. Si elle est inférieure à 9, alors la valeur de la nano de seconde est tronquée, seuls les chiffres les plus significatifs étant sortis. Lors de l'analyse en mode strict, le nombre de chiffres analysés doit correspondre au nombre de lettres de modèle. Lors de l'analyse en mode indulgent, le nombre de chiffres analysés doit être au moins le nombre de lettres de modèle, jusqu'à 9 chiffres.

Nous voyons donc que S représente n'importe quelle fraction de seconde (y compris la nanoseconde), pas seulement les millisecondes. De plus, la partie fractionnaire ne prend pas bien pour le moment dans l'analyse de valeurs adjacentes, malheureusement.

MODIFIER:

Comme toile de fond ici quelques remarques sur l'analyse de valeur adjacente. Tant que les champs sont séparés par des littéraux comme une virgule décimale ou des séparateurs de parties temporelles (deux-points), l'interprétation des champs dans un texte à analyser n'est pas difficile car l'analyseur sait alors facilement quand s'arrêter, c'est-à-dire quand la partie de champ est terminée et lorsque le champ suivant démarre. Par conséquent, l'analyseur JSR-310 peut traiter la séquence de texte si vous spécifiez une virgule décimale.

Mais si vous avez une séquence de chiffres adjacents s'étendant sur plusieurs champs, des difficultés d'implémentation surviennent. Afin de permettre à l'analyseur de savoir quand un champ s'arrête dans le texte, il est nécessaire d'informer l'analyseur à l'avance qu'un champ donné est représenté par une largeur fixe de caractères numériques. Cela fonctionne avec toutes les méthodes appendValue(...)- qui supposent des représentations numériques.

Malheureusement, JSR-310 n'a pas réussi à le faire également avec la partie fractionnaire (appendFraction(...)). Si vous recherchez le mot-clé "adjacent" dans le javadoc de la classe DateTimeFormatterBuilder, vous constatez que cette fonctionnalité est UNIQUEMENT réalisée par les méthodes appendValue(...)-. Notez que la spécification pour la lettre de modèle S est légèrement différente mais délègue en interne à la méthode appendFraction()-. Je suppose que nous devrons au moins attendre jusqu'à Java 9 (comme indiqué dans JDK-bug-log, ou plus tard ???)) jusqu'à ce que les parties de fraction puissent également gérer l'analyse des valeurs adjacentes.


Mise à jour du 25/11/2015:

Le code suivant utilisant uniquement deux chiffres de fraction ne fonctionne pas et génère un DateTimeParseException.

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

La solution

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

ne fonctionne pas car SimpleDateFormat interprète la fraction de manière incorrecte (voir sortie, 55 ms au lieu de 550 ms).

Ce qui reste comme solution est soit d'attendre longtemps sous-estimé jusqu'à Java 9 (ou plus tard?)) Soit d'écrire votre propre hack ou d'utiliser des bibliothèques tierces comme solution.

Solution basée sur un hack sale:

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

Solution utilisant Joda-Time :

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

Solution utilisant ma bibliothèque 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

Mise à jour du 29/04/2016:

Comme les gens peuvent le voir via le problème JDK mentionné ci-dessus, il est maintenant marqué comme résolu - pour Java 9 .

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

Quelque chose comme ça m'a aidé

1
Andrei Amarfii

Voici un algorithme qui ajuste l'ordre des zéros de fin qui sont conventionnellement renvoyés à partir de la date formatée 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;
}
0
Mapsy