Как я могу проанализировать даты RFC 3339 с Java?
Я пытаюсь разобрать дату, возвращенную как значение из HTML5 datetime
поле ввода. Попробуйте в Opera, чтобы увидеть пример. Дата возвращения выглядит следующим образом: 2011-05-03T11:58:01Z
,
Я хотел бы разобрать это в Java Date или Calendar Object.
В идеале решение должно иметь следующие вещи:
- Нет внешних библиотек (банок)
- Обрабатывает все приемлемые форматы RFC 3339
- Строка должна легко проверяться, чтобы увидеть, является ли она действительной датой RFC 3339
10 ответов
Только что обнаружил, что Google реализовал парсер Rfc3339 в клиентской библиотеке Google HTTP
Проверено. Это хорошо работает, чтобы разобрать варьируется фрагмент времени секунд.
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()
}
Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(datetimeInFRC3339format)