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


667

我正在尝试将ISO 8601格式的String转换为java.util.Date

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

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

对我来说,这很好。


243
准备接收很多“使用JodaTime”答案...
JuanZe 2010年

3
@ Ice09:如果DateTimeFormat的API文档正确(尽管JoDa文档可能会引起误解,错误或不完整),则您在自己的“答案”中使用的模式与ISO8601不兼容。
jarnbjo 2010年

21
我不确定何时添加它,但是“ X”似乎可以解决SimpleDateFormat中的此问题。模式“ yyyy-MM-dd'T'HH:mm:ssX”成功解析了问题中的示例。
mlohbihler

12
在“X”是可用的,因为Java的7
拉斯Grammel

3
Java 8使其变得简单!亚当在下面的答案中有一颗隐藏的宝石:stackoverflow.com/a/27479533/1262901
Fabian Keller

Answers:


477

不幸的是,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,但我不知道您为什么要为此烦恼。


18
JAXB解决方案是一种非常有创意的方法!它也可以正常工作,我已经用样本对其进行了测试。但是,对于遇到问题并被允许使用JodaTime的人,我建议您使用它,因为它感觉更自然。但是您的解决方案不需要其他库(至少在Java 6中)。
Ice09 2010年

36
相反:日历c = GregorianCalendar.getInstance(); c.setTime(aDate);返回javax.xml.bind.DatatypeConverter.printDateTime(c);
Alexander Ljungberg

4
其实并不是那么简单的b / c,您必须初始化jaxb datatypeConverter。我最终自己使用了DatatypeFactory,就像DataTypeConverterImpl在内部所做的那样。真是头疼
gtrak 2010年

3
@Simon:不,时区当然不会被忽略。你一定做错了。如果您输入的字符不止几个,并告诉我们您实际在做什么,有人可能会向您解释。
jarnbjo 2012年

4
@jarnbjo,您是我遇到的第一个也是唯一的一个人,他们更喜欢标准的1.8之前的Java日期类而不是joda-time。我发现joda-time确实可以使用,特别是与可憎的标准api相比时。
NimChimpsky 2014年

245

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中

UPD 02/13/2020: Java 8中有一种全新的方法来实现此目的


7
您的回答帮助我将MongoDB的ISODate转换为本地日期。问候。
蓝天

9
@ b.long Java为此类符合ISO 8601格式的文件添加了更多的常量。Java为日期时间工作提供了一个全新的框架,其中包括对此类格式的内置默认支持。请参阅受Joda-Time启发的Java 8中的新java.time框架,该框架取代了麻烦的java.util.Date,.Calendar和SimpleDateFormat类。
罗勒·布尔克

2
这是否意味着您需要提前知道日期格式?如果你要接受什么样的string1string2,但不知道你会得到。
Timmmm 2015年

16
“ Z”必须用引号引起来
kervin

7
@kervin如果Z用引号引起来,那么格式化程序是否会专门寻找字符Z,而不是它可以代表的所有偏移量字符串?如果您的日期字符串碰巧是UTC,则引用Z似乎只能巧合。
spaaarky21 '16

201

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

我一直在寻找Android(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中的错误。如果您像我一样惊讶,请参阅这个谜语。对于其他Java引擎,您可以将实例缓存在私有静态字段中(使用ThreadLocal,以确保线程安全)。


2
也许应该把这个问题变成一个自己的问题,并给出自己的答案?
Thorbear 2012年

5
这是我在寻找答案时浏览的第一页,因此看起来很合适。对于大多数Java开发人员来说,Android并非完全是Java。但是,在大多数情况下,一个的工作原理与其他的相同,因此许多Android开发人员在寻找“ java”时都会进行搜索。
wrygiel 2012年

1
请注意,这并不代表毫秒的分辨率。这很容易添加。
Sky Kelsey

6
我必须添加.SSS几分之一秒,但效果很好。您为什么这样做s = s.substring(0, 22) + s.substring(23);-我看不出要点
Dori 2013年

1
输入= input.replaceAll(“ [Zz]”,“ +0000”); 也会工作,可以避免子字符串操作。
Javanator

115

java.time

java.time API(Java中内嵌的8和更高版本),使这是一个更容易一些。

如果您知道输入采用UTC格式,例如最后的Z(对于Zulu),则Instant该类可以解析。

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

如果您的输入可能是另一个从UTC偏移的值,而不是最后由(Zulu)指示的UTCZ,请使用OffsetDateTime该类进行解析。

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

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

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

8
这个答案太辛苦了。根据定义,java.util.Date没有时区。因此,无需所有与时区相关的代码:LocalDateTimeZoneIdatZone。这个简单的单行代码会执行以下操作:java.util.Date date = Date.from( ZonedDateTime.parse( "2014-12-12T10:39:40Z" ).toInstant() );
Basil Bourque 2014年

5
@BasilBourque这不必要地复杂:Date.from(Instant.parse("2014-12-12T10:39:40Z" ));足够了。
assylias 2015年

3
@assylias,您是正确的,但仅在日期字符串为UTC时才有效,ISO8601允许任何时区...
亚当

2
@Adam我的错-我还没有意识到这个问题比您的例子更笼统。作为附带OffsetDateTime说明,足以解析ISO8601(不包含时区信息,而仅包含偏移量)。
assylias 2015年

1
@assylias感谢您对Instant进行解析的评论。虽然不足以解决这个特定问题,但值得指出一个重要区别。因此,我添加了第二个代码示例。糟糕,刚注意到这并不是我的答案;我希望亚当批准。
罗勒·布尔克

67

杰克逊-数据绑定库也有ISO8601DateFormat类,做的是(在实际执行ISO8601Utils

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

无法解析此日期:2015-08-11T13:10:00。我懂了String index out of range: 19。查看代码,似乎需要指定毫秒数和时区。这些应该是可选的。
2015年

2
要引用文档,解析格式为:[yyyy-MM-dd|yyyyMMdd][T(hh:mm[:ss[.sss]]|hhmm[ss[.sss]])]?[Z|[+-]hh:mm]]。换句话说,毫秒是可选的,但时区是必需的。
david_p 2015年

2
嗯,是的,实际上看起来您是对的。不过,我很确定ISO8601允许您省略时区,所以仍然是错误的。不过,JodaTime的作品是:new DateTime("2015-08-11T13:10:00").toDate()
Timmmm

3
该类现已弃用,新类为StdDateFormat。否则,它的工作原理相同。
JohnEye

51

tl; dr

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

使用java.time

Java 8及更高版本中的新java.time包受到Joda-Time的启发。

OffsetDateTime级代表以时间轴上的时刻偏移从-UTC而不是一个时区。

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

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

2010-01-01T12: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" ) );

关于java.time

java.time框架是建立在Java 8和更高版本。这些类取代麻烦的老传统日期时间类,如java.util.DateCalendar,和SimpleDateFormat

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

要了解更多信息,请参见Oracle教程。并在Stack Overflow中搜索许多示例和说明。规格为JSR 310

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

在哪里获取java.time类?

ThreeTen-额外项目与其他类扩展java.time。该项目为将来可能在java.time中添加内容提供了一个试验场。你可能在这里找到一些有用的类,比如IntervalYearWeekYearQuarter,和更多



27

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

这意味着需要时区。根据ISO 8601,它是可选的。由于是秒,等于是这只解析ISO 8601的特定子集
Timmmm

1
可与Java 1.8
完美

20

DatatypeConverter解决方案不适用于所有VM。以下对我有用:

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

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


15

我认为我们应该使用

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

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


5
为什么这是一个比其他方法更好的答案,其中包括带有76个投票的公认答案?
埃里克·罗伯逊

3
@ErickRobertson:这很简单,开箱即用,灵活,无需转换,大多数人并不关心时区。
TWiStErRob

7
如果您不在乎时区,那么与时间打交道就没有多大意义了!
Dori 2013年

16
这完全忽略了时区。在意识到这一点之前一直在使用它,所以我切换到JodaTime。
约书亚·品特

3
丢掉时区只会在某些时候导致错误。
Bart van Kuik 2014年

11

从Java 8开始,有一种全新的,官方支持的方法:

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

2
如果字符串是即时格式,并且尾随Z偏移量,则无需显式指定。只是Instant i = Instant.parse(s);。问题中的字符串has +01:00,在这种情况下DateTimeFormatter.ISO_INSTANT不起作用(至少在我的Java 11上不起作用)。
Ole VV

2
@ OleV.V。你可以用ISO_OFFSET_DATE_TIME与偏移格式化日期,如+01:00docs.oracle.com/javase/8/docs/api/java/time/format/...
卢卡斯Basquerotto

1
没错,@ LucasBasquerotto。尽管没有明确提及格式化程序,但亚当罗勒·布尔克的答案已经做了类似的事情。
Ole VV

10

解析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());
  }
}

6

java.time

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


“问题”中的输入字符串仅具有与UTC的偏移量,而没有完整的时区。所以InstantZonedDateTime适当这里,不是ZonedDateTime
罗勒·布尔克

6

Java 7+的解决方法是使用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'
我们需要使用来将ISO8601日期标准化为Java 6中可读的格式SimpleDateFormat

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

上面的方法来代替[ Z+0000]或[ +01:00使用+0100]当Java 6中发生错误(你可以检测Java版本和替换的try / catch与if语句)。


不,麻烦的旧日期时间类,例如DateSimpleDateFormat设计不良,令人困惑和有缺陷。它们现在已成为旧版,被内置在Java 8及更高版本中的java.time类所取代。对于Java 6和Java 7,在ThreeTen-Backport项目中向后移植了许多java.time功能。将该库添加到您的应用程序比使用那些旧类更好。java.time中的单行解决方案:OffsetDateTime.parse( "2010-01-01T12:00:00+01:00" )
Basil Bourque

5

我遇到了同样的问题,并通过以下代码解决了该问题。

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

对我来说很好。


无需使用joda-time,XML api或其他任何工具即可满足我的需求。只是正确的模式。
Philippe Gioseffi'3


4

Java有许多解析日期时间的方法,如此处的出色答案所示。但是有些令人惊讶的是,Java的时间类都没有完全实现ISO 8601!

对于Java 8,我建议:

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”。

如果必须处理这些问题,建议您先使用正则表达式来嗅探语法。

有ISO的一个不错的名单这里8601分的例子有很多的极端情况:https://www.myintervals.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/我不知道任何可以解决所有问题的Java类。


OffsetDateTime会做,并且在概念上将日期时间与偏移量更好地匹配。
Ole VV

嘿@ OleV.V。谢谢你的建议。遗憾的是,否:OffsetDateTime.parse()将引发多个有效ISO 8601字符串的异常,例如“ 2017-09-13T10:36:40 + 01”或“ 2017-09-13”
Daniel Winterstein

我只想说这OffsetDateTime处理您处理的示例ZonedDateTime。我相信它不会处理任何没有的示例ZonedDateTime。从这个意义上讲,这没有改善(但也没有更糟)。抱歉,我不太清楚。
Ole VV

1
考虑到目前的状况,这应该是2020年可接受的答案。
slashCoder


3

就像其他人提到的那样,Android没有一个很好的方法来支持使用SDK中包含的类来解析/格式化ISO 8601日期。我已经多次编写了此代码,因此最终创建了一个包含DateUtils类的Gist,该类支持格式化和解析ISO 8601和RFC 1123日期。要点还包括一个测试案例,说明了其支持的内容。

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


2

JAVA 1.7的SimpleDateFormat对ISO 8601格式具有很酷的模式。

类SimpleDateFormat

这是我所做的:

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

2
Z格式字符串中的字符不是ISO 8601时区,如果要使用ISO 8601时区,则应使用X(或XXXXX
Vojta

d是String类型
添儿童

1

像这样做:

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

}

这是输出:

2016年10月19日星期三15:15:36 CST


1

使用像 LocalDate.parse(((String) data.get("d_iso8601")),DateTimeFormatter.ISO_DATE)


1

令我惊讶的是,甚至没有一个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
	};
}


1

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

 @Test
    public void shouldHandleDaylightSavingTimes() throws ParseException {

        //ISO8601 UTC date format
        SimpleDateFormat 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 Time
        Date d1 = utcFormat.parse("2019-10-27T00:30:00.000Z");
        Date d2 = utcFormat.parse("2019-10-27T01:30:00.000Z");

        //Date 2 is before date 2
        Assert.assertTrue(d1.getTime() < d2.getTime());
        // And there is 1 hour difference between the 2 dates
        Assert.assertEquals(1000*60*60, d2.getTime() - d1.getTime());

        //Print the dates in local time
        SimpleDateFormat 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 same
        Assert.assertEquals(ld1, ld2);

        //They both have the following local values
        Assert.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());

    }

3
仅供参考,非常麻烦的日期,时间类,如java.util.Datejava.util.Calendarjava.text.SimpleDateFormat现在的遗产,由取代java.time内置到Java 8和更高等级。请参见Oracle 教程
罗勒·布尔克

LocalDateTime完全不处理夏令时(DST)是正确的,因为它根本不处理时区。为此,我们需要ZonedDateTime。提议DateSimpleDateFormat是—恕我直言,不好。
Ole VV

1
确实ZonedDateTime有效。而且java.time.Instant也是处理DST的不错选择。我知道java.util.Date已过时,不应使用,但我只是在回答原始问题:如何将8601中的字符串转换为java.util.date
...。– ddtxra

0

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

当我查询我的需求时,我偶然发现了这个问题,并注意到AFAIU,没有任何答案完全符合我的需求。所以我开发了jISO8601并将其推向Maven。

只需添加您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");

希望对您有所帮助。



-1

基本功能礼貌:@wrygiel。

此函数可以将ISO8601格式转换为可以处理偏移值的Java Date。根据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字符串转换为Date(Local TimeZone)对象
  • 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;
}

}


2
注意-DateFormat和派生类不兼容多线程!使用静态的SimpleDateFormat对象(例如DATE_FORMAT_1和DATE_FORMAT_2)意味着调用ISO8601DateFormatter函数的多个线程将共享同一DateFormat对象。这会导致数据损坏,并从DateFormat调用返回错误的日期。要解决此问题,您只需使模式字符串常量,并在需要时创建本地SimpleDateFormat变量。这将确保每个对象仅被一个线程使用。
Theo 2014年

更好的线程安全解决方案是改为使用为线程安全而构建的日期时间库。在Java中,世界可能是Joda-Time或java.time。
罗勒·布尔克

-1

这似乎最适合我:

public static Date fromISO8601_( String string ) {

    try {
            return new SimpleDateFormat ( "yyyy-MM-dd'T'HH:mm:ssXXX").parse ( string );
    } catch ( ParseException e ) {
        return Exceptions.handle (Date.class, "Not a valid ISO8601", e);
    }


}

我需要将JavaScript日期字符串来回转换为Java。我发现以上工作与建议。有一些使用SimpleDateFormat的示例很接近,但似乎不是建议的子集:

http://www.w3.org/TR/NOTE-datetime

并受PLIST和JavaScript字符串支持,而这正是我所需要的。

这似乎是那里最常见的ISO8601字符串形式,也是很好的子集。

他们给出的示例是:

1994-11-05T08:15:30-05:00 corresponds 
November 5, 1994, 8:15:30 am, US Eastern Standard Time.

 1994-11-05T13:15:30Z corresponds to the same instant.

我也有一个快速版本:

final static int SHORT_ISO_8601_TIME_LENGTH =  "1994-11-05T08:15:30Z".length ();
                                            // 01234567890123456789012
final static int LONG_ISO_8601_TIME_LENGTH = "1994-11-05T08:15:30-05:00".length ();


public static Date fromISO8601( String string ) {
    if (isISO8601 ( string )) {
        char [] charArray = Reflection.toCharArray ( string );//uses unsafe or string.toCharArray if unsafe is not available
        int year = CharScanner.parseIntFromTo ( charArray, 0, 4 );
        int month = CharScanner.parseIntFromTo ( charArray, 5, 7 );
        int day = CharScanner.parseIntFromTo ( charArray, 8, 10 );
        int hour = CharScanner.parseIntFromTo ( charArray, 11, 13 );

        int minute = CharScanner.parseIntFromTo ( charArray, 14, 16 );

        int second = CharScanner.parseIntFromTo ( charArray, 17, 19 );

        TimeZone tz ;

         if (charArray[19] == 'Z') {

             tz = TimeZone.getTimeZone ( "GMT" );
         } else {

             StringBuilder builder = new StringBuilder ( 9 );
             builder.append ( "GMT" );
             builder.append( charArray, 19, LONG_ISO_8601_TIME_LENGTH - 19);
             String tzStr = builder.toString ();
             tz = TimeZone.getTimeZone ( tzStr ) ;

         }
         return toDate ( tz, year, month, day, hour, minute, second );

    }   else {
        return null;
    }

}

...

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


public static boolean isISO8601( String string ) {
      boolean valid = true;

      if (string.length () == SHORT_ISO_8601_TIME_LENGTH) {
          valid &=  (string.charAt ( 19 )  == 'Z');

      } else if (string.length () == LONG_ISO_8601_TIME_LENGTH) {
          valid &=  (string.charAt ( 19 )  == '-' || string.charAt ( 19 )  == '+');
          valid &=  (string.charAt ( 22 )  == ':');

      } else {
          return false;
      }

    //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4
    // "1 9 9 4 - 1 1 - 0 5 T 0 8 : 1 5 : 3 0 - 0 5 : 0 0

    valid &=  (string.charAt ( 4 )  == '-') &&
                (string.charAt ( 7 )  == '-') &&
                (string.charAt ( 10 ) == 'T') &&
                (string.charAt ( 13 ) == ':') &&
                (string.charAt ( 16 ) == ':');

    return valid;
}

我尚未对其进行基准测试,但是我想它将很快。它似乎有效。:)

@Test
public void testIsoShortDate() {
    String test =  "1994-11-05T08:15:30Z";

    Date date = Dates.fromISO8601 ( test );
    Date date2 = Dates.fromISO8601_ ( test );

    assertEquals(date2.toString (), date.toString ());

    puts (date);
}

@Test
public void testIsoLongDate() {
    String test =  "1994-11-05T08:11:22-05:00";

    Date date = Dates.fromISO8601 ( test );
    Date date2 = Dates.fromISO8601_ ( test );

    assertEquals(date2.toString (), date.toString ());

    puts (date);
}

-2

我认为很多人想做的就是解析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.org 请参阅http://en.wikipedia.org/wiki/ISO_8601 请参阅http://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)


 */
@Test
public 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非GNUNext)也使用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 Date字符串,尽管它是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/boon Boon具有PLIST解析器(ASCII)和JSON解析器。

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

由加特林性能大佬独立验证。

https://github.com/gatling/json-parsers-benchmark

Benchmark                               Mode Thr     Count  Sec         Mean   Mean error        Units
BoonCharArrayBenchmark.roundRobin      thrpt  16        10    1   724815,875    54339,825    ops/s
JacksonObjectBenchmark.roundRobin      thrpt  16        10    1   580014,875   145097,700    ops/s
JsonSmartBytesBenchmark.roundRobin     thrpt  16        10    1   575548,435    64202,618    ops/s
JsonSmartStringBenchmark.roundRobin    thrpt  16        10    1   541212,220    45144,815    ops/s
GSONStringBenchmark.roundRobin         thrpt  16        10    1   522947,175    65572,427    ops/s
BoonDirectBytesBenchmark.roundRobin    thrpt  16        10    1   521528,912    41366,197    ops/s
JacksonASTBenchmark.roundRobin         thrpt  16        10    1   512564,205   300704,545    ops/s
GSONReaderBenchmark.roundRobin         thrpt  16        10    1   446322,220    41327,496    ops/s
JsonSmartStreamBenchmark.roundRobin    thrpt  16        10    1   276399,298   130055,340    ops/s
JsonSmartReaderBenchmark.roundRobin    thrpt  16        10    1    86789,825    17690,031    ops/s

它具有用于流,读取器,bytes [],char [],CharSequence(StringBuilder,CharacterBuffer)和String的最快的JSON解析器。

在以下位置查看更多基准测试:

https://github.com/RichardHightower/json-parsers-benchmark


这个关于JSON的答案与问题无关。此外,此问题是不正确的,因为在极少数JSON数据类型中没有“ JSON日期”之类的东西。如今,所有这些代码都可以通过对内置Java功能的单行调用来代替:Instant.parse( "2013-12-14T01:55:33.412Z" )
Basil Bourque
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.