协慌网

登录 贡献 社区

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

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

如果与区域设置(比较示例)一起使用,我发现模式yyyy-MM-dd'T'HH:mm:ssZ

但是,使用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" ) ;

对我来说,这很好。

答案

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

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 中有一种全新的方法来执行此操作

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

我一直在寻找适用于 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,以确保线程安全)。