Как проанализировать RFC 3339 datetimes с Java?

Я пытаюсь проанализировать дату, возвращаемую как значение из HTML5 datetime поле ввода. Попробуйте это в Opera, чтобы увидеть пример. Возвращаемая дата выглядит следующим образом:2011-05-03T11:58:01Z.

Я хотел бы разобрать это в Java Date или Calendar объект.

В идеале решение должно иметь следующие вещи:

  • нет внешних библиотек (jars)
  • обрабатывает все приемлемые форматы RFC 3339
  • строка должна быть легко проверено, чтобы узнать, является ли это допустимой датой RFC 3339

7 ответов


только что обнаружил, что google реализовал синтаксический анализатор Rfc3339 в клиентской библиотеке Google HTTP

https://github.com/google/google-http-java-client/blob/dev/google-http-client/src/main/java/com/google/api/client/util/DateTime.java

проверено. Он хорошо работает для разбора варьируется sub секунд фрагмента времени.

import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;

import com.google.api.client.util.DateTime;

DateTimeFormatter formatter = DateTimeFormatter
            .ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
            .withZone(ZoneId.of("UTC"));

@Test
public void test1e9Parse() {
    String timeStr = "2018-04-03T11:32:26.553955473Z";

    DateTime dateTime = DateTime.parseRfc3339(timeStr);
    long millis = dateTime.getValue();

    String result = formatter.format(new Date(millis).toInstant());

    assert result.equals("2018-04-03T11:32:26.553Z");
}

@Test
public void test1e3Parse() {
    String timeStr = "2018-04-03T11:32:26.553Z";

    DateTime dateTime = DateTime.parseRfc3339(timeStr);
    long millis = dateTime.getValue();

    String result = formatter.format(new Date(millis).toInstant());

    assert result.equals("2018-04-03T11:32:26.553Z");
}

@Test
public void testEpochSecondsParse() {

    String timeStr = "2018-04-03T11:32:26Z";

    DateTime dateTime = DateTime.parseRfc3339(timeStr);
    long millis = dateTime.getValue();

    String result = formatter.format(new Date(millis).toInstant());

    assert result.equals("2018-04-03T11:32:26.000Z");
}

Итак, в принципе это будет сделано с использованием разных SimpleDateFormat шаблоны.

вот список шаблонов для индивидуальные декларации в RFC 3339:

  • дата-fullyear:yyyy
  • дата-месяц: MM
  • дата-mday: dd
  • время-час: HH
  • времени минутах: mm
  • время-Второй: ss
  • time-secfrac:.SSS (S означает миллисекунду, хотя-неясно, что произойдет, если их будет больше или меньше 3 цифр.)
  • time-numoffset: (как +02:00 вроде бы не поддерживается - вместо этого он поддерживает форматы +0200, GMT+02:00 и некоторые именованные часовые пояса с помощью z и Z.)
  • смещение по времени:'Z' (не поддерживает другие часовые пояса) - вы должны использовать format.setTimezone(TimeZone.getTimeZone("UTC")) перед использованием этот.)
  • частичное-время: HH:mm:ss или HH:mm:ss.SSS.
  • полный-время: HH:mm:ss'Z' или HH:mm:ss.SSS'Z'.
  • полное-дата: yyyy-MM-dd
  • дата-время: yyyy-MM-dd'T'HH:mm:ss'Z' или yyyy-MM-dd'T'HH:mm:ss.SSS'Z'

как мы видим, это, похоже, не в состоянии разобрать все. Может быть, было бы лучше реализовать RFC3339DateFormat С нуля (с использованием регулярных выражений, для простоты или разбора вручную, для эффективности).


tl; dr

Instant.parse( "2011-05-03T11:58:01Z" )

ISO 8601

на самом деле RFC 3339 это всего лишь профиль фактического стандарта,ISO 8601.

RFC отличается тем, что он намеренно нарушает ISO 8601, чтобы позволить отрицательное смещение нулевых часов (-00:00) и дает это семантическое значение "смещение неизвестно". Эта семантика кажется мне очень плохой идеей. Я советую придерживаться более разумных правил ISO 8601. В ISO 8601, имея отсутствие смещения вообще означает, что смещение неизвестно-очевидное значение, тогда как правило RFC является заумным.

современные java.время классы используют форматы ISO 8601 по умолчанию при разборе / генерации строк.

ваша входная строка представляет момент в UTC. The Z на конце сокращенно от Zulu и означает UTC.

Instant (не Date)

современный класс Instant представляет момент в UTC. Этот класс заменяет java.util.Date, и использует более точное разрешение наносекунд, а не миллисекунд.

Instant instant = Instant.parse( "2011-05-03T11:58:01Z" ) ;

ZonedDateTime (не Calendar)

чтобы увидеть тот же самый момент через настенные часы, используемые людьми определенного региона (часовой пояс), примените ZoneId и ZonedDateTime. Этот класс ZonedDateTime заменяет java.util.Calendar, теперь режим обслуживания, советует миграцию в java.время классы.

чтобы узнать больше, см. В Oracle Учебник. И search Stack Overflow для многих примеров и объяснений. Спецификация JSR 310.

вы можете обменять java.время объекты непосредственно с вашей базой данных. Используйте драйвер JDBC соответствуют JDBC 4.2 или позже. Нет необходимости в строках, нет необходимости в java.sql.* классы.

где получить java.время занятий?

  • Java SE 8, Java SE 9, и позже
    • встроенный.
    • часть стандартного API Java в комплекте реализация.
    • Java 9 добавляет некоторые незначительные функции и исправления.
  • Java SE 6 и Java SE 7
    • большая часть java.функциональность времени обратно портирована на Java 6 & 7 в ThreeTen-Backport.
  • Android
      версии Android bundle реализации java.классы время.
  • для более раннего Android ( ThreeTenABP проект приспосабливается ThreeTen-Backport (упоминалось выше). См.как использовать ThreeTenABP....

на ThreeTen-Extra проект расширяет java.время с дополнительными занятиями. Этот проект является полигон для возможных будущих дополнений к Java.время. Здесь вы можете найти полезные классы, такие как Interval, YearWeek, YearQuarter и больше.


здесь простой способ сделать это. Это может удовлетворить ваши потребности.


может быть, не самый элегантный способ, но, конечно, работает тот, который я недавно сделал:

Calendar cal = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss");
cal.setTime(sdf.parse(dateInString.replace("Z", "").replace("T", "-")));

с форматом, который у вас есть, например, 2011-05-03T11:58:01Z, ниже кода. Тем не менее, я недавно пробовал HTML5 datetime в Chrome и Opera, он дает мне 2011-05-03T11:58Z --> не имеет части ss, которая не может быть обработана кодом ниже.

new Timestamp(javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar(date).toGregorianCalendar().getTimeInMillis());

Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(datetimeInFRC3339format)