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

发布于 2024-11-08 17:19:37 字数 279 浏览 6 评论 0 原文

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

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

理想情况下,解决方案应具有以下内容:

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

I'm trying to parse the date returned as a value from the HTML5 datetime input field. Try it in Opera to see an example. The date returned looks like this: 2011-05-03T11:58:01Z.

I'd like to parse that into a Java Date or Calendar Object.

Ideally a solution should have the following things:

  • No external libraries (jars)
  • Handles all acceptable RFC 3339 formats
  • A String should be able to be easily validated to see if it is a valid RFC 3339 date

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

计㈡愣 2024-11-15 17:19:37

tl;dr

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.

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

对于其他格式,请参阅各种类的 parsetoString 方法,例如 LocalDateOffsetDateTime 等在。另外,请搜索 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 及更高版本中。这些类取代了麻烦的旧遗留日期时间类,例如java.util.Date日历,& SimpleDateFormat

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

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

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

从哪里获取 java.time 类?

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

tl;dr

Instant.parse( "2011-05-03T11:58:01Z" )

ISO 8601

Actually, RFC 3339 is but a mere self-proclaimed “profile” of the actual standard, ISO 8601.

The RFC is different in that it purposely violates ISO 8601 to allow a negative offset of zero hours (-00:00) and gives that a semantic meaning of “offset unknown“. That semantic seems like a very bad idea to me. I advise sticking with the more sensible ISO 8601 rules. In ISO 8601, having no offset at all means the offset is unknown – an obvious meaning, whereas the RFC rule is abstruse.

The modern java.time classes use the ISO 8601 formats by default when parsing/generating strings.

Your input string represents a moment in UTC. The Z on the end is short for Zulu and means UTC.

Instant (not Date)

The modern class Instant represents a moment in UTC. This class replaces java.util.Date, and uses a finer resolution of nanoseconds rather than milliseconds.

Instant instant = Instant.parse( "2011-05-03T11:58:01Z" ) ;

ZonedDateTime (not Calendar)

To see that same moment through the wall-clock time used by the people of a certain region (a time zone), apply a ZoneId to get a ZonedDateTime. This class ZonedDateTime replaces the java.util.Calendar class.

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;  // Same moment, same point on the timeline, different wall-clock time.

Converting

I strongly recommend avoiding the legacy date-time classes when possible. But if you must inter-operate with old code not yet updated to java.time, you may convert back-and-forth. Call new methods added to the old classes.

Instant replaces 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 replaces GregorianCalendar.

java.util.GregorianCalendar myGregCal = java.util.GregorianCalendar.from( zdt ) ;  // From modern to legacy.
ZonedDateTime zdt = myGregCal.toZonedDateTime() ;           // From legacy to modern.

If you have a java.util.Calendar that is actually a GregorianCalendar, cast.

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

Bulleted concerns

Regarding your Question’s specific issues…

  • No external libraries (jars)

The java.time classes are built into Java 8, 9, 10, and later. An implementation is also included in later Android. For earlier Java and earlier Android, see the next section of this Answer.

  • Handles all acceptable RFC 3339 formats

The various java.time classes handle every ISO 8601 format I know of. They even handle some formats that mysteriously disappeared from later editions of the standard.

For other formats, see the parse and toString methods of the various classes such as LocalDate, OffsetDateTime, and so on. Also, search Stack Overflow as there are many examples and discussions on this topic.

  • A String should be able to be easily validated to see if it is a valid RFC 3339 date

To validate input strings, trap for DateTimeParseException.

try {
    Instant instant = Instant.parse( "2011-05-03T11:58:01Z" ) ;
} catch ( DateTimeParseException e ) {
    … handle invalid input
}

About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

流年里的时光 2024-11-15 17:19:37

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

以下是 RFC 3339 中的单独声明的模式列表:

  • 日期-fullear:yyyy
  • 日期-月份:MM
  • 日期-mday:dd
  • 时间-小时:HH
  • 时间-分钟: mm
  • time-second: 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 是一个更好的主意(为了简单起见,使用正则表达式,为了提高效率,使用手动解析)。

So, in principle this would be done using different SimpleDateFormat patterns.

Here a list of patterns for the individual declarations in RFC 3339:

  • date-fullyear: yyyy
  • date-month: MM
  • date-mday: dd
  • time-hour: HH
  • time -minute: mm
  • time-second: ss
  • time-secfrac: .SSS (S means millisecond, though - it is not clear what would happen if there are more or less than 3 digits of these.)
  • time-numoffset: (like +02:00 seems to be not supported - instead it supports the formats +0200, GMT+02:00 and some named time zones using z and Z.)
  • time-offset: 'Z' (not supporting other time zones) - you should use format.setTimezone(TimeZone.getTimeZone("UTC")) before using this.)
  • partial-time: HH:mm:ss or HH:mm:ss.SSS.
  • full-time: HH:mm:ss'Z' or HH:mm:ss.SSS'Z'.
  • full-date: yyyy-MM-dd
  • date-time: yyyy-MM-dd'T'HH:mm:ss'Z' or yyyy-MM-dd'T'HH:mm:ss.SSS'Z'

As we can see, this seems not to be able to parse everything. Maybe it would be a better idea to implement an RFC3339DateFormat from scratch (using regular expressions, for simplicity, or parsing by hand, for efficiency).

阪姬 2024-11-15 17:19:37

刚刚发现谷歌在谷歌HTTP客户端库中实现了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");
}

Just found that google implemented Rfc3339 parser in Google HTTP Client Library

https://github.com/google/google-http-java-client/blob/dev/google-http-client/src/main/java/com/google/api/client/util/DateTime.java

Tested. It works well to parse varies sub seconds time fragment.

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");
}
吖咩 2024-11-15 17:19:37

使用您的格式,例如 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());

With the format you have e.g. 2011-05-03T11:58:01Z, below code will do. However, I recently tryout html5 datetime in Chrome and Opera, it give me 2011-05-03T11:58Z --> do not have the ss part which cannot be handled by code below.

new Timestamp(javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar(date).toGregorianCalendar().getTimeInMillis());
情话墙 2024-11-15 17:19:37

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

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

Maybe not the most elegant way, but certainly working one I recently made:

Calendar cal = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss");
cal.setTime(sdf.parse(dateInString.replace("Z", "").replace("T", "-")));
_蜘蛛 2024-11-15 17:19:37

我正在使用这个:

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

I'm using this:

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

Example:

String dateTimeString = "2007-05-01T15:43:26.3452+07:00";
ZonedDateTime zonedDateTime = ZonedDateTime.from(RFC_3339_DATE_TIME_FORMATTER.parse(dateTimeString));
屋檐 2024-11-15 17:19:37

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

现在在您的 Activity/fuction/dataSourse Mapper 中使用此方法来获取字符串格式的日期,如下所示

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

,输出将如下所示

18-01-2022

Though, The question is very old, but it may help one who wants it Kotlin version of this answer. By using this file, anyone can convert a Rfc3339 date to any date-format. Here I take a empty file name DateUtil and create a function called getDateString() which has 3 arguments.

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

And now use this method in your activity/fuction/dataSourse Mapper to get Date in String format like this

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

and output will be like this

18-01-2022
淡紫姑娘! 2024-11-15 17:19:37

为了将来参考,作为替代方案,您可以使用 ITU[1],它是手写的,可以精确处理 RFC-3339 解析,并且还可以让您轻松处理闰秒。该库没有依赖关系,大小仅为 18 kB。

完全披露:我是作者

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

For future reference, as an alternative, you could use ITU[1] which is hand-written to deal with exactly RFC-3339 parsing and also lets you easily deal with leap seconds. The library is dependency-free and only weighs in at 18 kB.

Full disclosure: I'm the author

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

月下伊人醉 2024-11-15 17:19:37
Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(datetimeInFRC3339format)
Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(datetimeInFRC3339format)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文