web-dev-qa-db-fra.com

Java 8 Date et heure: analyse la chaîne ISO 8601 sans deux points en décalage

Nous essayons d'analyser la chaîne DateTime ISO 8601 suivante avec décalage de fuseau horaire:

final String input = "2022-03-17T23:00:00.000+0000";

OffsetDateTime.parse(input);
LocalDateTime.parse(input, DateTimeFormatter.ISO_OFFSET_DATE_TIME);

Les deux approches échouent (ce qui est logique car OffsetDateTime utilise également le DateTimeFormatter.ISO_OFFSET_DATE_TIME) À cause des deux points dans le décalage du fuseau horaire.

Java.time.format.DateTimeParseException: le texte '2022-03-17T23: 00: 00.000 + 0000' n'a pas pu être analysé à l'index 23

Mais selon Wikipedia il existe 4 formats valides pour un décalage de fuseau horaire:

<time>Z 
<time>±hh:mm 
<time>±hhmm 
<time>±hh

D'autres frameworks/langages peuvent analyser cette chaîne sans aucun problème, par exemple Javascript Date() ou Jacksons ISO8601Utils (ils discutent de ce problème ici )

Maintenant, nous pourrions écrire notre propre DateTimeFormatter avec un RegEx complexe, mais à mon avis, la bibliothèque Java.time Devrait être capable d'analyser cette chaîne ISO 8601 valide par défaut car elle est valide.

Pour l'instant, nous utilisons Jacksons ISO8601DateFormat, Mais nous préférons utiliser la bibliothèque officielle date.time Pour travailler avec. Quelle serait votre approche pour résoudre ce problème?

18
u_b

Si vous souhaitez analyser tous les formats valides de décalages (Z, ±hh:mm, ±hhmm et ±hh), une alternative consiste à utiliser un Java.time.format.DateTimeFormatterBuilder avec des motifs optionnels (malheureusement, il semble qu'il n'y ait pas de lettre de motif unique pour tous les faire correspondre):

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
    // date/time
    .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
    // offset (hh:mm - "+00:00" when it's zero)
    .optionalStart().appendOffset("+HH:MM", "+00:00").optionalEnd()
    // offset (hhmm - "+0000" when it's zero)
    .optionalStart().appendOffset("+HHMM", "+0000").optionalEnd()
    // offset (hh - "Z" when it's zero)
    .optionalStart().appendOffset("+HH", "Z").optionalEnd()
    // create formatter
    .toFormatter();
System.out.println(OffsetDateTime.parse("2022-03-17T23:00:00.000+0000", formatter));
System.out.println(OffsetDateTime.parse("2022-03-17T23:00:00.000+00", formatter));
System.out.println(OffsetDateTime.parse("2022-03-17T23:00:00.000+00:00", formatter));
System.out.println(OffsetDateTime.parse("2022-03-17T23:00:00.000Z", formatter));

Les quatre cas ci-dessus l'analyseront pour 2022-03-17T23:00Z.


Vous pouvez également définir un modèle de chaîne unique si vous le souhaitez, à l'aide de [] pour délimiter les sections facultatives:

// formatter with all possible offset patterns
DateTimeFormatter formatter = DateTimeFormatter
    .ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS[xxx][xx][X]");

Ce formateur fonctionne également pour tous les cas, tout comme le formateur précédent ci-dessus. Vérifiez le javadoc pour obtenir plus de détails sur chaque modèle.


Remarques:

  • Un formateur avec des sections optionnelles comme ci-dessus est bon pour l'analyse, mais pas pour le formatage. Lors du formatage, il imprimera toutes les sections facultatives, ce qui signifie qu'il imprimera l'offset plusieurs fois. Donc, pour formater la date, utilisez simplement un autre formateur.
  • Le deuxième formateur accepte exactement 3 chiffres après la virgule décimale (en raison de .SSS). D'autre part, ISO_LOCAL_DATE_TIME est plus flexible: les secondes et les nanosecondes sont facultatives, et il accepte également de 0 à 9 chiffres après la virgule décimale. Choisissez celui qui convient le mieux à vos données d'entrée.
24
user7605325

Vous n'avez pas besoin d'écrire une expression rationnelle complexe - vous pouvez créer un DateTimeFormatter qui fonctionnera facilement avec ce format:

DateTimeFormatter formatter =
    DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSX", Locale.ROOT);

OffsetDateTime odt = OffsetDateTime.parse(input, formatter);

Cela acceptera également "Z" au lieu de "0000". Il acceptera pas "+00: 00" (avec les deux-points ou similaire. C'est surprenant compte tenu de la documentation, mais si votre valeur a toujours le décalage UTC sans les deux-points, cela devrait être correct.

4
Jon Skeet