Java DateTimeFormatterBuilder с необязательным шаблоном приводит к исключению DateTimeParseException - PullRequest
0 голосов
/ 25 апреля 2018

Цель

Предоставить гибкий анализатор для экземпляров LocalDate, который может обрабатывать ввод в одном из следующих форматов:

  • гггг
  • ггггММ
  • yyyyMMdd

Попытка реализации

Следующий класс пытается обработать как первый, так и второй шаблон.Парсинг работает для ввода года, но год + месяц приводит к исключению, описанному ниже.

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;

public class DateTest {

    public static void main(String[] args) {
        DateTimeFormatter parser = new DateTimeFormatterBuilder()
        .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
        .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
        .appendPattern("yyyy")
        .optionalStart().appendPattern("MM").optionalEnd().toFormatter();

        System.out.println(parser.parse("2014", LocalDate::from)); // Works
        System.out.println(parser.parse("201411", LocalDate::from)); // Fails
    }
}

Вторая попытка parse () приводит к следующему исключению:

Exception in thread "main" java.time.format.DateTimeParseException: Text '201411' could not be parsed at index 0
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)

Я думаюмое понимание того, как работают необязательные частичные шаблоны, отсутствует.Достижима ли моя цель одного парсера с гибким форматом, или мне нужно проверить длину ввода и выбрать из списка парсеры?Как всегда, помощь приветствуется.

Ответы [ 4 ]

0 голосов
/ 25 апреля 2018

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

Мой анализ был сделан двумя разными способами:

  • отладка (чтобы увидеть, что действительно стоит за непонятным сообщением об ошибке)

  • моделирование поведения в другом движке синтаксического анализа на основе моей библиотеки Time4J для получения лучшего сообщения об ошибке:

    ChronoFormatter<LocalDate> cf =
    ChronoFormatter
        .ofPattern(
            "yyyy[MM]",
            PatternType.THREETEN,
            Locale.ROOT,
            PlainDate.axis(TemporalType.LOCAL_DATE)
        )
        .withDefault(PlainDate.MONTH_AS_NUMBER, 1)
        .withDefault(PlainDate.DAY_OF_MONTH, 1)
        .with(Leniency.STRICT);
    System.out.println(cf.parse("201411")); 
    // java.text.ParseException: Positive sign must be present for big number.
    

Вы можете обойти проблему, указав строителю всегда использовать только четыре цифры года:

DateTimeFormatter parser =
    new DateTimeFormatterBuilder()
        .appendValue(ChronoField.YEAR, 4)
        .optionalStart()
        .appendPattern("MM[dd]")
        .optionalEnd()
        .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
        .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
        .toFormatter();

System.out.println(parser.parse("2014", LocalDate::from)); // 2014-01-01
System.out.println(parser.parse("201411", LocalDate::from)); // 2014-11-01
System.out.println(parser.parse("20141130", LocalDate::from)); // 2014-11-30

Обратите внимание на расположение элементов по умолчанию в компоновщике. Они вызываются не в начале, а в конце, потому что обработка элементов по умолчанию, к сожалению, чувствительна к положению в java.time. И я также добавил дополнительный необязательный раздел для дня месяца в первом необязательном разделе. Это решение кажется мне более чистым, вместо того, чтобы использовать последовательность из 3 необязательных секций, как было предложено Данилой Жаренковым, потому что последний также может анализировать совершенно разные входные данные со многими другими цифрами (возможное неправильное использование необязательных секций в качестве замены или-паттернов, особенно в снисходительном порядке). синтаксический анализ).

О позиционно-чувствительном поведении элементов по умолчанию здесь цитата из API-документации :

Во время синтаксического анализа проверяется текущее состояние анализа. Если указанное поле не имеет связанного значения, потому что оно не было в этот момент успешно проанализирован, то указанное значение впрыскивается в результат разбора. Инъекция немедленная, поэтому пара поле-значение будет видима для любых последующих элементов в форматировщик. Таким образом, этот метод обычно вызывается в конце строитель.


Кстати: в моей библиотеке Time4J я также могу определить реальных или-образцов , используя символ "|" и затем создайте этот форматер:

ChronoFormatter<LocalDate> cf =
    ChronoFormatter
        .ofPattern(
            "yyyyMMdd|yyyyMM|yyyy",
            PatternType.CLDR,
            Locale.ROOT,
            PlainDate.axis(TemporalType.LOCAL_DATE)
        )
        .withDefault(PlainDate.MONTH_AS_NUMBER, 1)
        .withDefault(PlainDate.DAY_OF_MONTH, 1)
        .with(Leniency.STRICT);
0 голосов
/ 25 апреля 2018

Вы устанавливаете значение для месяца и дня, но передаете месяц и год. В этом проблема.

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

.parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
.parseDefaulting(ChronoField.YEAR_OF_ERA, ZonedDateTime.now().getYear())
0 голосов
/ 25 апреля 2018

Вот решение. Вы можете определить возможные шаблоны внутри appendPattern (). И по желанию поставить по умолчанию.

   DateTimeFormatter parser = new DateTimeFormatterBuilder()
            .appendPattern("[yyyy][yyyyMM][yyyyMMdd]")
            .optionalStart()
              .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
              .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
            .optionalEnd()
            .toFormatter();
    System.out.println(parser.parse("2014",LocalDate::from)); // Works
    System.out.println(parser.parse("201411",LocalDate::from)); // Works
    System.out.println(parser.parse("20141102",LocalDate::from)); // Works

Выход

2014-01-01
2014-11-01
2014-11-02
0 голосов
/ 25 апреля 2018

В этой части кода вы уже установили значение для месяца и дня .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1) .parseDefaulting(ChronoField.DAY_OF_MONTH, 1) Затем вы пытаетесь передать ввод для месяца и года в своем коде System.out.println(parser.parse("201411", LocalDate::from));, который вы уже установили.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...