Не удается разобрать дату с помощью JodaTime

Со вчерашнего дня я пытаюсь разобрать простую дату, используя JodaTime, и до сих пор не получаю.....

Вот (случайная) дата, которую я пытаюсь проанализировать: 2017-Sept-14 (Даже с S в верхнем регистре ничего не меняется...)

Вот мой код

 DateTimeFormatter dateTimeFormat = DateTimeFormat.forPattern("yyyy-MM-dd");
 // The variable 'parsed' is a dynamic string. For now I set it to 2017-sept-14 
 DateTime dateTime = dateTimeFormat.parseDateTime(parsed);
 Log.d(TAG, "Parsed date = "+ dateTime.toString());

И вот у меня есть исключение:

java.lang.IllegalArgumentException: Invalid format: "2017-sept-14" is malformed at "sept-14" at org.joda.time.format.DateTimeFormatter.parseDateTime(DateTimeFormatter.java:945)

Что мне здесь не хватает??

ОБНОВЛЕНИЕ: Фактически, то, что я получаю из своего текстового поля, находится в форме выше, то есть дата-месяц-день (месяц имеет длину 3 или 4 символа в зависимости от месяца....) Так что я хочу получить в качестве вывода, когда у меня будет 2017-sept-14 просто 2017-09-14

4 ответа

Joda-Time принимает название месяца в коротком формате (в большинстве языков обычно с 3 буквами) или в длинном формате (с полным названием). Ваш ввод, кажется, на английском языке и с 4 буквами, что не поддерживается.

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

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

Я также анализирую это LocalDate, потому что это toString() Метод уже производит вывод, который вы хотите:

String input = "2017-Sept-14";
input = input.replace("Sept", "Sep");
DateTimeFormatter dateTimeFormat = DateTimeFormat.forPattern("yyyy-MMM-dd").withLocale(Locale.ENGLISH);
LocalDate dateTime = dateTimeFormat.parseLocalDate(input);
System.out.println(dateTime);

Выход:

2017-09-14

Я предполагал, что локаль была английской, но в эстонской локали короткое название месяца "сентябрь", так что вы также можете сделать следующее:

String input = "2017-Sept-14";
input = input.toLowerCase(); // et_EE locale accepts only "sept"
DateTimeFormatter dateTimeFormat = DateTimeFormat.forPattern("yyyy-MMM-dd")
    .withLocale(new Locale("et", "EE"));
LocalDate dateTime = dateTimeFormat.parseLocalDate(input);
System.out.println(dateTime);

Или вы можете попробовать с вашей системой по умолчанию (на основе ваших комментариев, которые SimpleDateFormat работает с французской локалью, так что есть шанс, что код выше также будет работать).


Java новый API даты / времени

Joda-Time находится в режиме сопровождения и заменяется новыми API, поэтому я не рекомендую начинать новый проект с ним. Даже на веб-сайте joda говорится: "Обратите внимание, что Joda-Time считается в значительной степени" законченным "проектом. Никаких серьезных улучшений не планируется. Если вы используете Java SE 8, пожалуйста, перейдите на java.time (JSR-310)".,

Если вы не можете (или не хотите) переходить с Joda-Time на новый API, вы можете игнорировать этот раздел.

В Android вы можете использовать ThreeTen Backport, отличный бэкпорт для новых классов даты / времени в Java 8. Чтобы он работал, вам также понадобится ThreeTenABP (подробнее о том, как его использовать здесь).

Вы можете создать форматтер, установить язык и разобрать его в LocalDate:

import org.threeten.bp.LocalDate;
import org.threeten.bp.format.DateTimeFormatter;
import org.threeten.bp.format.DateTimeFormatterBuilder;

DateTimeFormatter f = new DateTimeFormatterBuilder()
    // case insensitive (so it accepts Sept, sept, and so on)
    .parseCaseInsensitive()
    // pattern
    .appendPattern("yyyy-MMM-dd")
    // set locale
    .toFormatter(new Locale("et", "EE"));
System.out.println(LocalDate.parse("2017-Sept-14", f));

Выход:

2017-09-14

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


По желанию, вы можете создать карту пользовательских названий месяцев и использовать ее в форматере. Единственная деталь заключается в том, что вы должны заполнить его значениями за все месяцы. я кладу Sept в сентябре, и вы можете заполнить другие месяцы соответственно:

// map of custom names for month
Map<Long, String> monthNames = new HashMap<>();
// put the names used in your input
monthNames.put(1L, "Jan");
monthNames.put(2L, "Feb");
monthNames.put(3L, "Mar");
monthNames.put(4L, "Apr");
monthNames.put(5L, "May");
monthNames.put(6L, "Jun");
monthNames.put(7L, "Jul");
monthNames.put(8L, "Aug");
monthNames.put(9L, "Sept");
monthNames.put(10L, "Oct");
monthNames.put(11L, "Nov");
monthNames.put(12L, "Dec");

DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // case insensitive (so it accepts Sept, sept, and so on)
    .parseCaseInsensitive()
    // year
    .appendPattern("yyyy-")
    // month, using custom names
    .appendText(ChronoField.MONTH_OF_YEAR, monthNames)
    // day
    .appendPattern("-dd")
    // create formatter
    .toFormatter();

String input = "2017-Sept-14";
System.out.println(LocalDate.parse(input, fmt));

Joda-Time не предлагает простой способ. Единственный (сложный) способ - определить собственную реализацию DateTimeParser. Вы можете найти вдохновение, как реализовать это в другом старом SO-сообщении от меня, а затем сделать:

DateTimeParser monthParser = ...; // left as exercise to you

DateTimeFormatter joda = 
  new DateTimeFormatterBuilder()
  .appendPattern("yyyy-")
  .append(monthParser)
  .append("-dd")
  .toFormatter();
LocalDate ld = joda.parseLocalDate("2017-sept-14");

Я не уверен, почему ответ Хьюго, предлагающий основанный на карте поиск настроенных имен, не работает для вас. Возможно, названия месяцев являются переменными и не являются фиксированными (для одного и того же месяца). Может быть, вы просто хотите проверить, начинаются ли названия месяцев с того же префикса. Если так, то моя lib Time4J может помочь вам, потому что она управляет гораздо большим количеством атрибутов форматирования / синтаксического анализа для управления синтаксическим анализом, см. Следующий короткий пример (он также управляет дополнительными конечными точками, если они есть):

String s = "2017-sept.-14";
ChronoFormatter<PlainDate> f =
  ChronoFormatter
    .setUp(PlainDate.class, new java.util.Locale("fr", "FR"))
    .addPattern("uuuu-MMM", PatternType.CLDR)
    .skipUnknown(c -> c == '.', 1)
    .addPattern("-dd", PatternType.CLDR)
    .build()
    .with(net.time4j.format.Attributes.PARSE_CASE_INSENSITIVE, true)
    .with(net.time4j.format.Attributes.PARSE_PARTIAL_COMPARE, true);
System.out.println(f.parse(s)); // 2017-09-14

Для Android вы должны заменить Time4J на Time4A, а также заменить данное лямбда-выражение анонимным классом, реализующим ChronoCondition-интерфейс.

Кстати, справочная таблица для фиксированных названий месяцев возможна для каждой библиотеки. Для Joda, см. Мой старый SO-пост, упомянутый выше как ссылку. Для Java-8 или тройного обратного порта см. Ответ Гюго. За SimpleDateFormat посмотрите, как установить подходящий экземпляр DateFormatSymbols, Для Time4J, см. API-интерфейс builder (аналог Java-8).

Последнее слово о SimpleDateFormat, Даже если кажется, что это работает для вас сейчас (просто из-за мягкого синтаксического анализа, который работает для старой Java и Time4J, но что интересно, не для java.time-API), я бы все равно не доверял ему в любой ситуации, и этот старый класс синтаксического анализатора также не является потокобезопасным.

Вам нужен двухзначный номер месяца (09), если вы хотите сохранить схему yyyy-MM-dd, В противном случае измените ваш шаблон на yyyy-MMMM-dd,

Так что я не знаю, если это правильный путь, но это сработало для меня (в итоге я использовал SimpleDateFormat...):

SimpleDateFormat sdfSource = new SimpleDateFormat("yyyy-MMM-dd");
SimpleDateFormat sdfDestination = new SimpleDateFormat("yyyy-MM-dd");
try {
      Date date = sdfSource.parse(parsed);
      String strOutput = sdfDestination.format(date);
      Log.d(TAG, "Parsed date = "+ strOutput);
    } catch (ParseException e) {
        e.printStackTrace();
    }
Другие вопросы по тегам