【问题标题】:How do I parse RFC 3339 datetimes with Java?如何使用 Java 解析 RFC 3339 日期时间?
【发布时间】:2011-08-27 15:28:21
【问题描述】:

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

我想将其解析为 Java 日期或日历对象。

理想的解决方案应该具备以下几点:

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

【问题讨论】:

标签: java datetime html date-parsing rfc3339


【解决方案1】:

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,请强制转换。

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 格式。他们甚至处理了一些在标准的后续版本中神秘消失的格式。

其他格式见LocalDateOffsetDateTime等各个类的parsetoString方法。此外,请搜索 Stack Overflow,因为有 许多 示例和关于此主题的讨论。

  • 应该能够轻松验证字符串以查看它是否是有效的 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 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

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

从哪里获取 java.time 类?

  • Java SE 8Java SE 9 及更高版本
  • 内置。​​
  • 标准 Java API 的一部分,带有捆绑实现。
  • Java 9 添加了一些小功能和修复。
  • Java SE 6Java SE 7
  • ThreeTen-Backport 中的大部分 java.time 功能都向后移植到 Java 6 和 7。
  • Android
  • java.time 类的 Android 捆绑包实现的更高版本。
  • 对于早期的 Android (ThreeTenABP 项目采用 ThreeTen-Backport(如上所述)。见How to use ThreeTenABP…

ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

【讨论】:

  • 不幸的是,Instant 在带有时区、2019-11-30T10:30:00+02:002019-10-12T07:20:50.52+00:00 的日期上总是失败,无论是否有毫秒。它失败并出现java.time.format.DateTimeParseException: Text '2019-11-30T10:30:00+02:00' could not be parsed at index 19 之类的错误,或者index 23 上的错误。因此我不能在生产中使用它,它实际上不支持 RFC-3339。
【解决方案2】:

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

这里是individual declarations in RFC 3339 的模式列表:

  • 日期全听:yyyy
  • 日期-月份:MM
  • 日期-mday:dd
  • 时间-小时:HH
  • 时间-分钟:mm
  • 时间-秒:ss
  • time-secfrac:.SSSS 表示毫秒,但尚不清楚如果这些数字多于或少于 3 位会发生什么。)
  • time-numoffset:(好像+02:00 似乎不受支持 - 相反,它支持+0200GMT+02:00 和一些使用zZ 的命名时区。)
  • 时间偏移:'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 会更好(使用正则表达式,为了简单,或者手动解析,为了效率)。

【讨论】:

  • 不应该 date-fullyearyyyy
  • 看起来你就在这里 - EEEE 代表“星期几”。我不确定我是从哪里得到这个的,也许是从另一个为 y 带来不同的图书馆?
  • 在 Java 7+ 中,SimpleDateFormat 添加了 X timezone format 一个 Z 格式的超集,接受 +/- 两位数或四位数字和可选冒号(但忽略分钟)
  • SimpleDateFormat 阻塞超过毫秒的分数。
  • @Thomas 在我使用 Java 8 时,我从来不需要用它来解析日期(它总是在我用于 HTTP 和/或 Json 的库中完成映射)。随意添加您自己的答案,我会赞成。 (看起来DateTimeFormatter.ISO_OFFSET_DATE_TIME 包含此功能。)
【解决方案3】:

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

【讨论】:

  • 你似乎在使用 java.time 类(好)。但是java.time 中没有DateTime 类。我很困惑。
  • 对不起,忘记为 DateTime 类编写导入。它属于 com.google.api.client.util 包。
【解决方案4】:

也许不是最优雅的方式,但肯定是我最近制作的一种方式:

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

【讨论】:

    【解决方案5】:

    使用您拥有的格式,例如2011-05-03T11:58:01Z,下面的代码就可以了。但是,我最近在 Chrome 和 Opera 中试用了 html5 日期时间,它给了我 2011-05-03T11:58Z --> 没有下面的代码无法处理的 ss 部分。

    new Timestamp(javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar(date).toGregorianCalendar().getTimeInMillis());
    

    【讨论】:

      【解决方案6】:

      我正在使用这个:

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

      【讨论】:

        【解决方案7】:

        虽然,这个问题已经很老了,但它可能会对想要这个答案的 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) {
                ""
            }
        }
        

        现在在您的活动/功能/dataSourse Mapper 中使用此方法以字符串格式获取日期,如下所示

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

        输出会是这样的

        18-01-2022
        

        【讨论】:

          【解决方案8】:
          Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(datetimeInFRC3339format)
          

          【讨论】:

            猜你喜欢
            • 2020-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-03-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多