web-dev-qa-db-fra.com

SimpleDateFormat avec les paramètres régionaux allemands - Java 8 vs Java 10+

J'ai du code et un cas de test dans une application héritée , qui peut être résumée comme suit:

@Test
public void testParseDate() throws ParseException {
    String toParse = "Mo Aug 18 11:25:26 MESZ +0200 2014";
    String pattern = "EEE MMM dd HH:mm:ss z Z yyyy";

    DateFormat dateFormatter = new SimpleDateFormat(pattern, Locale.GERMANY);
    Date date = dateFormatter.parse(toParse);

    //skipped assumptions
}

Ce test passe en Java 8 et ci-dessous. Cependant avec Java 10 vers le haut, cela conduit à un Java.text.ParseException: Unparseable date: "Mo Aug 18 11:25:26 MESZ +0200 2014".

Pour mémoire: Outre de_DE, l'exception est également levée pour les paramètres régionaux de_CH, de_AT, de_LU.

Je suis conscient du fait que le formatage de la date était modifié avec JDK 9 ( JEP 252 ). Cependant, je considère que c'est un changement perturbateur qui rompt la compatibilité descendante. Extrait:

Dans JDK 9, les données du Common Locale Data Repository (CLDR) du Consortium Unicode sont activées en tant que données de paramètres régionaux par défaut, de sorte que vous pouvez utiliser les données de paramètres régionaux standard sans autre action.

Dans JDK 8, bien que les données locales CLDR soient regroupées avec le JRE, elles ne sont pas activées par défaut.

Le code qui utilise des services sensibles aux paramètres régionaux tels que la mise en forme de la date, de l'heure et des nombres peut produire des résultats différents avec les données de paramètres régionaux CLDR.

Ajout d'un . pour le jour de la semaine (Mo.) compense cela et le test passe. Cependant, ce n'est pas une vraie solution pour les anciennes données (sous forme sérialisée comme XML).

En vérifiant cela stackoverflow post , il semble que le comportement soit intentionnel pour les paramètres régionaux allemands et peut être atténué en spécifiant Java.locale.providers avec le mode COMPAT. Cependant, je n'aime pas l'idée de s'appuyer sur une valeur de propriété système pour deux raisons:

  1. changement dans les prochaines versions du JDK.
  2. être oublié dans des environnements différents.

Ma question est:

  • Comment puis-je conserver la compatibilité descendante du code hérité avec ce modèle de date particulier, sans réécrire/modifier les données sérialisées existantes ni ajouter/modifier les propriétés du système (comme Java.locale.providers), qui peut être oublié dans différents environnements (serveurs d'applications, jars autonomes, ...)?
49
rzo

Je ne dis pas que c'est une bonne solution, mais cela semble être une solution.

    Map<Long, String> dayOfWeekTexts = Map.of(1L, "Mo", 2L, "Di", 
            3L, "Mi", 4L, "Do", 5L, "Fr", 6L, "Sa", 7L, "So");
    Map<Long, String> monthTexts = Map.ofEntries(Map.entry(1L, "Jan"), 
            Map.entry(2L, "Feb"), Map.entry(3L, "Mär"), Map.entry(4L, "Apr"),
            Map.entry(5L, "Mai"), Map.entry(6L, "Jun"), Map.entry(7L, "Jul"),
            Map.entry(8L, "Aug"), Map.entry(9L, "Sep"), Map.entry(10L, "Okt"),
            Map.entry(11L, "Nov"), Map.entry(12L, "Dez"));

    DateTimeFormatter formatter = new DateTimeFormatterBuilder()
            .appendText(ChronoField.DAY_OF_WEEK, dayOfWeekTexts)
            .appendLiteral(' ')
            .appendText(ChronoField.MONTH_OF_YEAR, monthTexts)
            .appendPattern(" dd HH:mm:ss z Z yyyy")
            .toFormatter(Locale.GERMANY);

    String toParse = "Mo Aug 18 11:25:26 MESZ +0200 2014";
    OffsetDateTime odt = OffsetDateTime.parse(toParse, formatter);
    System.out.println(odt);
    ZonedDateTime zdt = ZonedDateTime.parse(toParse, formatter);
    System.out.println(zdt);

Sortie en cours d'exécution sur mon Oracle JDK 10.0.1:

2014-08-18T11:25:26+02:00
2014-08-18T11:25:26+02:00[Europe/Berlin]

Là encore, aucune solution de Nice ne peut exister.

Java.time, La moderne Java, nous permet de spécifier les textes à utiliser pour les champs à la fois pour le formatage et l'analyse. Je l'exploite donc pour le jour de la semaine et pour mois, en spécifiant les abréviations sans point qui ont été utilisées avec les anciennes données locales COMPAT ou JRE. J'ai utilisé le Java 9 Map.of et Map.ofEntries pour construire le les cartes dont nous avons besoin. Si cela doit fonctionner dans Java 8 aussi, vous devez trouver un autre moyen de remplir les deux cartes, je vous fais confiance pour le faire.

Si vous avez besoin d'un Java.util.Date À l'ancienne (probablement dans une base de code héritée), convertissez comme ceci:

    Date date = Date.from(odt.toInstant());
    System.out.println("As legacy Date: " + date);

Sortie dans mon fuseau horaire (Europe/Copenhague, probablement en gros d'accord avec le vôtre):

As legacy Date: Mon Aug 18 11:25:26 CEST 2014

Suggestion de stratégie

Je pense que si c'était moi, j'envisagerais de procéder de cette façon:

  1. Attendez . Définissez la propriété système appropriée à partir de Java: System.setProperty("Java.locale.providers", "COMPAT,CLDR"); afin qu'elle ne soit pas oubliée dans aucun environnement. Les données locales COMPAT existent depuis la version 1.0 (je crois, au moins proches), donc beaucoup de code en dépend (pas seulement le vôtre). Le nom a été changé de JRE à COMPAT en Java 9. Pour moi, cela peut sembler un plan pour garder les données pendant un certain temps encore. Selon la documentation d'accès anticipé il sera toujours disponible en Java 11 (la prochaine "prise en charge à long terme" Java version) et sans avertissement de dépréciation ou similaire. Et devrait-il être supprimé dans une future version Java version, vous pourrez probablement découvrir assez tôt que vous pouvez résoudre le problème avant la mise à niveau.
  2. Utilisez ma solution ci-dessus .
  3. Utilisez l'interface du fournisseur de services locaux auquel Basil Bourque a lié. Il ne fait aucun doute que c'est la bonne solution au cas où les données COMPAT devraient être supprimées à un moment inconnu à l'avenir. Vous pouvez même être en mesure de copier les données locales COMPAT dans vos propres fichiers afin qu'ils ne puissent pas vous les retirer, vérifiez uniquement s'il y a des problèmes de droits d'auteur avant de le faire. La raison pour laquelle je mentionne la solution Nice en dernier est que vous avez dit que vous n'êtes pas satisfait d'avoir à définir une propriété système dans tous les environnements possibles où votre programme peut s'exécuter. Pour autant que je sache, l'utilisation de vos propres données locales via l'interface du fournisseur de services locaux vous obligera à définir la même propriété système (uniquement sur une valeur différente).
19
Ole V.V.

Juste pour mentionner: SimpleDateFormat est une ancienne façon de formater des dates dont BTW n'est pas sûr pour les threads. Depuis Java 8, il existe de nouveaux packages appelés Java.time et Java.time.format et vous devez les utiliser pour travailler avec des dates. Pour vos besoins, vous devez utiliser la classe DateTimeFormatter .

2
Michael Gantman

La valeur formatée en Java 8 était Fr Juni 15 00:20:21 MESZ +0900 2018 Mais elle a changé en Fr. Juni 15 00:20:21 MESZ +0900 2018 EEE inclut. CECI = IS COMPATIBILITY ISSUE et peu importe que les anciennes versions de code ne fonctionnent pas dans les versions plus récentes. (Désolé pour le traducteur) Si la chaîne de date est la vôtre, vous devez ajouter un point pour les utilisateurs de la nouvelle version. Ou faire les utilisateurs utilisent Java 8 pour utiliser votre logiciel.

Cela peut rendre le logiciel plus lent, l'utilisation de la méthode de sous-chaîne est également bonne.

    String toParse = "Mo Aug 18 11:25:26 MESZ +0200 2014";
    String str = toParse.substring(0, 2) + "." + toParse.substring(2);
    String pattern = "EEE MMM dd HH:mm:ss z Z yyyy";

    DateFormat dateFormatter = new SimpleDateFormat(pattern, Locale.GERMANY);
    System.out.println(dateFormatter.format(System.currentTimeMillis()));
    Date date = dateFormatter.parse(str);

Désolé encore pour mon mauvais anglais.

0
dhkim0800