我正在尝试将ISO 8601格式的字符串转换为java.util.Date。

我发现模式yyyy-MM-dd'T'HH:mm:ssZ是符合iso8601的,如果使用区域设置(比较样本)。

然而,使用java.text。SimpleDateFormat,我无法转换正确格式化的字符串2010-01-01T12:00:00+01:00。我必须首先将其转换为2010-01-01T12:00:00+0100,不带冒号。

目前的解决方案是

SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.GERMANY);
String date = "2010-01-01T12:00:00+01:00".replaceAll("\\+0([0-9]){1}\\:00", "+0$100");
System.out.println(ISO8601DATEFORMAT.parse(date));

这显然不太好。是我错过了什么,还是有更好的解决方案?


回答

感谢JuanZe的评论,我发现了Joda-Time魔法,这里也有描述。

所以解是

DateTimeFormatter parser2 = ISODateTimeFormat.dateTimeNoMillis();
String jtdate = "2010-01-01T12:00:00+01:00";
System.out.println(parser2.parseDateTime(jtdate));

或者更简单地说,通过构造函数使用默认解析器:

DateTime dt = new DateTime( "2010-01-01T12:00:00+01:00" ) ;

对我来说,这很好。


当前回答

java.time

java。time API(内置于Java 8及更高版本中),使此操作更容易一些。

如果知道输入是UTC,比如末尾的Z(代表Zulu), Instant类就可以解析。

java.util.Date date = Date.from( Instant.parse( "2014-12-12T10:39:40Z" ));

如果您的输入可能是另一个offset-from-UTC值,而不是末尾的Z (Zulu)所指示的UTC,则使用OffsetDateTime类进行解析。

OffsetDateTime odt = OffsetDateTime.parse( "2010-01-01T12:00:00+01:00" );

然后提取一个Instant,并通过调用。

Instant instant = odt.toInstant();  // Instant is always in UTC.
java.util.Date date = java.util.Date.from( instant );

其他回答

正如其他人所提到的,Android并没有一个很好的方法来支持使用SDK中包含的类来解析/格式化ISO 8601日期。我已经多次编写了这段代码,所以我最终创建了一个Gist,其中包括一个DateUtils类,支持格式化和解析ISO 8601和RFC 1123日期。Gist还包括一个测试用例,显示它支持什么。

https://gist.github.com/mraccola/702330625fad8eebe7d3

Java 12

从Java 12开始,Instant#parse可以解析包含时区偏移的日期-时间字符串。

Date dt = Date.from(Instant.parse(your-date-time-string));

使用Instant# Parse解析ISO 8601格式的日期-时间字符串,并使用dat# from将结果转换为java.util.Date。在Ideone.com上查看运行的代码。

演示:

import java.time.Instant;
import java.util.Date;
import java.util.stream.Stream;

public class Main {
  public static void main(String[] args) {
    Stream.of(
        "2010-01-01T12:00:00+01:00",
        "2010-01-01T12:00:00-01:00",
        "2010-01-01T12:00:00Z"
      )
      .map(Instant::parse)
      .map(Date::from)
      .forEach(System.out::println);
  }
}

在Ideone.com上查看运行的代码。

从Trail: Date Time了解更多关于现代Date-Time API的信息。

Java 8 +

简单的一句话,我没有在答案中找到:

Date date = Date.from(ZonedDateTime.parse("2010-01-01T12:00:00+01:00").toInstant());

Date不包含时区,它将以UTC存储,但即使在使用System.out.println(Date)进行简单输出时,也会正确地转换为您的JVM时区。

我不能使用Java 8的特性,所以只有Java .util. date可用。我已经依赖于gson库,但不想直接使用ISO8601Utils。ISO8601Utils是一个内部API, gson的作者警告不要使用它。

我用gson的公共API解析了一个ISO8601日期:

fun parseISO8601DateToLocalTimeOrNull(date: String): Date? {
    return try {
        GsonBuilder()
            .create()
            .getAdapter(Date::class.java)
            .fromJson("\"$date\"")
     } catch (t: Throwable) {
        null
     }
}

实际上,适配器仍然使用ISO8601Utils。但是如果您正在使用适配器,您可以确保不同的兼容版本的gson不会破坏您的项目。

我担心适配器的创建可能会很慢,所以我用debuggable=false在Pixel 3a上测量执行时间。parseISO8601DateToLocalTimeOrNull需要大约0.5毫秒来解析一个日期。

Java 7文档所推崇的方式是:

DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
String string1 = "2001-07-04T12:08:56.235-0700";
Date result1 = df1.parse(string1);

DateFormat df2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
String string2 = "2001-07-04T12:08:56.235-07:00";
Date result2 = df2.parse(string2);

你可以在SimpleDateFormat javadoc的examples部分找到更多的例子。

UPD 02/13/2020:在Java 8中有一种全新的方式来做到这一点