Не удалось получить LocalDate из TemporalAccessor для строки на основе недели

Я пытаюсь разобрать простую строку в формате "YYYYww" (например, 201901) в LocalDate, но ни одна из моих попыток не увенчалась успехом.

Я попытался проанализировать его, просто используя шаблон «YYYYww», а также вручную добавив значения в FormatterBuilder. Поскольку моя входная строка не содержит дня, я также настроил средство форматирования по умолчанию на воскресенье.

Вот код, который у меня не работает, с Java 8 (IBM JRE 8.0.5.25).

public static void main(String[] args) {
    formatter1(); // Unable to obtain LocalDate from TemporalAccessor
    formatter2(); // Text '201901' could not be parsed at index 0
    formatter3(); // Text '201901' could not be parsed at index 6
}

public static void formatter1() {
    DateTimeFormatter formatter = new DateTimeFormatterBuilder()
            .appendValue(WeekFields.ISO.weekBasedYear(), 4, 4, SignStyle.NEVER)
            .appendValue(WeekFields.ISO.weekOfYear(), 2, 2, SignStyle.NEVER)
            .parseDefaulting(WeekFields.ISO.dayOfWeek(), DayOfWeek.SUNDAY.getValue())
            .toFormatter();

    LocalDate.parse("201901", formatter);
}

public static void formatter2() {
    DateTimeFormatter formatter = new DateTimeFormatterBuilder()
            .appendPattern("YYYYww")
            .parseDefaulting(WeekFields.ISO.dayOfWeek(), DayOfWeek.SUNDAY.getValue())
            .toFormatter();

    LocalDate.parse("201901", formatter);
}

public static void formatter3() {
    DateTimeFormatter formatter = new DateTimeFormatterBuilder()
            .parseLenient()
            .appendPattern("YYYYww")
            .parseDefaulting(WeekFields.ISO.dayOfWeek(), DayOfWeek.SUNDAY.getValue())
            .toFormatter();

    LocalDate.parse("201901", formatter);
}

Как видно из примера кода, я получаю разные сообщения об ошибках, особенно в первом примере, который меня сбивает с толку, поскольку TemporalAccessor содержит год на основе недели, неделю года и день недели, чего должно быть достаточно для создания LocalDate .

Exception in thread "main" java.time.format.DateTimeParseException: Text '201901' could not be parsed: Unable to obtain LocalDate from TemporalAccessor: {WeekOfYear[WeekFields[MONDAY,4]]=1, WeekBasedYear[WeekFields[MONDAY,4]]=2019, DayOfWeek=7},ISO of type java.time.format.Parsed
    at java.time.format.DateTimeFormatter.createError(DateTimeFormatter.java:1931)
    at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1866)
    at java.time.LocalDate.parse(LocalDate.java:411)
    at Main.formatter1(Main.java:22)
    at Main.main(Main.java:10)
Caused by: java.time.DateTimeException: Unable to obtain LocalDate from TemporalAccessor: {WeekOfYear[WeekFields[MONDAY,4]]=1, WeekBasedYear[WeekFields[MONDAY,4]]=2019, DayOfWeek=7},ISO of type java.time.format.Parsed
    at java.time.LocalDate.from(LocalDate.java:379)
    at java.time.LocalDate$$Lambda$7.000000001061ED20.queryFrom(Unknown Source)
    at java.time.format.Parsed.query(Parsed.java:237)
    at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1862)
    ... 3 more

person user3242181    schedule 13.08.2019    source источник
comment
Небольшой, но полезный трюк для отладки таких вещей, кстати. вызывает TemporalAccessor a = formatter.parse("..."); таким образом, вы получаете промежуточный шаг, и немного проще понять, почему все происходит именно так, как происходит :)   -  person Rohde Fischer    schedule 13.08.2019
comment
Я не могу воспроизвести. Оба ваших метода formatter2 и formatter3 безупречно работают на моей Java 11. Чтобы заставить formatter1 работать, просто замените WeekFields.ISO.weekOfYear() на WeekFields.ISO.weekOfWeekBasedYear()   -  person Ole V.V.    schedule 21.08.2019
comment
@ОлеВ.В. Извините, я использую Java 8 (IBM)   -  person user3242181    schedule 26.08.2019
comment
Я воспроизвел на (Oracle) Java 1.8.0_101. formatter2() бросает java.time.format.DateTimeParseException: Text '201901' could not be parsed at index 0. formatter3() вместо этого выдает java.time.format.DateTimeParseException: Text '201901' could not be parsed at index 6. Я нахожу это очень интересным и не могу сразу объяснить. (Добавление вашей версии Java в вопрос позволит мне отозвать свой отрицательный голос.)   -  person Ole V.V.    schedule 26.08.2019
comment
Очень интересно, так что похоже, что это было исправлено в более новых версиях Java. Спасибо за подтверждение, я добавил свою версию в вопрос.   -  person user3242181    schedule 26.08.2019


Ответы (4)


Это работает на Java 8 и более поздних версиях:

    DateTimeFormatter formatter = new DateTimeFormatterBuilder()
            .appendValue(WeekFields.ISO.weekBasedYear(), 4)
            .appendValue(WeekFields.ISO.weekOfWeekBasedYear(), 2)
            .parseDefaulting(WeekFields.ISO.dayOfWeek(), DayOfWeek.SUNDAY.getValue())
            .toFormatter();

    System.out.println(LocalDate.parse("201901", formatter));

Выход:

2019-01-06

Я тестировал на Oracle Java 1.8.0_101. У меня не установлена ​​IBM Java.

Ключом к тому, чтобы заставить его работать, является использование weekOfWeekBasedYear() вместо weekOfYear(). Другие мои изменения косметические. WeekFields.ISO.weekOfYear() определяет номера недель, считая понедельники в году, а это не то, что вам нужно, поскольку неделя 1 в соответствии с ISO может начинаться в понедельник ближе к концу предыдущего года (с 29 по 31 декабря). Поэтому он также плохо работает с годом на основе недели, и Java отказывается использовать их вместе для определения недели.

На Java 9 оба ваших formatter2() и formatter3() тоже работают (проверено на Oracle Java 9.0.4). Кажется, в Java 8 была ошибка, из-за которой синтаксический анализ смежных полей не работал для YYYYww. Однако я искал базу данных ошибок Oracle Java, но не нашел описания ошибки.

ИСО 8601

ISO 8601, стандарт, устанавливающий используемое вами определение недель, также определяет формат недели. Это похоже на 2012-W48. Поэтому, если у вас нет веской и очень конкретной причины не делать этого, вам следует использовать именно этот формат, особенно при обмене данными с любой внешней системой. Это также устраняет проблему синтаксического анализа соседних полей в Java 8.

    DateTimeFormatter formatter = new DateTimeFormatterBuilder()
            .appendPattern("YYYY-'W'ww")
            .parseDefaulting(WeekFields.ISO.dayOfWeek(), DayOfWeek.SUNDAY.getValue())
            .toFormatter();

    System.out.println(LocalDate.parse("2019-W01", formatter));

2019-01-06

Связь

person Ole V.V.    schedule 26.08.2019

Насколько я знаю, вы не можете этого сделать. Насколько мне известно, в вашем коде есть две проблемы. Во-первых, у LocalDate недостаточно информации, поскольку ваш код не может знать, до какого дня недели он должен выполнять синтаксический анализ.

Второй — использование немного странных полей. Если вы просто используете поля Chrono, все должно быть в порядке. Вы также можете настроить свой шаблон так, чтобы он содержал либо пробел, либо тире, так как кажется, что синтаксический анализ не нравится, когда он находится в одном числе - хотя не уверен, почему

Например, вы можете сделать следующее:

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
        .appendValue(ChronoField.YEAR, 4, 4, SignStyle.NEVER)
        .appendValue(ChronoField.ALIGNED_WEEK_OF_YEAR, 2, 2, SignStyle.NEVER)
        .parseDefaulting(ChronoField.DAY_OF_WEEK, DayOfWeek.SUNDAY.getValue())
        .toFormatter();

LocalDate d = LocalDate.parse("201933", formatter);

Однако использование номеров недель не кажется хорошим вариантом. Если вы можете использовать месяцы, как предлагает @butiri-dan, вам, скорее всего, будет лучше.

Изменить: по предложению @carlos-heuberger теперь используется ChronoField.DAY_OF_WEEK в parseDefaulting-setep

person Rohde Fischer    schedule 13.08.2019
comment
Кажется, это работает, однако мне интересно, являются ли ChronoFields правильными полями для использования. К сожалению, отдел, которому требуется это приложение, работает на основе этих недельных кодов, поэтому мне приходится работать с ними. - person user3242181; 13.08.2019
comment
Судя по документации, я бы сказал да: docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/, но он не слишком специфичен и действительно оставляет один интересно, есть ли лучший вариант :) - person Rohde Fischer; 13.08.2019
comment
Я бы использовал ChronoField.DAY_OF_WEEK (вместо WeekFields дня) и для года/недель, основанных на неделе, IsoFields.WEEK_BASED_YEAR и IsoFields.WEEK_OF_WEEK_BASED_YEAR - person user85421; 13.08.2019
comment
Хороший вопрос, соответственно обновлю ответ, спасибо @carlos-heuberger - person Rohde Fischer; 13.08.2019
comment
Очень интересно, раньше не знал о IsoFields. Однако использование IsoFields.WEEK_BASED_YEAR и IsoFields.WEEK_OF_WEEK_BASED_YEAR вместо соответствующих ChronoField приводит к другой обработке первой недели. Если вы измените день по умолчанию с воскресенья на понедельник в моем примере, использование IsoFields приведет к анализу 2018-12-31, а использование ChronoField.YEAR и ChronoField.ALIGNED_WEEK_OF_YEAR — к 2019-01-07. Заставляет меня задаться вопросом, действительно ли использование ChronoField правильно в этом случае. - person user3242181; 14.08.2019

Вы можете использовать YearMonth

public static void main(String[] args) {
    System.out.println(YearMonth.parse("201901", DateTimeFormatter.ofPattern("yyyyMM")));
}

Вывод

2019-01
person Butiri Dan    schedule 13.08.2019
comment
Хорошая идея, однако, если я правильно понимаю вопрос, он пытается использовать номера недель, а не месяцев:/ - person Rohde Fischer; 13.08.2019
comment
К сожалению, я ввожу не год и месяц, а год и неделю. - person user3242181; 13.08.2019

Ну, первая неделя 2019 года не дает вам конкретной даты. Вам нужно добавить конкретный день недели. Я запустил это:

System.out.println(LocalDate.parse("2019-01-01", DateTimeFormatter.ofPattern("YYYY-ww-ee")));

и получил "2018-12-30" (это потому, что 1 января 2019 года выпало на вторник, то есть на третий день недели, поэтому первый день первой недели 2019 года на самом деле "2018-12-30"). Обратите внимание, что без тире "-" почему-то не получилось. Чтобы прочитать об опциях форматирования, перейдите сюда: DateTimeFormatter

person Michael Gantman    schedule 13.08.2019