Общая поддержка формата ISO 8601 в Java 6

Java 7 представила поддержку в SimpleDateFormat класс для формата ISO 8601, через символ X (вместо нижнего или верхнего регистра Z). Поддержка таких форматов в Java 6 требует предварительной обработки, поэтому наилучшим подходом является вопрос.

Этот новый формат является расширенным Z (прописная буква Z), с 2 дополнительными вариантами:

  1. Поле "минуты" является необязательным (то есть действительны 2-значные вместо 4-значных часовых поясов)
  2. Символ двоеточия (':') можно использовать для отделения двухзначного поля "часы" от двухзначного поля "минуты").

Итак, как видно из документации по Java 7SimpleDateFormatследующие 3 формата теперь действительны (вместо только второго Z в Java 6) и, конечно же, эквивалентны:

  1. -08
  2. -0800
  3. -08: 00

Как обсуждалось в предыдущем вопросе об особом случае поддержки такого "расширенного" формата часового пояса, всегда с разделителем ":", лучший подход для обратной передачи функциональности Java 7 в Java 6 заключается в создании подкласса SimpleDateformat класс и переопределить его parse() метод, то есть:

public Date parse(String date, ParsePosition pos)
{
    String iso = ... // Replace the X with a Z timezone string, using a regex

    if (iso.length() == date.length())
    {
        return null; // Not an ISO 8601 date
    }

    Date parsed = super.parse(iso, pos);

    if (parsed != null)
    {
        pos.setIndex(pos.getIndex()+1); // Adjust for ':'
    }

    return parsed;
}

Обратите внимание, что подкласс SimpleDateFormat объекты выше должны быть инициализированы с соответствующими Zна основе шаблона, т.е. если подкласс ExtendedSimpleDateformat и вы хотите разобрать даты, соответствующие шаблону yyyy-MM-dd'T'HH:mm:ssXто вы должны использовать объекты, созданные как

new ExtendedSimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");

В вышеупомянутом предыдущем вопросе регулярное выражение :(?=[0-9]{2}$) было предложено избавиться от ':' и в подобном вопросе регулярное выражение (?<=[+-]\d{2})$ было предложено добавить "минутное" поле как 00, если нужно.

Очевидно, что успешное выполнение 2 замен может быть использовано для достижения полной функциональности. Итак iso локальная переменная в переопределенной parse() метод будет установлен как

iso = date.replaceFirst(":(?=[0-9]{2}$)","");

или же

iso = iso.replaceFirst("(?<=[+-]\\d{2})$", "00");

с if проверить, чтобы убедиться, что pos значение также устанавливается позже, а также для length() Сравнение ранее.

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

Реализация предназначена для кода, который читает очень большое количество строковых полей, которые могут быть в любом формате (даже полностью без даты), выбирает только те, которые соответствуют формату, и возвращает проанализированный Java Date объект.

Таким образом, точность и скорость имеют первостепенное значение (т. Е. Если использовать 2 прохода быстрее, такой подход предпочтительнее).

3 ответа

Решение

Кажется, что вы можете использовать это:

import java.util.Calendar;
import javax.xml.bind.DatatypeConverter;

public class TestISO8601 {
    public static void main(String[] args) {
        parse("2012-10-01T19:30:00+02:00"); // UTC+2
        parse("2012-10-01T19:30:00Z");      // UTC
        parse("2012-10-01T19:30:00");       // Local
    }
    public static Date parse(final String str) {
        Calendar c = DatatypeConverter.parseDateTime(str);
        System.out.println(str + "\t" + (c.getTime().getTime()/1000));
        return c.getTime();
    }
}

Вы можете использовать java.time, современный Java-интерфейс даты и времени, в Java 6. Это может показаться мне хорошим и перспективным решением. Имеет хорошую поддержку ISO 8601.

import org.threeten.bp.OffsetDateTime;
import org.threeten.bp.format.DateTimeFormatter;

public class DemoIso8601Offsets {
    public static void main(String[] args) {
        System.out.println(OffsetDateTime.parse("2012-10-01T19:30:00+0200", 
                DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ssXX")));
        System.out.println(OffsetDateTime.parse("2012-10-01T19:30:00+02", 
                DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ssX")));
        System.out.println(OffsetDateTime.parse("2012-10-01T19:30:00+02:00"));
        System.out.println(OffsetDateTime.parse("2012-10-01T19:30:00Z"));
    }
}

Выход из этой программы:

2012-10-01T19:30+02:00
2012-10-01T19:30+02:00
2012-10-01T19:30+02:00
2012-10-01T19:30Z

Требуется добавить библиотеку ThreeTen Backport в настройку проекта.

  • В Java 8 и более поздних версиях, а также на более новых устройствах Android (начиная с уровня API 26) встроен современный API.
  • В Java 6 и 7 получите ThreeTen Backport, бэкпорт новых классов (ThreeTen для JSR 310; см. Ссылки внизу).
  • На (более старой) версии Android используется версия Android ThreeTen Backport. Это называется ThreeTenABP. И убедитесь, что вы импортируете классы даты и времени из org.threeten.bp с подпакетами.

Как вы можете видеть из кода, +02 а также +0200 требует форматера, где вы указываете формат смещения, в то время как +02:00 (а также Z тоже) соответствует формату по умолчанию и не требует указания.

Можем ли мы проанализировать все форматы смещения, используя один и тот же форматер?

При чтении смешанных данных вы не хотите обрабатывать каждый формат смещения специально. Лучше использовать дополнительные части в строке шаблона формата:

    DateTimeFormatter allInOne 
            = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss[XXX][XX][X]");
    System.out.println(OffsetDateTime.parse("2012-10-01T19:30:00+0200", allInOne));
    System.out.println(OffsetDateTime.parse("2012-10-01T19:30:00+02", allInOne));
    System.out.println(OffsetDateTime.parse("2012-10-01T19:30:00+02:00", allInOne));
    System.out.println(OffsetDateTime.parse("2012-10-01T19:30:00Z", allInOne));

Выход такой же, как указано выше. Квадратные скобки в [XXX][XX][X] имею ввиду что либо формат +02:00, +0200 или же +02 может присутствовать.

связи

Тот же подход работает для разных миллисекунд и разных смещений:

      String DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss[.SSS][.SS][.S][XXX][XX][X]";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN);

Date convertDate(String dateString) {
    return Date.from(OffsetDateTime.parse(dateString, formatter).toInstant());
}

Иногда вам нужно иметь два разных шаблона для геттера и сеттера:

      String DATE_TIME_PATTERN_SET = "yyyy-MM-dd'T'HH:mm:ss[.SSS][.SS][.S][XXX][XX][X]";
String DATE_TIME_PATTERN_GET = "yyyy-MM-dd'T'HH:mm:ssXXX";
DateTimeFormatter formatterSet = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN_SET);
DateFormat dateFormat = new SimpleDateFormat(DATE_TIME_PATTERN_GET);

Date convertToDate(String dateString) {
    return Date.from(OffsetDateTime.parse(dateString, formatterSet).toInstant());
}

String convertToString(Date date) {
    dateFormat.setTimeZone(TimeZone.getDefault());
    return dateFormat.format(date).replaceAll("Z$", "+00:00");
}
Другие вопросы по тегам