如何使用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解析器
测试。 它可以很好地解析不同的子秒时间片段。
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:
.SSS
(S
表示毫秒,但不清楚如果这些数字多于或少于3位会发生什么。) - time-numoffset :(例如
+02:00
似乎不受支持 – 相反它支持格式GMT+02:00
+0200
,GMT+02:00
以及使用z
和Z
一些命名时区。) - time-offset:
'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;博士
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.
如果你有一个实际上是GregorianCalendar
的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.
项目符号问题
关于你的问题的具体问题……
- 没有外部库(jar子)
java.time类内置于Java 8,9,10及更高版本中。 稍后的Android中还包含一个实现。 对于早期的Java和早期的Android,请参阅本答案的下一部分。
- 处理所有可接受的RFC 3339格式
各种java.time类处理我所知道的每种ISO 8601格式。 他们甚至处理了一些神秘地从标准的后期版本中消失的格式。
对于其他格式,请参阅各种类的parse
和toString
方法,如LocalDate
, OffsetDateTime
等。 此外,搜索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.Date
, Calendar
和SimpleDateFormat
。
现在处于维护模式的Joda-Time项目建议迁移到java.time类。
要了解更多信息,请参阅Oracle教程 。 并搜索Stack Overflow以获取许多示例和解释。 规范是JSR 310 。
您可以直接与数据库交换java.time对象。 使用符合JDBC 4.2或更高版本的JDBC驱动程序 。 不需要字符串,不需要java.sql.*
类。
从哪里获取java.time类?
- Java SE 8 , Java SE 9及更高版本
- 内置。
- 带有捆绑实现的标准Java API的一部分。
- Java 9增加了一些小function和修复。
- Java SE 6和Java 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的试验场。 您可以在这里找到一些有用的课程,如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,下面的代码可以。 但是,我最近在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)