Не удается разобрать дату с помощью 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();
}