2017-09-21 14 views
10

Je porte une application existante de Joda-Time à Java 8 java.time.DateTimeFormatter en semaine semble désactivé par un

J'ai rencontré un problème où l'analyse d'une chaîne de date/heure contenant une valeur «jour de la semaine» a déclenché une exception dans mes tests unitaires.

Lors de l'analyse:

21/12/2016 20:50:25 Décembre Mercredi 3 +0000

en utilisant le format:

yyyy'-'MM'-'dd' 'HH':'mm':'ss' 'EEEE' 'MMMM' 'ZZ' 'e 

Je reçois:

java.time.format.DateTimeParseException: 
Text '2016-12-21 20:50:25 Wednesday December +0000 3' 
could not be parsed: Conflict found: 
Field DayOfWeek 3 differs from DayOfWeek 2 derived from 2016-12-21 

Lorsque lett ing la DateTimeFormatter indiquent ce qu'il attend:

String logline  = "2016-12-21 20:50:25 Wednesday December +0000"; 
String format  = "yyyy'-'MM'-'dd' 'HH':'mm':'ss' 'EEEE' 'MMMM' 'ZZ"; 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format).withLocale(Locale.ENGLISH);; 
ZonedDateTime dateTime = formatter.parse(logline, ZonedDateTime::from); 

format  = "yyyy'-'MM'-'dd' 'HH':'mm':'ss' 'EEEE' 'MMMM' 'ZZ' 'e"; 
formatter = DateTimeFormatter.ofPattern(format).withLocale(Locale.ENGLISH); 
System.out.println(formatter.format(dateTime)); 

Je reçois maintenant cette sortie:

2016-12-21 20:50:25 Wednesday December +0000 4 

donc efficacement la cause racine du problème est que le drapeau e dans Joda-Time considère lundi être 1 pourtant le Java 8 java.time considère lundi être 0.

Maintenant, pour les modèles qui java.time.DateTimeFormatter prend en charge que je trouve dans les deux the Oracle documentation et JSR-310 ceci:

e/c  localized day-of-week  number/text  2; 02; Tue; Tuesday; T 

Cet exemple explicite de 2 et « mardi » me porte à croire que le mercredi devrait également en java. l'heure sera 3 au lieu de 4.

Quel est le problème ici? Ai-je mal compris? Est-ce un bug dans Java 8?

+7

Quel fuseau horaire/locale utilisez-vous, comme le dit le doc le jour de la semaine, certaines locales commencent la semaine du dimanche par opposition au lundi – hardillb

+0

Pour clarifier j'ai ajouté le .withLocale (Locale.ENGLISH); dans le code. –

Répondre

6

Il y a une différence sur la façon dont Joda-Time et java.time interprète le modèle e.


En Joda temps, le motif edesignates the numeric value of day-of-week:

Symbol Meaning  Presentation Examples 
------ ----------- ------------ ------- 
e  day of week number  2 

Ainsi, en utilisant e équivaut à obtenir le jour de la semaine à partir d'un objet Date:

// using org.joda.time.DateTime and org.joda.time.format.DateTimeFormat 
DateTime d = new DateTime(2016, 12, 21, 20, 50, 25, 0, DateTimeZone.UTC); 
DateTimeFormatter fmt = DateTimeFormat.forPattern("e").withLocale(Locale.ENGLISH); 
System.out.println(d.toString(fmt)); // 3 
System.out.println(d.getDayOfWeek()); // 3 
System.out.println(d.dayOfWeek().getAsText(Locale.ENGLISH)); // Wednesday 

Remarque que le formateur et getDayOfWeek() renvoient 3. Le getDayOfWeek() method renvoie une valeur définie dans DateTimeConstants class et Wednesday's value is 3 (the third day of the week selon ISO's definition).


Dans java.time API, le motif ehas a different meaning:

Pattern Count Equivalent builder methods 
------- ----- -------------------------- 
e  1  append special localized WeekFields element for numeric day-of-week 

Il utilise l'élément WeekFields localisé, et cela peut varier en fonction de l'environnement local. Le comportement peut être différent par rapport à la méthode getDayOfWeek():

ZonedDateTime z = ZonedDateTime.of(2016, 12, 21, 20, 50, 25, 0, ZoneOffset.UTC); 
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("e", Locale.ENGLISH); 
System.out.println(z.format(fmt)); // 4 
System.out.println(z.getDayOfWeek()); // WEDNESDAY 
System.out.println(z.getDayOfWeek().getValue()); // 3 

Notez que le formatter utilise le jour localisé de la semaine pour les paramètres régionaux anglais, et la valeur est 4, tout en appelant getDayOfWeek().getValue() retours 3.

C'est parce que e avec paramètres régionaux anglais est équivalent à l'aide d'un java.time.temporal.WeekFields:

// using localized fields 
WeekFields wf = WeekFields.of(Locale.ENGLISH); 
System.out.println(z.get(wf.dayOfWeek())); // 4 

Alors que getDayOfWeek() équivaut à utiliser la définition de l'ISO:

// same as getDayOfWeek() 
System.out.println(z.get(WeekFields.ISO.dayOfWeek())); // 3 

C'est parce que la définition de l'ISO utilise lundi le premier jour de la semaine, tandis que WeekFields avec les paramètres régionaux anglais utilise le dimanche:

// comparing the first day of week 
System.out.println(WeekFields.ISO.getFirstDayOfWeek()); // MONDAY 
System.out.println(wf.getFirstDayOfWeek()); // SUNDAY 

Ainsi, le modèle e pourrait se comporter différemment ou non getDayOfWeek(), selon les paramètres régionaux définis dans le formatter (ou la langue par défaut JVM, si aucune est définie). En environnement local français, par exemple, il se comporte comme ISO, alors que dans certains endroits arabes, le premier jour de la semaine est le samedi:

WeekFields.of(Locale.FRENCH).getFirstDayOfWeek(); // MONDAY 
WeekFields.of(new Locale("ar", "AE")).getFirstDayOfWeek(); // SATURDAY 

Selon javadoc, les seuls motifs qui renvoient une valeur numérique pour le jour de la semaine semblent être les localisés. Donc, pour analyser l'entrée 2016-12-21 20:50:25 Wednesday December +0000 3, vous pouvez utiliser un java.time.format.DateTimeFormatterBuilder et se joindre à la configuration date/heure avec un java.time.temporal.ChronoField pour indiquer la valeur numérique du jour de la semaine (le champ sensible non-locale ISO):

String input = "2016-12-21 20:50:25 Wednesday December +0000 3"; 
DateTimeFormatter parser = new DateTimeFormatterBuilder() 
    // date/time pattern 
    .appendPattern("yyyy-MM-dd HH:mm:ss EEEE MMMM ZZ ") 
    // numeric day of week 
    .appendValue(ChronoField.DAY_OF_WEEK) 
    // create formatter with English locale 
    .toFormatter(Locale.ENGLISH); 

ZonedDateTime date = ZonedDateTime.parse(input, parser); 

Notez également que vous n'avez pas besoin de citer les caractères -, : et l'espace, afin que le motif devienne plus clair et lisible (IMO).

J'ai également défini les paramètres régionaux anglais, car si vous ne définissez pas, il utilisera les paramètres régionaux par défaut JVM et il n'est pas garanti d'être toujours anglais. Et il peut également être changé sans préavis, même au moment de l'exécution, il est donc préférable d'en spécifier un, surtout si vous savez déjà dans quelle langue l'entrée est.


Mise à jour: probablement le modèle ccccc devrait fonctionner, car il est équivalent à appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW_STANDALONE) et dans mes tests (JDK 1.8.0_144), elle retourne (et parse) 3:

DateTimeFormatter parser = DateTimeFormatter 
    .ofPattern("yyyy-MM-dd HH:mm:ss EEEE MMMM ZZ ccccc", Locale.ENGLISH); 
ZonedDateTime date = ZonedDateTime.parse(input, parser); 
+1

Merci. Cette explication aide vraiment. J'ai la citation de tous ces champs comme ceci parce que l'expression est générée à partir d'une chaîne d'entrée formatée par strftime. Je dois trouver un moyen d'ajouter ces valeurs pour que tout fonctionne correctement. Voir https://github.com/nielsbasjes/logparser/blob/master/httpdlog/httpdlog-parser/src/main/java/nl/basjes/parse/httpdlog/dissectors/StrfTimeStampDissector.java#L200 –

+2

@NielsBasjes Vous êtes bienvenue, heureux d'aider! J'ai fait quelques tests et peut-être que le modèle 'ccccc' devrait être ce dont vous avez besoin. J'ai mis à jour la réponse. –

1

Locale.ENGLISH Le mercredi est le 4ème jour de la semaine, la semaine commence le dimanche. Vous pouvez vérifier d'abord jour de la semaine avec

WeekFields.of(Locale.ENGLISH).getFirstDayOfWeek(); //it's SUNDAY