Java 8 Дата и время: разбор строки ISO 8601 без двоеточия в смещении

Мы пытаемся проанализировать следующую строку DateTime ISO 8601 со смещением часового пояса:

final String input = "2022-03-17T23:00:00.000+0000";

OffsetDateTime.parse(input);
LocalDateTime.parse(input, DateTimeFormatter.ISO_OFFSET_DATE_TIME);

Оба подхода сбой (что имеет смысл, так как OffsetDateTime также использует DateTimeFormatter.ISO_OFFSET_DATE_TIME) из-за двоеточия в смещении временной зоны.

java.time.format.DateTimeParseException: текст '2022-03-17T23: 00: 00.000 + 0000' не может быть проанализирован по индексу 23

Но согласно Википедии есть 4 действительных формата для смещения временной зоны:

<time>Z 
<time>±hh:mm 
<time>±hhmm 
<time>±hh

Другие фреймворки/языки могут анализировать эту строку без каких-либо проблем, например Javascript Date() или Jacksons ISO8601Utils (здесь они обсуждаются здесь)

Теперь мы можем написать собственный DateTimeFormatter со сложным RegEx, но, на мой взгляд, библиотека java.time должна иметь возможность анализировать эту действительную строку ISO 8601 по умолчанию, так как она действительна.

В настоящее время мы используем Jacksons ISO8601DateFormat, но мы бы предпочли использовать официальную библиотеку date.time для работы. Каким будет ваш подход к решению этой проблемы?

Ответ 1

Если вы хотите проанализировать все допустимые форматы смещений (Z, ±hh:mm, ±hhmm и ±hh), одной из альтернатив является использование java.time.format.DateTimeFormatterBuilder с необязательными шаблонами (к сожалению, похоже, что нет единого шаблон письма, чтобы соответствовать им всем):

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
    // date/time
    .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
    // offset (hh:mm - "+00:00" when it zero)
    .optionalStart().appendOffset("+HH:MM", "+00:00").optionalEnd()
    // offset (hhmm - "+0000" when it zero)
    .optionalStart().appendOffset("+HHMM", "+0000").optionalEnd()
    // offset (hh - "Z" when it zero)
    .optionalStart().appendOffset("+HH", "Z").optionalEnd()
    // create formatter
    .toFormatter();
System.out.println(OffsetDateTime.parse("2022-03-17T23:00:00.000+0000", formatter));
System.out.println(OffsetDateTime.parse("2022-03-17T23:00:00.000+00", formatter));
System.out.println(OffsetDateTime.parse("2022-03-17T23:00:00.000+00:00", formatter));
System.out.println(OffsetDateTime.parse("2022-03-17T23:00:00.000Z", formatter));

Все четыре вышеупомянутых случая проанализируют это к 2022-03-17T23:00Z.


Вы также можете определить одиночный строковый шаблон, если хотите, используя [] для разделения дополнительных разделов:

// formatter with all possible offset patterns
DateTimeFormatter formatter = DateTimeFormatter
    .ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS[xxx][xx][X]");

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


Заметки:

  • Форматер с дополнительными разделами, подобными приведенным выше, хорош для анализа, но не для форматирования. При форматировании он напечатает все дополнительные секции, что означает, что он будет печатать смещение много раз. Итак, для форматирования даты просто используйте другой форматер.
  • Второй форматер принимает ровно 3 цифры после десятичной точки (из-за .SSS). С другой стороны, ISO_LOCAL_DATE_TIME более гибок: секунды и наносекунды являются необязательными, и он также принимает от 0 до 9 цифр после десятичной точки. Выберите тот, который лучше всего подходит для ваших входных данных.

Ответ 2

Вам не нужно писать сложное регулярное выражение - вы можете создать DateTimeFormatter, который легко будет работать с этим форматом:

DateTimeFormatter formatter =
    DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSX", Locale.ROOT);

OffsetDateTime odt = OffsetDateTime.parse(input, formatter);

Это также примет "Z" вместо "0000". Он не принимает "+00: 00" (с двоеточием или аналогичным. Это удивительно, учитывая документацию, но если ваше значение всегда имеет смещение UTC без двоеточия, все должно быть в порядке.

Ответ 3

Я бы не назвал это решением, но обходным путем. Шаблон SimpleDateFormat Z поддерживает отображаемый вами временной синтаксис, поэтому вы можете сделать что-то вроде этого:

final String input = "2022-03-17T23:00:00.000+0000";

try {
    OffsetDateTime.parse(input);
    LocalDateTime.parse(input, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
catch (DateTimeParseException e) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SZ", Locale.GERMANY);
    sdf.parse(input);
}

Вы по-прежнему используете официальные библиотеки, поставляемые с JVM. Один из них не является частью date.time-library, но все же; -)

Ответ 4

Так как это без двоеточия, вы можете использовать свою собственную строку формата:

final String input = "2022-03-17T23:00:00.000+0000";

    DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
    Date parsed = df.parse(input);
    System.out.println(parsed);