Подтвердить что ты не робот

DateDimeFormatter рабочий день кажется одним

Я переношу существующее приложение из 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 и 'Tuesday' заставляет меня думать, что среда также должна быть в java.time be 3 вместо 4.

Что здесь не так? Не понимаю ли я? Является ли это ошибкой в ​​Java 8?

4b9b3361

Ответ 1

Здесь разница в том, как 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);

Ответ 2

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

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