Android конвертирует ошибку разбора даты и времени (даже пробовал joda time)
Я анализирую несколько новостных лент, и pubDate каждого элемента соответствует одному и тому же формату:
Вс, 11 июня 2017 18:18:23 +0000
К сожалению, один канал не делает:
Суббота, 10 июня 2017 12:49:45 EST
Я пытался разобрать дату без удачи с помощью androids java date и SimpleDateFormat
:
try {
Calendar cal = Calendar.getInstance();
TimeZone tz = cal.getTimeZone();
SimpleDateFormat readDate = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z");
readDate.setTimeZone(TimeZone.getTimeZone("UTC"));
Date date = readDate.parse(rssDateTime);
SimpleDateFormat writeDate = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z");
writeDate.setTimeZone(tz);
parsedDate = writeDate.format(date);
} catch (ParseException e) {
e.printStackTrace();
}
Какие броски и ошибки:
java.text.ParseException: не разбираемая дата: "сб, 3 июня 2017 19:53:09 EST" (по смещению 26)
Я также попытался сделать это, используя время Joda:
DateTime dtUTC = null;
DateTimeZone timezone = DateTimeZone.getDefault();
DateTimeFormatter formatDT = DateTimeFormat.forPattern("EEE, d MMM yyyy HH:mm:ss Z");
DateTime dtRssDateTime = formatDT.parseDateTime(rssDateTime);
DateTime now = new DateTime();
DateTime nowUTC = new LocalDateTime(now).toDateTime(DateTimeZone.UTC);
long instant = now.getMillis();
long instantUTC = nowUTC.getMillis();
long offset = instantUTC - instant;
dtUTC = dtRssDateTime.withZoneRetainFields(timezone);
dtUTC = dtUTC.minusMillis((int) offset);
String returnTimeDate = "";
returnTimeDate = dtUTC.toString(formatDT);
Который выдает ошибку:
Вызвано: java.lang.IllegalArgumentException: неверный формат: "Сб, 10 июня 2017 12:49:45 EST" искажен в " EST"
Кто-нибудь сталкивался с этим раньше?
1 ответ
Прежде всего, если вы начинаете новый проект, я предлагаю вам использовать новый API даты-времени вместо joda-time (подробнее об этом ниже). Во всяком случае, вот решение для обоих.
Время йода
Проблема в том, что шаблон Z
это смещение (в таких форматах, как +0000
или же -0100
), но строка EST
это короткое имя часового пояса, которое анализируется шаблоном z
(взгляните на jodatime javadoc для более подробной информации).
Итак, вам нужен шаблон с необязательными секциями, который может получать один или другой одновременно. Вы можете сделать это с org.joda.time.format.DateTimeFormatterBuilder
учебный класс.
Сначала вам нужно создать 2 экземпляра org.joda.time.format.DateTimeParser
(один для Z
и прочее для z
), и добавьте их как дополнительные парсеры. Затем вы создаете org.joda.time.format.DateTimeFormatter
используя код ниже. Обратите внимание, что я также использовал java.util.Locale
просто чтобы убедиться, что он правильно анализирует названия дней недели и месяцев (чтобы вы не зависели от локали по умолчанию, которая может варьироваться в зависимости от системы / машины):
// offset parser (for "+0000")
DateTimeParser offsetParser = new DateTimeFormatterBuilder().appendPattern("Z").toParser();
// timezone name parser (for "EST")
DateTimeParser zoneNameParser = new DateTimeFormatterBuilder().appendPattern("z").toParser();
// formatter for both patterns
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// append common pattern
.appendPattern("EEE, d MMM yyyy HH:mm:ss ")
// optional offset
.appendOptional(offsetParser)
// optional timezone name
.appendOptional(zoneNameParser)
// create formatter (use English Locale to make sure it parses weekdays and month names independent of JVM config)
.toFormatter().withLocale(Locale.ENGLISH)
// make sure the offset "+0000" is parsed
.withOffsetParsed();
// parse the strings
DateTime est = fmt.parseDateTime("Sat, 10 Jun 2017 12:49:45 EST");
DateTime utc = fmt.parseDateTime("Sun, 11 Jun 2017 18:18:23 +0000");
System.out.println(est);
System.out.println(utc);
Выход будет:
2017-06-10T12: 49: 45.000-04: 00
2017-06-11T18: 18: 23.000Z
Если они не совсем такие, как вы ожидали (или по-прежнему получаете ошибки), ознакомьтесь с примечаниями ниже.
Примечания:
Обратите внимание, что
EST
был напечатан как дата / время со смещением-0400
, Это потому чтоEST
внутренне сталAmerica/New_York
часовой пояс, который в настоящее время в летнее время и его смещение-0400
(Я мог бы понять это, делаяDateTimeZone.forTimeZone(TimeZone.getTimeZone("EST"))
, Проблема в том, что эти трехбуквенные имена неоднозначны и не являются стандартными, и joda-time предполагает для них "значение по умолчанию". Итак, если вы не ожидали этого часового пояса и не хотите полагаться на значения по умолчанию, вы можете использовать карту с пользовательскими значениями, например:// mapping EST to some other timezone (I know it's wrong and Chicago is not EST, it's just an example) Map<String, DateTimeZone> map = new LinkedHashMap<>(); map.put("EST", DateTimeZone.forID("America/Chicago")); // parser for my custom map DateTimeParser customTimeZoneParser = new DateTimeFormatterBuilder().appendTimeZoneShortName(map).toParser(); DateTimeFormatter fmt = new DateTimeFormatterBuilder() // append common pattern .appendPattern("EEE, d MMM yyyy HH:mm:ss ") // optional offset .appendOptional(offsetParser) // optional custom timezone name .appendOptional(customTimeZoneParser) // optional timezone name (accepts all others that are not in the map) .appendOptional(zoneNameParser) // create formatter (use English Locale to make sure it parses weekdays and month names independent of JVM config) .toFormatter().withLocale(Locale.ENGLISH) // make sure the offset "+0000" is parsed .withOffsetParsed(); System.out.println(fmt.parseDateTime("Sat, 10 Jun 2017 12:49:45 EST"));
Это будет разбирать EST
как America/Chicago
(Я знаю, что это не так, и Чикаго не EST
, это просто пример того, как вы можете изменить значения по умолчанию, используя карту), и результат будет:
2017-06-10T12: 49: 45.000-05: 00
Если вы получили ошибку с первым кодом выше, вы также можете использовать это, отображение EST
в нужный часовой пояс (в зависимости от версии jodatime и Java, которые вы используете, EST
может не отображаться на значение по умолчанию и вызывает исключение, поэтому использование пользовательской карты позволяет избежать этого).
Новый API даты и времени
Как сказано в комментарии @Ole VV (а у меня вчера не было времени писать), joda-time заменяется новым Java Date and Time API, который намного превосходит старый Date
а также SimpleDateFormat
классы.
Если вы используете Java >= 8, java.time
пакет уже является частью JDK. Для Java <= 7 есть бэкпорт ThreeTen. А для Android есть ThreeTenABP (подробнее о том, как его использовать здесь).
Если вы начинаете новый проект, рассмотрите новый API вместо joda-time, потому что на сайте joda написано: Обратите внимание, что Joda-Time считается в значительной степени "законченным" проектом. Никаких серьезных улучшений не планируется. Если используется Java SE 8, перейдите на java.time (JSR-310).
Код ниже работает для обоих. Разница лишь в именах пакетов (в Java 8 java.time
и в ThreeTen Backport (или Android в ThreeTenABP) является org.threeten.bp
), но имена классов и методов совпадают.
Идея очень похожа на йодатим, с небольшими отличиями:
- Вы можете использовать дополнительные разделители разделов
[]
- набор с пользовательскими именами часовых поясов (для сопоставления
EST
к некоторому действительному не однозначному часовому поясу) (какEST
не сопоставляется ни с одним из значений по умолчанию) - новый класс используется:
ZonedDateTime
, который представляет дату и время с часовым поясом (поэтому он охватывает оба ваших случая)
Напоминаю, что эти классы находятся в java.time
пакет (или в org.threeten.bp
в зависимости от используемой версии Java, как описано выше):
// set with custom timezone names
Set<ZoneId> set = new HashSet<>();
// when parsing, ambiguous EST uses to New York
set.add(ZoneId.of("America/New_York"));
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// append pattern, with optional offset (delimited by [])
.appendPattern("EEE, d MMM yyyy HH:mm:ss[ Z]")
// append optional timezone name with custom set for EST
.optionalStart().appendLiteral(" ").appendZoneText(TextStyle.SHORT, set).optionalEnd()
// create formatter using English locale to make sure it parses weekdays and month names correctly
.toFormatter(Locale.ENGLISH);
ZonedDateTime est = ZonedDateTime.parse("Sat, 10 Jun 2017 12:49:45 EST", fmt);
ZonedDateTime utc = ZonedDateTime.parse("Sun, 11 Jun 2017 18:18:23 +0000", fmt);
System.out.println(est); // 2017-06-10T12:49:45-04:00[America/New_York]
System.out.println(utc); // 2017-06-11T18:18:23Z
Выход будет:
2017-06-10T12: 49: 45-04: 00 [Америка / New_York]
2017-06-11T18: 18: 23Z
Обратите внимание, что в первом случае EST
был установлен на America/New_York
(как настроено пользовательским набором). appendZoneText
делает свое дело, используя значения в пользовательском наборе для разрешения неоднозначных случаев.
И второй случай был установлен в UTC, так как смещение +0000
,
Если вы хотите преобразовать первый объект в UTC, это просто:
System.out.println(est.withZoneSameInstant(ZoneOffset.UTC)); // 2017-06-10T16:49:45Z
Выходными данными будут дата / время Нью-Йорка, преобразованные в UTC:
2017-06-10T16: 49: 45Z
Вместо ZoneOffset.UTC
Конечно, вы можете использовать любой часовой пояс или смещение, которое вы хотите (используя ZoneId
а также ZoneOffset
классы, проверьте Javadoc для более подробной информации).