Java 6中的ISO 8601格式通用支持

9
Java 7在SimpleDateFormat类中引入了对ISO 8601格式的支持,使用字符X(而不是小写或大写的Z)。在Java 6中支持这种格式需要预处理,因此最好的方法是提出问题。
这种新格式是Z(大写Z)的超集,有两个附加变体:
1. “分钟”字段是可选的(即有效的2位数字时区而不是4位数字) 2. 冒号字符(“:”)可用于将2位数字的“小时”字段与2位数字的“分钟”字段分隔开。
因此,正如人们可以从 Java 7关于SimpleDateFormat的文档中观察到的那样,现在有效的有以下3种格式(而不仅仅是Java 6中由Z覆盖的第二种格式),当然,它们是等效的:
1. -08 2. -0800 3. -08:00

如之前问题提到的,关于支持这种“扩展”的时区格式的特殊情况,始终使用':'作为分隔符,将Java 7的功能回溯到Java 6的最佳方法是子类化SimpleDateformat类并覆盖其parse()方法,即:

public Date parse(String date, ParsePosition pos)
{
    String iso = ... // Replace the X with a Z timezone string, using a regex

    if (iso.length() == date.length())
    {
        return null; // Not an ISO 8601 date
    }

    Date parsed = super.parse(iso, pos);

    if (parsed != null)
    {
        pos.setIndex(pos.getIndex()+1); // Adjust for ':'
    }

    return parsed;
}

请注意,上述子类化的SimpleDateFormat对象必须使用相应的基于Z的模式进行初始化,即如果子类是ExtendedSimpleDateformat并且您想解析符合模式yyyy-MM-dd'T'HH:mm:ssX的日期,则应使用实例化的对象。
new ExtendedSimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");

在上述早期问题中,建议使用正则表达式:(?=[0-9]{2}$)来去掉':',在类似的问题中,建议使用正则表达式(?<=[+-]\d{2})$来添加"minute"字段为00(如果需要)。
显然,成功运行这两个替换可以用于实现完整功能。因此,在重写的parse()方法中,iso本地变量将被设置为:
iso = date.replaceFirst(":(?=[0-9]{2}$)","");

或者

iso = iso.replaceFirst("(?<=[+-]\\d{2})$", "00");

使用if检查,确保稍后也正确设置了pos值,并且在早期的length()比较中也是如此。问题是:我们可以使用单个正则表达式来实现相同的效果,包括所需的信息以避免不必要地检查长度并正确设置几行后的pos吗?该实现旨在用于读取数量非常大的字符串字段(可以是任何格式,甚至是完全非日期格式)的代码,仅选择符合格式的字段并返回解析后的Java Date对象。因此,精度速度都非常重要(即,如果使用两个步骤更快,则应优先考虑此方法)。

1
你是否检查了JDK 7中相应的代码? - assylias
还没有,因为我没有使用它,但可能这不会提供太多帮助,因为在SimpleDateFormat类内部,模式在处理之前被编译成语法,所以没有对应任何正则表达式。无论如何,谢谢和+1。 :-) - PNS
1
你考虑过javax.xml.datatype.DatatypeFactory吗?它支持8601格式的日期字符串。请参见http://download.java.net/jdk7/archive/b123/docs/api/javax/xml/datatype/DatatypeFactory.html#newXMLGregorianCalendar(java.lang.String)。 - Zagrev
为什么不尝试使用Joda Time并返回一个Date对象呢? - Michael-O
JodaTime 不支持 Java 支持的所有日期格式模式。否则,它将始终是首选。 - PNS
1
使用java.time类比旧的遗留日期时间类(Date, Calendar等)和Joda-Time更加容易。许多java.time功能在ThreeTen-Backport中被移植到Java 6和7,并在ThreeTenABP中进一步适用于Android。 - Basil Bourque
3个回答

6

看起来你可以使用这个:

import java.util.Calendar;
import javax.xml.bind.DatatypeConverter;

public class TestISO8601 {
    public static void main(String[] args) {
        parse("2012-10-01T19:30:00+02:00"); // UTC+2
        parse("2012-10-01T19:30:00Z");      // UTC
        parse("2012-10-01T19:30:00");       // Local
    }
    public static Date parse(final String str) {
        Calendar c = DatatypeConverter.parseDateTime(str);
        System.out.println(str + "\t" + (c.getTime().getTime()/1000));
        return c.getTime();
    }
}

5

您可以在Java 6中使用现代的Java日期和时间API——java.time。这对我来说似乎是一个不错且具有未来性的解决方案。它对ISO 8601有很好的支持。

import org.threeten.bp.OffsetDateTime;
import org.threeten.bp.format.DateTimeFormatter;

public class DemoIso8601Offsets {
    public static void main(String[] args) {
        System.out.println(OffsetDateTime.parse("2012-10-01T19:30:00+0200", 
                DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ssXX")));
        System.out.println(OffsetDateTime.parse("2012-10-01T19:30:00+02", 
                DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ssX")));
        System.out.println(OffsetDateTime.parse("2012-10-01T19:30:00+02:00"));
        System.out.println(OffsetDateTime.parse("2012-10-01T19:30:00Z"));
    }
}

该程序的输出结果是:

2012-10-01T19:30+02:00
2012-10-01T19:30+02:00
2012-10-01T19:30+02:00
2012-10-01T19:30Z
需要在项目设置中添加ThreeTen Backport库。
  • 在Java 8及更高版本和新的Android设备(API级别26以上)上,现代API已内置。
  • 在Java 6和7中获取ThreeTen Backport,即新类的后移(JSR 310的ThreeTen;请参见底部链接)。
  • 在(旧的)Android上使用ThreeTen Backport的Android版。它被称为ThreeTenABP。并确保您从org.threeten.bp及其子包中导入日期和时间类。
如您从代码中所看到的, +02+0200 需要使用格式器来指定偏移量的格式,而 +02:00(以及 Z)符合默认格式,无需指定。

我们可以使用相同的格式器解析所有偏移格式吗?

当读取混合数据时,您不希望特别处理每个偏移格式。最好使用格式模式字符串中的可选部分:

    DateTimeFormatter allInOne 
            = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss[XXX][XX][X]");
    System.out.println(OffsetDateTime.parse("2012-10-01T19:30:00+0200", allInOne));
    System.out.println(OffsetDateTime.parse("2012-10-01T19:30:00+02", allInOne));
    System.out.println(OffsetDateTime.parse("2012-10-01T19:30:00+02:00", allInOne));
    System.out.println(OffsetDateTime.parse("2012-10-01T19:30:00Z", allInOne));

输出与上面相同。方括号中的 [XXX][XX][X] 表示可能出现格式为 +02:00+0200+02

链接


1
从代码中可以看出,+02和+0200需要一个格式化程序:+02不需要格式化程序。 - Arvind Kumar Avinash
1
@ArvindKumarAvinash,对我来说,文档似乎自相矛盾,关于偏移量的分钟是否是可选的。从 OffsetDateTime.parse("2012-10-01T19:30:00+02") 中,我得到了 org.threeten.bp.format.DateTimeParseException: Text '2012-10-01T19:30:00+02' could not be parsed at index 19 的错误信息,因此显然在使用 ThreeTen Backport(因为问题中要求使用 Java 6)时,它们不是可选的(索引 19 是 +02 的位置)。如果你指的是 Java 11,那么同样的代码可以在没有异常的情况下使用 java.time.OffsetDateTime.parse(CharSequence) - Ole V.V.

0
相同的方法适用于不同的毫秒和不同的偏移量。
String DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss[.SSS][.SS][.S][XXX][XX][X]";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN);

Date convertDate(String dateString) {
    return Date.from(OffsetDateTime.parse(dateString, formatter).toInstant());
}

有时候你需要为getter和setter使用两种不同的模式:
String DATE_TIME_PATTERN_SET = "yyyy-MM-dd'T'HH:mm:ss[.SSS][.SS][.S][XXX][XX][X]";
String DATE_TIME_PATTERN_GET = "yyyy-MM-dd'T'HH:mm:ssXXX";
DateTimeFormatter formatterSet = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN_SET);
DateFormat dateFormat = new SimpleDateFormat(DATE_TIME_PATTERN_GET);

Date convertToDate(String dateString) {
    return Date.from(OffsetDateTime.parse(dateString, formatterSet).toInstant());
}

String convertToString(Date date) {
    dateFormat.setTimeZone(TimeZone.getDefault());
    return dateFormat.format(date).replaceAll("Z$", "+00:00");
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接