День недели DateTimeFormatter кажется ошибочным на один

Я переношу существующее приложение с Joda-Time на Java 8 java.time.

Я столкнулся с проблемой, когда синтаксический анализ строки даты/времени, содержащей значение «день недели», вызвал исключение в моих модульных тестах.

При разборе:

2016-12-21 20:50:25 Среда Декабрь +0000 3

используя формат:

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

Я получил:

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

Когда DateTimeFormatter указывает, что он ожидает:

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));

Теперь я получаю этот вывод:

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

Таким образом, основная причина проблемы заключается в том, что флаг e в Joda-Time считает понедельник 1, а Java 8 java.time считает понедельник 0.

Теперь о шаблонах, которые поддерживает java.time.DateTimeFormatter, я нахожу в обоих документация Oracle и в JSR-310 это:

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

Этот явный пример 2 и «вторник» заставляет меня поверить, что среда также должна быть в java.time 3 вместо 4.

Что здесь не так? Я неправильно понимаю? Это ошибка в Java 8?


person Niels Basjes    schedule 21.09.2017    source источник
comment
Какой часовой пояс/язык вы используете, поскольку документ говорит, что это локализованный день недели, некоторые локали начинают неделю с воскресенья, а не с понедельника   -  person hardillb    schedule 21.09.2017
comment
Чтобы уточнить, я добавил .withLocale(Locale.ENGLISH); в коде.   -  person Niels Basjes    schedule 21.09.2017


Ответы (2)


Есть разница в том, как Joda-Time и java.time интерпретируют шаблон e.


В Joda-Time шаблон e обозначает числовое значение дня недели:

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

Таким образом, использование e эквивалентно получению дня недели из объекта даты:

// 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

Обратите внимание, что форматтер и getDayOfWeek() возвращают 3. Метод getDayOfWeek() возвращает значение, определенное в DateTimeConstants class, и Среда – 3 (третий день недели согласно определение ISO).


В java.time API шаблон e имеет другое значение:

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

Он использует локализованный элемент WeekFields, и это может варьироваться в зависимости от региона. Поведение может отличаться от метода 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

Обратите внимание, что средство форматирования использует локализованный день недели для английской локали и значение равно 4, а вызов getDayOfWeek().getValue() возвращает 3.

Это потому, что e с английской локалью эквивалентно использованию java.time.temporal.WeekFields:

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

Хотя getDayOfWeek() эквивалентно использованию определения ISO:

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

Это связано с тем, что определение ISO использует понедельник в качестве первого дня недели, а WeekFields с английской локалью использует воскресенье:

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

Таким образом, шаблон e может вести себя по-разному или не вести себя по отношению к getDayOfWeek() в зависимости от языкового стандарта, установленного в средстве форматирования (или языкового стандарта JVM по умолчанию, если он не установлен). Например, во французской локали он ведет себя так же, как ISO, а в некоторых арабских локалях первый день недели — суббота:

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

Согласно javadoc, единственные шаблоны, которые возвращают числовое значение дня недели, по-видимому, являются локализованными. Таким образом, для анализа ввода 2016-12-21 20:50:25 Wednesday December +0000 3 вы можете использовать java.time.format.DateTimeFormatterBuilder и соединить шаблон даты/времени с java.time.temporal.ChronoField, чтобы указать числовое значение дня недели (поле 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);

Также обратите внимание, что вам не нужно заключать в кавычки -, : и символы пробела, чтобы шаблон стал более четким и читаемым (IMO).

Я также установил английскую локаль, потому что, если вы ее не установите, будет использоваться локаль JVM по умолчанию, и не всегда гарантируется, что она будет английской. И его также можно изменить без предварительного уведомления, даже во время выполнения, поэтому лучше указать его, особенно если вы уже знаете, на каком языке ввод.


Обновление: возможно, шаблон ccccc должен работать, так как он эквивалентен appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW_STANDALONE) и в моих тестах (JDK 1.8.0_144) возвращает (а также анализирует) 3:

DateTimeFormatter parser = DateTimeFormatter
    .ofPattern("yyyy-MM-dd HH:mm:ss EEEE MMMM ZZ ccccc", Locale.ENGLISH);
ZonedDateTime date = ZonedDateTime.parse(input, parser);
person Community    schedule 21.09.2017
comment
Спасибо. Это объяснение действительно помогает. У меня есть кавычки всех этих полей, потому что выражение генерируется из входной строки в формате strftime. Я должен найти способ добавить эти значения, чтобы все работало правильно. См. https://github.com/nielsbasjes/logparser/blob/master/httpdlog/httpdlog-parser/src/main/java/nl/basjes/parse/httpdlog/dissectors/StrfTimeStampDissector.java#L200 - person Niels Basjes; 21.09.2017
comment
@NielsBasjes Пожалуйста, рад помочь! Я сделал несколько тестов и, возможно, шаблон ccccc должен подойти вам. Я обновил ответ. - person ; 21.09.2017

В Locale.ENGLISH среда - 4-й день недели, так как неделя начинается в воскресенье. Вы можете проверить первый день недели с помощью

WeekFields.of(Locale.ENGLISH).getFirstDayOfWeek(); //it's SUNDAY
person Anatoly Shamov    schedule 21.09.2017