Как разобрать RFC 3339 с помощью Java?

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

Я хотел бы проанализировать это в Java Date или Calendar Object.

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

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

Ответы

Ответ 1

Только что обнаружил, что 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

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

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");
}

Ответ 2

ТЛ; др

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

ISO 8601

На самом деле RFC 3339 - это всего лишь самопровозглашенный "профиль" действующего стандарта ISO 8601.

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

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

Ваша входная строка представляет момент в UTC. Буква 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.

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;  // Same moment, same point on the timeline, different wall-clock time.

преобразование

Я настоятельно рекомендую по возможности избегать устаревших классов даты и времени. Но если вам нужно взаимодействовать со старым кодом, который еще не обновлен до java.time, вы можете конвертировать туда и обратно. Вызовите новые методы, добавленные к старым классам.

Instant заменяет java.util.Date.

java.util.Date myJUDate = java.util.Date.from( instant ) ;  // From modern to legacy.
Instant instant = myJUDate.toInstant() ;                    // From legacy to modern.

ZonedDateTime заменяет GregorianCalendar.

java.util.GregorianCalendar myGregCal = java.util.GregorianCalendar.from( zdt ) ;  // From modern to legacy.
ZonedDateTime zdt = myGregCal.toZonedDateTime() ;           // From legacy to modern.

Если у вас есть java.util.Calendar который на самом деле является GregorianCalendar, выполните приведение.

java.util.GregorianCalendar myGregCal = ( java.util.GregorianCalendar ) myCal ;  // Cast to the concrete class.
ZonedDateTime zdt = myGregCal.toZonedDateTime() ;           // From legacy to modern.

Маркированные проблемы

Что касается ваших вопросов конкретных вопросов...

  • Нет внешних библиотек (банок)

Классы java.time встроены в Java 8, 9, 10 и более поздние версии. Реализация также включена в более позднюю версию Android. Для более ранней версии Java и более ранней версии Android см. Следующий раздел этого ответа.

  • Обрабатывает все приемлемые форматы RFC 3339

Различные классы java.time обрабатывают все известные мне форматы ISO 8601. Они даже обрабатывают некоторые форматы, которые таинственным образом исчезли из более поздних выпусков стандарта.

Для других форматов смотрите методы parse и toString различных классов, таких как LocalDate, OffsetDateTime и т.д. Кроме того, поиск, так как есть много примеров и обсуждений на эту тему.

  • Строка должна легко проверяться, чтобы увидеть, является ли она действительной датой RFC 3339

Чтобы проверить входные строки, перехватите DateTimeParseException.

try {
    Instant instant = Instant.parse( "2011-05-03T11:58:01Z" ) ;
} catch ( DateTimeParseException e ) {
    … handle invalid input
}

О java.time

Инфраструктура java.time встроена в Java 8 и более поздние версии. Эти классы вытеснять неприятные старые устаревшие классы даты и времени, такие как java.util.Date, Calendar, и SimpleDateFormat.

Проект Joda-Time, находящийся сейчас в режиме обслуживания, рекомендует перейти на классы java.time.

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

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

Где взять классы java.time?

  • Java SE 8, Java SE 9 и более поздние
    • Встроенный.
    • Часть стандартного Java API с комплексной реализацией.
    • Java 9 добавляет некоторые незначительные функции и исправления.
  • Java SE 6 и Java SE 7
    • Большая часть функциональности java.time перенесена на Java 6 и 7 в ThreeTen-Backport.
  • Android
    • Более поздние версии Android связывают реализации классов java.time.
    • Для более ранних версий Android (<26) проект ThreeTenABP адаптирует ThreeTen-Backport (упомянутый выше). Смотрите Как использовать ThreeTenABP….

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

Ответ 3

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

Вот список шаблонов для отдельных объявлений в RFC 3339:

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

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

В формате, который у вас есть, например. 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());

Ответ 7

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