Как я могу проанализировать даты RFC 3339 с Java?

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

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

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

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

10 ответов

Решение

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

ТЛ; др

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 и многое другое.

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

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

  • Дата-fullyear: yyyy
  • дата месяц: MM
  • Дата-MDAY: dd
  • Время-час: HH
  • минутное время: mm
  • Время-вторых: ss
  • Время-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 с нуля (использование регулярных выражений для простоты или разбор вручную для эффективности).

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

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());

Хотя вопрос очень старый, но он может помочь тому, кто хочет получить Kotlin-версию этого ответа. Используя этот файл, любой может преобразовать дату Rfc3339 в любой формат даты. Здесь я беру пустое имя файла DateUtilи создайте функцию с именем getDateString()который имеет 3 аргумента.

      1st argument : Your input date
2nd argument : Your input date pattern
3rd argument : Your wanted date pattern

DateUtil.kt

      object DatePattern {
    const val DAY_MONTH_YEAR = "dd-MM-yyyy"
    const val RFC3339 = "yyyy-MM-dd'T'HH:mm:ss'Z'"
}

fun getDateString(date: String, inputDatePattern: String, outputDatePattern: String): String {
    return try {
        val inputFormat = SimpleDateFormat(inputDatePattern, getDefault())
        val outputFormat = SimpleDateFormat(outputDatePattern, getDefault())

        outputFormat.format(inputFormat.parse(date))
    } catch (e: Exception) {
        ""
    }
}

И теперь используйте этот метод в своей деятельности/функции/датасорсе Mapper, чтобы получить дату в формате строки, как это

      getDate("2022-01-18T14:41:52Z", RFC3339, DAY_MONTH_YEAR)

и вывод будет таким

      18-01-2022

Я использую это:

DateTimeFormatter RFC_3339_DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()
            .append(ISO_LOCAL_DATE_TIME)
            .optionalStart()
            .appendOffset("+HH:MM", "Z")
            .optionalEnd()
            .toFormatter();

Пример:

String dateTimeString = "2007-05-01T15:43:26.3452+07:00";
ZonedDateTime zonedDateTime = ZonedDateTime.from(RFC_3339_DATE_TIME_FORMATTER.parse(dateTimeString));

Для справки в будущем, в качестве альтернативы, вы можете использовать ITU[1], который написан от руки для точного разбора RFC-3339, а также позволяет вам легко справляться с дополнительными секундами. Библиотека не зависит от зависимостей и весит всего 18 КБ.

Полное раскрытие: я автор

      try 
{
    final OffsetDateTime dateTime = ITU.parseDateTime(dateTimeStr);
}
catch (LeapSecondException exc) 
{
  // The following helper methods are available let you decide how to progress
  //int exc.getSecondsInMinute()
  //OffsetDateTime exc.getNearestDateTime()
  //boolean exc.isVerifiedValidLeapYearMonth()
}

[1] - https://github.com/ethlo/itu

Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(datetimeInFRC3339format)
Другие вопросы по тегам