Как именно DateFormat.parse (String) обрабатывает конечный текст без даты? - PullRequest
3 голосов
/ 05 июля 2019

Метод синтаксического анализа класса DateFormat, как ожидается, игнорирует завершающий текст , если ему удастся проанализировать дату с начала строки.

API документы являются уклончивыми:

Анализирует текст с начала данной строки, чтобы получить дату.Метод может не использовать весь текст данной строки.

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

Учитывая

  DateFormat df = new SimpleDateFormat("yyyy-MM-dd");

Это работает неправильно (неожиданно):

System.out.println(df.parse("2019-12-112"));

Output: Sat Mar 21 00:00:00 MSK 2020

Хотя это работает нормально (какожидается):

System.out.println(df.parse("2019-12-11sometext"));

Output: Wed Dec 11 00:00:00 MSK 2019

Если остальная часть анализируемого текста состоит из цифр, кажется, что он всегда не сможет правильно проанализировать (что дает разные ошибочные результаты):

System.out.println(df.parse("2019-12-1189"));
System.out.println(df.parse("2019-12-11234"));

Output: 
Fri Mar 03 00:00:00 MSK 2023
Fri Sep 02 00:00:00 MSK 2050

Теперь setLenient()ведет себя так:

  1. Если за (правильной) датой следует пробел или буквы, то настройка setLenient () не имеет значения .Все анализируется правильно и без исключений для обоих setLenient (true / false).
  2. Но , если за (правильной) датой сразу следует цифра (цифры), тогда setLenient () делает (неожиданную) разницу : default (true) не приводит ни к исключению, ни к неправильному (!) Результату синтаксического анализа, но setLenient (false) woud приводит к ParseException: Unparseable date: "2019-12-111", что также странно (дата верна, а игнорирование завершающего текста было своего рода обещанным API) ...

Date parse(String source, ParsePosition pos) ведет себя абсолютно идентично Date parse(String source) во всех вышеупомянутых случаях (включая реакцию на setLenient) для единственного (и ожидаемого) исключения, которое где one-arg parse () throws ParseException , это два аргумента parse () возвращает null (что является его реакцией на неправильный формат даты в начале строкидля анализа).

Также стоит отметить, что после вызова mydateFormat.parse("2019-12-11234", pos)setLenient (true) ), pos.getIndex() возвращает 13 - что указывает "за" 234 часть, что означает это относится к 234 номиналуt как «-dd» часть «yyyy-MM-dd» в new SimpleDateFormat("yyyy-MM-dd").

PS Я знаю, что это устаревший API даты / времени, и я предпочитаю java.time вместо Date /Calendar / DateFormat и т. Д. Но он все еще широко представлен в унаследованном коде для поддержки в случаях, когда рефакторинг невозможен.

Ответы [ 3 ]

2 голосов
/ 05 июля 2019

Когда вы устанавливаете для isLenient значение true и добавляете дополнительное число, средство форматирования будет пытаться вычислить новую дату из заданных значений, поэтому df.parse("2019-12-1189") будет проанализировано как 2019-12-01 + 1189 дней (1189/365 = 3,26), поэтому Пт марта 03 00:00:00 MSK 2023 выглядит как правильный результат (я не знаю точной математики здесь).

То же самое для 11234, который добавляет примерно 30 лет.

Вы также можете попробовать это для 2019-12-32 и 2019-13-01, которые оба преобразуются в 2020-01-01

0 голосов
/ 08 июля 2019

SimpleDateFormat внутренне использует (специфичный для локали) экземпляр NumberFormat для анализа данной даты.В моем случае это экземпляр DecimalFormat.

Отладка SimpleDateFormat показывает, что важная часть в этом:

if (obeyCount) {
    if ((start+count) > text.length()) {
        break parsing;
    }
    number = numberFormat.parse(text.substring(0, start+count), pos);
} else {
    number = numberFormat.parse(text, pos);
}

Учитывая ваш первый пример:

DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
System.out.println(df.parse("2019-12-112"));

внутренне установленный obeyCount равен false, поэтому numberFormat.parse() всегда вызывается для всей части текста: 112.То же самое происходит с этим примером:

DateFormat df = new SimpleDateFormat("yyyyMMdd");
System.out.println(df.parse("201912112"));

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

Также обратите внимание, что setLenient(boolean) не изменяет внутреннийNumberFormat синтаксический анализатор, но только экземпляр Calendar, который вступает в игру после анализа.В остальном см. Ответ Джоаким Даниэльсон.

0 голосов
/ 08 июля 2019

Два очка.

  1. Даже если вы уже сказали это сами, я хочу повторить: не используйте SimpleDateFormat.Это общеизвестно хлопотно и давно устарело.
  2. Ваша цитата из документации должна быть прочитана в сочетании, по крайней мере, с еще одним моментом.По сути, SimpleDateFormat не может сделать то, что вы хотели.

java.time

    LocalDate date = LocalDate.from(DateTimeFormatter.ISO_LOCAL_DATE
            .parse("2019-12-112", new ParsePosition(0)));
    System.out.println(date);

Вывод:

2019-12-11

Я думаю, что это результат, который вы хотели.2019-12-11 был проанализирован как дата, а 2 был проигнорирован как конечный текст, а не часть даты.

Вы не можете сделать это с SimpleDateFormat

Чтобы подчеркнуть мою точку зрения, яЯ использую другой пример:

    DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
    df.setLenient(false);
    System.out.println(df.parse("2019-12-013"));

Пт 13 декабря 00:00:00 CET 2019

Это поведение задокументировано .В дополнение к вашей цитате нам нужно прочитать:

  • Number: … При синтаксическом анализе количество букв шаблона игнорируется, если только это не необходимо для разделения двух смежных полей.

Итак, в моем примере, поскольку после dd нет смежных полей, мы не можем убедить SimpleDateFormat читать 013 как что-либо еще, кромечисло 13.

Ссылки

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