将符合ISO 8601的字符串转换为java.util.日期

我试图将ISO8601格式的字符串转换为java.util.Date

如果与Locale(比较示例)一起使用,我发现模式yyyy-MM-dd'T'HH:mm:ssZ符合ISO8601。

但是,使用java.text.SimpleDateFormat,我无法转换格式正确的String2010-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" ) ;

对我来说,这很好。

810370 次浏览

不幸的是,可用于SimpleDateFormat简单日期格式(Java6及更早版本)的时区格式不符合ISO8601。SimpleDateFormat理解时区字符串,如“GMT+01:00”或“+0100”,后者根据rfc#822

即使Java7根据ISO 8601添加了对时区描述符的支持,SimpleDateFormat仍然无法正确解析完整的日期字符串,因为它不支持可选部分。

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

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

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

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

DatatypeConver解决方案并不适用于所有VM。以下方法适用于我:

javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar("2011-01-01Z").toGregorianCalendar().getTime()

我发现joda不能开箱即用(特别是对于我上面给出的日期时区的例子,这应该是有效的)

好的,这个问题已经回答了,但我还是放弃我的答案。它可能会帮助某人。

我一直在寻找安卓解决方案(API 7)。

  • Joda是不可能的-它是巨大的,并且受到初始化缓慢的影响。对于这个特定的目的来说,它似乎也是一个主要的过度杀戮。
  • 涉及javax.xml的答案在Android API 7上不起作用。

最终实现了这个简单的类。它涵盖了ISO 8601字符串中的只有最常见的形式,但在某些情况下(当您非常确定输入将采用这个格式时),这应该足够了。

import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Calendar;import java.util.Date;import java.util.GregorianCalendar;
/*** Helper class for handling a most common subset of ISO 8601 strings* (in the following format: "2008-03-01T13:00:00+01:00"). It supports* parsing the "Z" timezone, but many other less-used features are* missing.*/public final class ISO8601 {/** Transform Calendar to ISO 8601 string. */public static String fromCalendar(final Calendar calendar) {Date date = calendar.getTime();String formatted = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").format(date);return formatted.substring(0, 22) + ":" + formatted.substring(22);}
/** Get current date and time formatted as ISO 8601 string. */public static String now() {return fromCalendar(GregorianCalendar.getInstance());}
/** Transform ISO 8601 string to Calendar. */public static Calendar toCalendar(final String iso8601string)throws ParseException {Calendar calendar = GregorianCalendar.getInstance();String s = iso8601string.replace("Z", "+00:00");try {s = s.substring(0, 22) + s.substring(23);  // to get rid of the ":"} catch (IndexOutOfBoundsException e) {throw new ParseException("Invalid length", 0);}Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(s);calendar.setTime(date);return calendar;}}

业绩说明:我每次都实例化新的SimpleDateFormat,以避免在Android 2.1中出现一个bug。如果你和我一样惊讶,请参阅这个谜语。对于其他Java引擎,您可以将实例缓存在私有静态字段中(使用ThreadLocal,以确保线程安全)。

我觉得我们应该用

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

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

基本功能礼貌:@wrygiel。

此函数可以将ISO8601格式转换为可以处理偏移值的Java日期。根据ISO 8601的定义,偏移可以以不同的格式提及。

±[hh]:[mm]±[hh][mm]±[hh]
Eg:  "18:30Z", "22:30+04", "1130-0700", and "15:00-03:30" all mean the same time. - 06:30PM UTC

这个类有静态方法来转换

  • ISO8601字符串到日期(本地时区)对象
  • 日期到ISO8601字符串
  • 夏令时自动计算

ISO8601字符串示例

/*       "2013-06-25T14:00:00Z";"2013-06-25T140000Z";"2013-06-25T14:00:00+04";"2013-06-25T14:00:00+0400";"2013-06-25T140000+0400";"2013-06-25T14:00:00-04";"2013-06-25T14:00:00-0400";"2013-06-25T140000-0400";*/

public class ISO8601DateFormatter {
private static final DateFormat DATE_FORMAT_1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");private static final DateFormat DATE_FORMAT_2 = new SimpleDateFormat("yyyy-MM-dd'T'HHmmssZ");private static final String UTC_PLUS = "+";private static final String UTC_MINUS = "-";
public static Date toDate(String iso8601string) throws ParseException {iso8601string = iso8601string.trim();if(iso8601string.toUpperCase().indexOf("Z")>0){iso8601string = iso8601string.toUpperCase().replace("Z", "+0000");}else if(((iso8601string.indexOf(UTC_PLUS))>0)){iso8601string = replaceColon(iso8601string, iso8601string.indexOf(UTC_PLUS));iso8601string = appendZeros(iso8601string, iso8601string.indexOf(UTC_PLUS), UTC_PLUS);}else if(((iso8601string.indexOf(UTC_MINUS))>0)){iso8601string = replaceColon(iso8601string, iso8601string.indexOf(UTC_MINUS));iso8601string = appendZeros(iso8601string, iso8601string.indexOf(UTC_MINUS), UTC_MINUS);}
Date date = null;if(iso8601string.contains(":"))date = DATE_FORMAT_1.parse(iso8601string);else{date = DATE_FORMAT_2.parse(iso8601string);}return date;}
public static String toISO8601String(Date date){return DATE_FORMAT_1.format(date);}
private static String replaceColon(String sourceStr, int offsetIndex){if(sourceStr.substring(offsetIndex).contains(":"))return sourceStr.substring(0, offsetIndex) + sourceStr.substring(offsetIndex).replace(":", "");return sourceStr;}
private static String appendZeros(String sourceStr, int offsetIndex, String offsetChar){if((sourceStr.length()-1)-sourceStr.indexOf(offsetChar,offsetIndex)<=2)return sourceStr + "00";return sourceStr;}

}

杰克逊数据库也有ISO8601DateFormat类这样做(ISO8601Utils中的实际实现)。

ISO8601DateFormat df = new ISO8601DateFormat();Date d = df.parse("2010-07-28T22:25:51Z");

Java7留档的方式:

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示例节中找到更多示例。

UPD 02/13/2020:有一个全新的方式在Java8中这样做

解析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 {@Testpublic 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 1.7的SimpleDateFormat对ISO 8601格式有一个很酷的模式。

SimpleDateFormat类

以下是我所做的:

Date d = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ",Locale.ENGLISH).format(System.currentTimeMillis());

tl; dr

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

或者对于Java12+,使用作者:Arvind Kumar Avinash中的Instant.parse

使用java.time

Java8及以后的新java.time包的灵感来自Joda-Time。

OffsetDateTime类表示时间线上具有UTC偏移量但不是时区的时刻。

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

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

2010-01-01 T 12:00+01:00

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

Instant instant = odt.toInstant();

…或者…

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

如果需要,调整到一个时区。时区是一个地区UTC偏移量值的历史记录,有一组处理异常的规则,如夏令时(DST)。因此,尽可能应用时区而不仅仅是偏移量。

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

对于仅限日期的值,请使用LocalDate

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

或:

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

关于java.time

java.time框架内置于Java8及更高版本中。这些类取代了麻烦的旧遗产日期时间类,例如java.util.DateCalendarSimpleDateFormat

要了解更多信息,请参阅Oracle教程。并搜索Stack Overflow以获取许多示例和解释。规格是JSR 310

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

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

在哪里可以获得java.time?


我认为很多人想要做的是解析JSON日期字符串。如果您来到此页面,您可能希望将JavaScript JSON日期转换为Java日期。

要显示JSON日期字符串的样子:

    var d=new Date();var s = JSON.stringify(d);
document.write(s);document.write("<br />"+d);

"2013-12-14T01:55:33.412Z"Fri Dec 13 2013 17:55:33 GMT-0800 (PST)

JSON日期字符串为2013-12-14T01:55:33.412Z。

JSON规范没有涵盖日期,但上面是一个非常具体的ISO 8601格式,而ISO_8601要大得多,这只是一个子集,尽管是一个非常重要的子集。

请参见http://www.json.orghttp://en.wikipedia.org/wiki/ISO_8601http://www.w3.org/TR/NOTE-datetime

碰巧我写了一个JSON解析器和一个PLIST解析器,它们都使用ISO-8601,但不是相同的位。

/*var d=new Date();var s = JSON.stringify(d);
document.write(s);document.write("<br />"+d);

"2013-12-14T01:55:33.412Z"Fri Dec 13 2013 17:55:33 GMT-0800 (PST)

*/@Testpublic void jsonJavaScriptDate() {String test =  "2013-12-14T01:55:33.412Z";
Date date = Dates.fromJsonDate ( test );Date date2 = Dates.fromJsonDate_ ( test );
assertEquals(date2.toString (), "" + date);
puts (date);}

我为我的项目写了两种方法。一种是标准的,一种是快速的。

同样,JSON日期字符串是ISO 8601的一个非常具体的实现。

(我在另一个答案中发布了另一个答案,该答案应该适用于PLIST日期,这是不同的ISO 8601格式)。

JSON日期如下:

public static Date fromJsonDate_( String string ) {
try {
return new SimpleDateFormat ( "yyyy-MM-dd'T'HH:mm:ss.SSSXXX").parse ( string );} catch ( ParseException e ) {return Exceptions.handle (Date.class, "Not a valid JSON date", e);}

}

PLIST文件(ASCII非GNUNText)也使用ISO 8601,但没有毫秒,所以……并非所有ISO-8601日期都是相同的。(至少我还没有找到一个使用milis的解析器,我看到的解析器完全跳过了时区OMG)。

现在是快速版本(您可以在Boon中找到它)。

public static Date fromJsonDate( String string ) {
return fromJsonDate ( Reflection.toCharArray ( string ), 0, string.length () );
}

请注意,Reflection.toCharArray如果可用则使用不安全,如果不可用则默认string.toCharArray。

(您可以通过将Reflection.toCharArray(string)替换为string.toCharArray()来从示例中删除它。

public static Date fromJsonDate( char[] charArray, int from, int to ) {
if (isJsonDate ( charArray, from, to )) {int year = CharScanner.parseIntFromTo ( charArray, from + 0, from + 4 );int month = CharScanner.parseIntFromTo ( charArray,  from +5,  from +7 );int day = CharScanner.parseIntFromTo ( charArray,  from +8,  from +10 );int hour = CharScanner.parseIntFromTo ( charArray,  from +11,  from +13 );
int minute = CharScanner.parseIntFromTo ( charArray,  from +14,  from +16 );
int second = CharScanner.parseIntFromTo ( charArray,  from +17,  from +19 );
int miliseconds = CharScanner.parseIntFromTo ( charArray,  from +20,  from +23 );
TimeZone tz = TimeZone.getTimeZone ( "GMT" );

return toDate ( tz, year, month, day, hour, minute, second, miliseconds );
}   else {return null;}
}

isJsonDate的实现如下:

public static boolean isJsonDate( char[] charArray, int start, int to ) {boolean valid = true;final int length = to -start;
if (length != JSON_TIME_LENGTH) {return false;}
valid &=  (charArray [ start + 19 ]  == '.');
if (!valid) {return false;}

valid &=  (charArray[  start +4 ]  == '-') &&(charArray[  start +7 ]  == '-') &&(charArray[  start +10 ] == 'T') &&(charArray[  start +13 ] == ':') &&(charArray[  start +16 ] == ':');
return valid;}

不管怎样……我的猜测是,来这里的很多人……可能正在寻找JSON日期字符串,虽然它是一个ISO-8601日期,但它是一个非常具体的日期,需要非常具体的解析。

public static int parseIntFromTo ( char[] digitChars, int offset, int to ) {int num = digitChars[ offset ] - '0';if ( ++offset < to ) {num = ( num * 10 ) + ( digitChars[ offset ] - '0' );if ( ++offset < to ) {num = ( num * 10 ) + ( digitChars[ offset ] - '0' );if ( ++offset < to ) {num = ( num * 10 ) + ( digitChars[ offset ] - '0' );if ( ++offset < to ) {num = ( num * 10 ) + ( digitChars[ offset ] - '0' );if ( ++offset < to ) {num = ( num * 10 ) + ( digitChars[ offset ] - '0' );if ( ++offset < to ) {num = ( num * 10 ) + ( digitChars[ offset ] - '0' );if ( ++offset < to ) {num = ( num * 10 ) + ( digitChars[ offset ] - '0' );if ( ++offset < to ) {num = ( num * 10 ) + ( digitChars[ offset ] - '0' );}}}}}}}}return num;}

请参见https://github.com/RichardHightower/boonBoon有一个PLIST解析器(ASCII)和一个JSON解析器。

JSON解析器是我所知道的最快JavaJSON解析器。

由Gatling Performance人员独立验证。

Benchmark                               Mode Thr     Count  Sec         Mean   Mean error        UnitsBoonCharArrayBenchmark.roundRobin      thrpt  16        10    1   724815,875    54339,825    ops/sJacksonObjectBenchmark.roundRobin      thrpt  16        10    1   580014,875   145097,700    ops/sJsonSmartBytesBenchmark.roundRobin     thrpt  16        10    1   575548,435    64202,618    ops/sJsonSmartStringBenchmark.roundRobin    thrpt  16        10    1   541212,220    45144,815    ops/sGSONStringBenchmark.roundRobin         thrpt  16        10    1   522947,175    65572,427    ops/sBoonDirectBytesBenchmark.roundRobin    thrpt  16        10    1   521528,912    41366,197    ops/sJacksonASTBenchmark.roundRobin         thrpt  16        10    1   512564,205   300704,545    ops/sGSONReaderBenchmark.roundRobin         thrpt  16        10    1   446322,220    41327,496    ops/sJsonSmartStreamBenchmark.roundRobin    thrpt  16        10    1   276399,298   130055,340    ops/sJsonSmartReaderBenchmark.roundRobin    thrpt  16        10    1    86789,825    17690,031    ops/s

它具有最快的JSON解析器,适用于流、阅读器、bytes[]、char[]、CharSequence(StringBuilder、字符缓冲区)和String。

查看更多基准:

Java版本7

您可以关注Oracle留档:http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html

X-用于ISO 8601时区

TimeZone tz = TimeZone.getTimeZone("UTC");DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");df.setTimeZone(tz);String nowAsISO = df.format(new Date());
System.out.println(nowAsISO);
DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");//nowAsISO = "2013-05-31T00:00:00Z";Date finalResult = df1.parse(nowAsISO);
System.out.println(finalResult);

java.time

请注意,在Java8中,您可以使用java.time.ZonedDateTime类及其静态parse(CharSequence text)方法。

java.time

java.timeAPI(内置于Java8及更高版本)使这变得更容易。

如果您知道输入在UTC中,例如末尾的Z(用于Zulu),则Instant类可以解析。

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

如果您的输入可能是另一个UTC偏移量值,而不是末尾Z(Zulu)指示的UTC,请使用OffsetDateTime类进行解析。

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

然后提取Instant,并通过调用from转换为java.util.Date

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

您还可以使用以下类-

org.springframework.extensions.surf.util.ISO8601DateFormat

Date date = ISO8601DateFormat.parse("date in iso8601");

链接到Java文档-包org.springframework.extensions.surf.maven.plugin.util的层次结构

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

我遇到了相同问题,并通过以下代码解决了它。

 public static Calendar getCalendarFromISO(String datestring) {Calendar calendar = Calendar.getInstance(TimeZone.getDefault(), Locale.getDefault()) ;SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());try {Date date = dateformat.parse(datestring);date.setHours(date.getHours() - 1);calendar.setTime(date);
String test = dateformat.format(calendar.getTime());Log.e("TEST_TIME", test);
} catch (ParseException e) {e.printStackTrace();}
return calendar;}

之前我用SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.getDefault());

但是后来我发现异常的主要原因是yyyy-MM-dd'T'HH:mm:ss.SSSZ

所以我用

SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());

这对我来说工作得很好。

我也有类似的需求:我需要能够解析任何符合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");

希望有帮助。

为了格式化这样的日期,以下内容在基于Java6的应用程序中为我工作。在thymeleaf项目中有一个DateFormatJacksonThymeleafISO8601DateFormat,它插入了缺失的冒号:

我用它来兼容ECMAScript日期格式。

像这样做:

public static void main(String[] args) throws ParseException {
String dateStr = "2016-10-19T14:15:36+08:00";Date date = javax.xml.bind.DatatypeConverter.parseDateTime(dateStr).getTime();
System.out.println(date);
}

以下是输出:

Wed Oct 19 15:15:36 CST 2016

Java7+的解决方法是使用SimpleDateFormat:
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.US);

此代码可以解析ISO8601格式,例如:

  • 2017-05-17T06:01:43.785Z
  • 2017-05-13T02:58:21.391+01:00

但是在Java6上,SimpleDateFormat不理解X字符,会抛出
IllegalArgumentException: Unknown pattern character 'X'
我们需要使用SimpleDateFormat将ISO8601日期规范化为Java6中可读的格式。

public static Date iso8601Format(String formattedDate) throws ParseException {try {DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.US);return df.parse(formattedDate);} catch (IllegalArgumentException ex) {// error happen in Java 6: Unknown pattern character 'X'if (formattedDate.endsWith("Z")) formattedDate = formattedDate.replace("Z", "+0000");else formattedDate = formattedDate.replaceAll("([+-]\\d\\d):(\\d\\d)\\s*$", "$1$2");DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US);return df1.parse(formattedDate);}}

当Java6中出现错误时,上面的方法将[Z替换为+0000]或[+01:00替换为+0100](您可以检测Java版本并将try/cat替换为if语句)。

使用字符串LocalDate.parse(((String) data.get("d_iso8601")),DateTimeFormatter.ISO_DATE)

Java有十几种不同的方法来解析日期时间,正如这里的优秀答案所展示的那样。但有些令人惊讶的是,没有Java的时间类完全实现ISO 8601!

Java8,我建议:

ZonedDateTime zp = ZonedDateTime.parse(string);Date date = Date.from(zp.toInstant());

这将处理UTC和偏移量的示例,例如“2017-09-13T10:36:40Z”或“2017-09-13T10:36:40+01:00”。它适用于大多数用例。

但它不会处理“2017-09-13T10:36:40+01”等示例,其中是有效的ISO 8601日期时间。
它也不会只处理日期,例如“2017-09-13”。

如果您必须处理这些,我建议首先使用regex来嗅探语法。

这里有一个很好的ISO 8601示例列表,其中有很多角落案例:https://www.myintervals.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/我不知道有任何Java类可以处理所有这些。

我很惊讶,甚至没有一个java库支持https://en.wikipedia.org/wiki/ISO_8601中的所有ISO 8601日期格式。Joda DateTime支持其中的大多数,但不是全部,因此我添加了自定义逻辑来处理所有这些。这是我的实现。

import java.text.ParseException;import java.util.Date;
import org.apache.commons.lang3.time.DateUtils;import org.joda.time.DateTime;
public class ISO8601DateUtils {	
/*** It parses all the date time formats from https://en.wikipedia.org/wiki/ISO_8601 and returns Joda DateTime.* Zoda DateTime does not support dates of format 20190531T160233Z, and hence added custom logic to handle this using SimpleDateFormat.* @param dateTimeString ISO 8601 date time string* @return*/public static DateTime parse(String dateTimeString) {try {return new DateTime( dateTimeString );} catch(Exception e) {try {Date dateTime = DateUtils.parseDate(dateTimeString, JODA_NOT_SUPPORTED_ISO_DATES);return new DateTime(dateTime.getTime());} catch (ParseException e1) {throw new RuntimeException(String.format("Date %s could not be parsed to ISO date", dateTimeString));}}}  
private static String[] JODA_NOT_SUPPORTED_ISO_DATES = new String[] {// upto millis"yyyyMMdd'T'HHmmssSSS'Z'","yyyyMMdd'T'HHmmssSSSZ","yyyyMMdd'T'HHmmssSSSXXX",			
"yyyy-MM-dd'T'HHmmssSSS'Z'","yyyy-MM-dd'T'HHmmssSSSZ","yyyy-MM-dd'T'HHmmssSSSXXX",			
// upto seconds"yyyyMMdd'T'HHmmss'Z'","yyyyMMdd'T'HHmmssZ","yyyyMMdd'T'HHmmssXXX",			
"yyyy-MM-dd'T'HHmmss'Z'","yyyy-MM-dd'T'HHmmssZ","yyyy-MM-dd'T'HHmmssXXX",			
// upto minutes"yyyyMMdd'T'HHmm'Z'","yyyyMMdd'T'HHmmZ","yyyyMMdd'T'HHmmXXX",
"yyyy-MM-dd'T'HHmm'Z'","yyyy-MM-dd'T'HHmmZ","yyyy-MM-dd'T'HHmmXXX",			
//upto hours is already supported by Joda DateTime};}

一个小测试,显示如何在ISO8601中解析日期,并且LocalDateTime不处理DST。

 @Testpublic void shouldHandleDaylightSavingTimes() throws ParseException {
//ISO8601 UTC date formatSimpleDateFormat utcFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
// 1 hour of difference between 2 dates in UTC happening at the Daylight Saving TimeDate d1 = utcFormat.parse("2019-10-27T00:30:00.000Z");Date d2 = utcFormat.parse("2019-10-27T01:30:00.000Z");
//Date 2 is before date 2Assert.assertTrue(d1.getTime() < d2.getTime());// And there is 1 hour difference between the 2 datesAssert.assertEquals(1000*60*60, d2.getTime() - d1.getTime());
//Print the dates in local timeSimpleDateFormat localFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm z Z", Locale.forLanguageTag("fr_CH"));localFormat.setTimeZone(TimeZone.getTimeZone("Europe/Zurich"));
//Both dates are at 02h30 local time (because of DST), but one is CEST +0200 and the other CET +0100 (clock goes backwards)Assert.assertEquals("2019-10-27 02:30 CEST +0200", localFormat.format(d1));Assert.assertEquals("2019-10-27 02:30 CET +0100", localFormat.format(d2));
//Small test that shows that LocalDateTime does not handle DST (and should not be used for storing timeseries data)LocalDateTime ld1 = LocalDateTime.ofInstant(d1.toInstant(), ZoneId.of("Europe/Zurich"));LocalDateTime ld2 = LocalDateTime.ofInstant(d2.toInstant(), ZoneId.of("Europe/Zurich"));
//Note that a localdatetime does not handle DST, therefore the 2 dates are the sameAssert.assertEquals(ld1, ld2);
//They both have the following local valuesAssert.assertEquals(2019, ld1.getYear());Assert.assertEquals(27, ld1.getDayOfMonth());Assert.assertEquals(10, ld1.getMonthValue());Assert.assertEquals(2, ld1.getHour());Assert.assertEquals(30, ld1.getMinute());Assert.assertEquals(0, ld1.getSecond());
}

从Java8开始,有一种全新的官方支持的方式可以做到这一点:

    String s = "2020-02-13T18:51:09.840Z";TemporalAccessor ta = DateTimeFormatter.ISO_INSTANT.parse(s);Instant i = Instant.from(ta);Date d = Date.from(i);

Java8+

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

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

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

在我搜索了很多将ISO8601转换为最新版本后,我突然发现了一个ISO8601Util.java的java类,这是com.google.gson.internal.bind.util的一部分。所以你可以用它来转换日期。

ISO8601Utils.parse("2010-01-01T12:00:00Z" , ParsePosition(0))

你可以简单地使用这个kotlin扩展函数

fun String.getDateFromString() : Date? = ISO8601Utils.parse(this ,ParsePosition(0))

当要从UTC转换为我们想要的格式时。它会根据我们停留的区域/位置而变化

//utcDate = "2021-06-05T02:46:29Z"fun converterUtcToReadableDateTime(utcDate: String): String {val offsetDateTime = OffsetDateTime.ofInstant(Instant.parse(utcDate), ZoneId.systemDefault())val patternDate = "dd MMM yyyy h:mm a"return DateTimeFormatter.ofPattern(patternDate).format(offsetDateTime)}
fun converterUtcToReadableDate(utcDate: String): String {val offsetDateTime = OffsetDateTime.ofInstant(Instant.parse(utcDate), ZoneId.systemDefault())val patternDate = "d MMM yyyy"return DateTimeFormatter.ofPattern(patternDate).format(offsetDateTime)}
fun converterUtcToReadableTime(utcDate: String): String {val offsetDateTime = OffsetDateTime.ofInstant(Instant.parse(utcDate), ZoneId.systemDefault())val patternDate = "h:mm a"return DateTimeFormatter.ofPattern(patternDate).format(offsetDateTime)}

我不能使用Java8个特性,所以只有java.util.Date可用。我已经依赖于gson库,但不想直接使用ISO8601UtilsISO8601Utils是内部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毫秒。

Java12

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

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

使用Instant#parse解析ISO8601格式的日期时间字符串,并使用Date#from将结果转换为java.util.Date。请参阅此代码跑Ideone.com

Demo

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

路径:日期时间了解有关现代日期时间API的更多信息。