如何使用Java解析RFC 3339日期时间?

我正在尝试将HTML5日期时间输入字段中返回的日期作为值进行解析。 在Opera中试一试看一个例子。 返回的日期如下所示: 2011-05-03T11:58:01Z

我想将它解析为Java Date或Calendar Object。

理想情况下,解决方案应该包含以下内容:

  • 没有外部库(jar子)
  • 处理所有可接受的RFC 3339格式
  • 应该能够轻松validation字符串以查看它是否是有效的RFC 3339日期

刚刚发现Google在Google HTTP Client Library中实现了Rfc3339解析器

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

因此,原则上这将使用不同的SimpleDateFormat模式来完成。

这里是RFC 3339中各个声明的模式列表:

  • date-fullyear: yyyy
  • 日期月: MM
  • date-mday: dd
  • 时间 – 小时: HH
  • 时间 – 分钟: mm
  • 时间秒: ss
  • time-secfrac: .SSSS表示毫秒,但不清楚如果这些数字多于或少于3位会发生什么。)
  • time-numoffset :(例如+02:00似乎不受支持 – 相反它支持格式GMT+02:00 +0200GMT+02:00以及使用zZ一些命名时区。)
  • time-offset: 'Z' (不支持其他时区) – 在使用之前你应该使用format.setTimezone(TimeZone.getTimeZone("UTC")) 。)
  • 部分时间: HH:mm:ssHH: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;博士

 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的时刻。 最后的ZZulu缩写,意思是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. 

如果你有一个实际上是GregorianCalendarjava.util.Calendar ,则GregorianCalendar

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

项目符号问题

关于你的问题的具体问题……

  • 没有外部库(jar子)

java.time类内置于Java 8,9,10及更高版本中。 稍后的Android中还包含一个实现。 对于早期的Java和早期的Android,请参阅本答案的下一部分。

  • 处理所有可接受的RFC 3339格式

各种java.time类处理我所知道的每种ISO 8601格式。 他们甚至处理了一些神秘地从标准的后期版本中消失的格式。

对于其他格式,请参阅各种类的parsetoString方法,如LocalDateOffsetDateTime等。 此外,搜索Stack Overflow,因为有很多关于此主题的示例和讨论。

  • 应该能够轻松validation字符串以查看它是否是有效的RFC 3339日期

要validation输入字符串,请捕获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.DateCalendarSimpleDateFormat

现在处于维护模式的Joda-Time项目建议迁移到java.time类。

要了解更多信息,请参阅Oracle教程 。 并搜索Stack Overflow以获取许多示例和解释。 规范是JSR 310 。

您可以直接与数据库交换java.time对象。 使用符合JDBC 4.2或更高版本的JDBC驱动程序 。 不需要字符串,不需要java.sql.*类。

从哪里获取java.time类?

  • Java SE 8Java SE 9及更高版本
    • 内置。
    • 带有捆绑实现的标准Java API的一部分。
    • Java 9增加了一些小function和修复。
  • Java SE 6Java SE 7
    • 许多java.timefunction都被反向移植到ThreeTen-Backport中的 Java 6和7。
  • Android的
    • 更高版本的Android捆绑java.time类的实现。
    • 对于早期的Android(<26), ThreeTenABP项目采用ThreeTen-Backport (如上所述)。 请参见如何使用ThreeTenABP ….

ThreeTen-Extra项目使用其他类扩展了java.time。 该项目是未来可能添加到java.time的试验场。 您可以在这里找到一些有用的课程,如IntervalYearWeekYearQuarter等。

这是一个简单的方法。 它可能适合您的需求。

也许不是最优雅的方式,但肯定是我最近做的一个:

 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,下面的代码可以。 但是,我最近在Chrome和Opera中尝试了html5 datetime,它给了我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)