我正在尝试将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" ) ;

对我来说,这很好。


当前回答

我有一个类似的需求:我需要能够解析任何符合ISO8601的日期,而不需要事先知道确切的格式,我想要一个轻量级的解决方案,也可以在Android上工作。

当我在谷歌上搜索我的需求时,我偶然发现了这个问题,并注意到AFAIU,没有一个答案完全符合我的需求。因此,我开发了jISO8601并将其推向maven central。

只需添加你的pom.xml:

<dependency>
  <groupId>fr.turri</groupId>
  <artifactId>jISO8601</artifactId>
  <version>0.2</version>
</dependency>

然后你就可以开始了:

import fr.turri.jiso8601.*;
...
Calendar cal = Iso8601Deserializer.toCalendar("1985-03-04");
Date date = Iso8601Deserializer.toDate("1985-03-04T12:34:56Z");

希望能有所帮助。

其他回答

我想我们应该用

DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")

日期:2010-01-01T12:00:00Z

另一种解析ISO8601时间戳的简单方法是使用org.apache.commons.lang.time.DateUtils:

import static org.junit.Assert.assertEquals;

import java.text.ParseException;
import java.util.Date;
import org.apache.commons.lang.time.DateUtils;
import org.junit.Test;

public class ISO8601TimestampFormatTest {
  @Test
  public void parse() throws ParseException {
    Date date = DateUtils.parseDate("2010-01-01T12:00:00+01:00", new String[]{ "yyyy-MM-dd'T'HH:mm:ssZZ" });
    assertEquals("Fri Jan 01 12:00:00 CET 2010", date.toString());
  }
}

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

博士tl;

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

或者对于Java 12+,使用Instant。解析见Arvind Kumar Avinash的答案。

使用java.time

新的java。Java 8中的time包,后来受到Joda-Time的启发。

OffsetDateTime类表示时间轴上的一个时刻,该时刻具有与utc的偏移量,而不是时区。

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

调用toString生成标准ISO 8601格式的字符串:

2010 - 01 - 01 - t12:00 + 01:00

要通过UTC的镜头看到相同的值,提取一个Instant或将偏移量从+01:00调整为00:00。

Instant instant = odt.toInstant();  

…or…

OffsetDateTime odtUtc = odt.withOffsetSameInstant( ZoneOffset.UTC );

根据需要调整时区。时区是一个区域的utc偏移值的历史记录,并带有一组处理异常(如夏令时)的规则。因此,尽可能应用时区,而不仅仅是偏移量。

ZonedDateTime zonedDateTimeMontréal = odt.atZoneSameInstant( ZoneId.of( "America/Montreal" ) );

对于只有日期的值,使用LocalDate。

LocalDate ld = LocalDate.of( 2010 , Month.JANUARY , 1 ) ;

Or:

LocalDate ld = LocalDate.parse( "2010-01-01" ) ;

关于java.time

java。时间框架内置于Java 8及更高版本中。这些类取代了麻烦的旧遗留日期-时间类,如java.util。日期,日历和简单日期格式。

要了解更多,请参阅Oracle教程。搜索Stack Overflow可以找到很多例子和解释。规范是JSR 310。

Joda-Time项目现在处于维护模式,建议迁移到java。时间类。

你可以交换java。Time对象直接使用数据库。使用符合JDBC 4.2或更高版本的JDBC驱动程序。不需要字符串,不需要java。sql。*类。Hibernate 5和JPA 2.2支持java.time。

从哪里获取java。时间类?

Java SE 8, Java SE 9, Java SE 10, Java SE 11, and later - Part of the standard Java API with a bundled implementation. Java 9 brought some minor features and fixes. Java SE 6 and Java SE 7 Most of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport. Android Later versions of Android (26+) bundle implementations of the java.time classes. For earlier Android (<26), the process of API desugaring brings a subset of the java.time functionality not originally built into Android. If the desugaring does not offer what you need, the ThreeTenABP project adapts ThreeTen-Backport (mentioned above) to Android. See How to use ThreeTenABP….


不幸的是,SimpleDateFormat (Java 6及更早版本)可用的时区格式不符合ISO 8601标准。SimpleDateFormat理解像“GMT+01:00”或“+0100”这样的时区字符串,后者根据rfc# 822。

即使Java 7根据ISO 8601增加了对时区描述符的支持,SimpleDateFormat仍然不能正确地解析一个完整的日期字符串,因为它不支持可选部分。

使用regexp重新格式化输入字符串当然是一种可能,但替换规则不像你的问题那么简单:

有些时区不是UTC的完整小时,因此字符串不一定以“:00”结尾。 ISO8601只允许在时区中包含小时数,因此“+01”相当于“+01:00”。 ISO8601允许使用“Z”来表示UTC而不是“+00:00”。

更简单的解决方案可能是使用JAXB中的数据类型转换器,因为JAXB必须能够根据XML Schema规范解析ISO8601日期字符串。javax.xml.bind.DatatypeConverter.parseDateTime("2010-01-01T12:00:00Z")会给你一个Calendar对象,如果你需要一个Date对象,你可以简单地在它上面使用getTime()。

你可能也可以使用Joda-Time,但我不知道你为什么要费心(更新2022;可能是因为Android的javax.xml包中缺少整个javax.xml.bind部分)。