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 для более подробной информации).

Другие вопросы по тегам