如何解析可以为任何ISO 8601格式的Java字符串?

时间:2018-08-14 15:32:11

标签: java date datetime

在Java中将字符串解析为Date的最佳方法是什么?该Date可以采用任何有效的ISO 8601格式或Unix纪元毫秒?例如,它需要能够解析以下内容(所有这些都是有效的ISO 8601或Unix纪元毫秒):

  • 1534251817666
  • 2017-01-01
  • 2017-01-01T00
  • 2017-01-01T00:03
  • 2017-01-01T00:03,5
  • 2017-01-01T00:03.5
  • 2017-01-01T03:03:00 + 00:00
  • 2017-01-01T03:03:00-05:00
  • 2017-01-01T03:03:00 + 0500
  • 2017-01-01T03:03:00Z
  • 20170101T030300Z
  • 2017-W01-1
  • 2017W011
  • 2017-001
  • 2017001

我发现以下代码可以解决大多数情况,但不能解决所有情况,因为提供的java.time中没有DateTimeFormatters可以处理所有ISO 8601情况:

try {
    return Date.from(Instant.ofEpochMilli(Long.parseLong(time)));
} catch (NumberFormatException e) {
    return Date.from(Instant.parse(time));
}

2 个答案:

答案 0 :(得分:0)

解析所有这些格式的一种方法是编写一个正则表达式,然后从解析的值中创建适当的Temporal对象。

private static Temporal parse(String text) {
    String regex = "(?:" +
                      "(\\d{9,})" +        // 1: millis
                   "|" +
                      "(\\d{4})" +         // 2: year
                      "(?:" +
                         "-?(\\d{3})" +    // 3: day-of-year
                      "|" +
                         "(-?)W(\\d{2})" + // 5: week-of-year
                         "(?:\\4(\\d))?" + // 6: day-of-week (optional)
                      "|" +
                         "(-?)(\\d{2})" +  // 8: month-of-year
                         "\\7(\\d{2})" +   // 9: day-of-month
                      ")" +
                      "(?:T(\\d{2})" +             // 10: hour (optional)
                        "(?:(:?)(\\d{2})" +        // 12: minute (optional)
                          "(?:\\11(\\d{2})" +      // 13: second (optional)
                            "(?:\\.(\\d{1,9}))?" + // 14: fractional (optional)
                          ")?" +
                        ")?" +
                        "(?:" +
                          "(Z)" +          // 15: Zulu
                        "|" +
                          "([+-]\\d{2})" + // 16: Offset hour (signed)
                          ":?(\\d{2})" +   // 17: Offset minute
                        ")?" +
                      ")?" +
                   ")";
    Matcher m = Pattern.compile(regex).matcher(text);
    if (! m.matches())
        throw new DateTimeParseException("Invalid date string", text, 0);

    // Handle millis
    if (m.start(1) != -1)
        return Instant.ofEpochMilli(Long.parseLong(m.group(1)));

    // Parse local date
    LocalDate localDate;
    if (m.start(3) != -1)
        localDate = LocalDate.ofYearDay(Integer.parseInt(m.group(2)), Integer.parseInt(m.group(3)));
    else if (m.start(5) != -1)
        localDate = LocalDate.parse(m.group(2) + "-W" + m.group(5) + "-" + (m.start(6) == -1 ? "1" : m.group(6)),
                                    DateTimeFormatter.ISO_WEEK_DATE);
    else
        localDate = LocalDate.of(Integer.parseInt(m.group(2)), Integer.parseInt(m.group(8)), Integer.parseInt(m.group(9)));
    if (m.start(10) == -1)
        return localDate;

    // Parse local time
    int hour   = Integer.parseInt(m.group(10));
    int minute = (m.start(12) == -1 ? 0 : Integer.parseInt(m.group(12)));
    int second = (m.start(13) == -1 ? 0 : Integer.parseInt(m.group(13)));
    int nano   = (m.start(14) == -1 ? 0 : Integer.parseInt((m.group(14) + "00000000").substring(0, 9)));
    LocalTime localTime = LocalTime.of(hour, minute, second, nano);

    // Return date/time
    if (m.start(15) != -1)
        return ZonedDateTime.of(localDate, localTime, ZoneOffset.UTC);
    if (m.start(16) == -1)
        return LocalDateTime.of(localDate, localTime);
    ZoneOffset zone = ZoneOffset.ofHoursMinutes(Integer.parseInt(m.group(16)), Integer.parseInt(m.group(17)));
    return ZonedDateTime.of(localDate, localTime, zone);
}

测试

public static void main(String[] args) {
    test("1534251817666");
    test("2017-01-01");
    test("2017-01-01T00");
    test("2017-01-01T00:03");
    test("2017-01-01T00:03:00.5"); // modified
    test("2017-01-01T03:03:00+00:00");
    test("2017-01-01T03:03:00-05:00");
    test("2017-01-01T03:03:00+0500");
    test("2017-01-01T03:03:00Z");
    test("20170101T030300Z");
    test("2017-W01-1");
    test("2017W011");
    test("2017-001");
    test("2017001");
}
private static void test(String text) {
    Temporal parsed = parse(text);
    System.out.printf("%-25s -> %-25s %s%n", text, parsed, parsed.getClass().getSimpleName());
}

输出

1534251817666             -> 2018-08-14T13:03:37.666Z  Instant
2017-01-01                -> 2017-01-01                LocalDate
2017-01-01T00             -> 2017-01-01T00:00          LocalDateTime
2017-01-01T00:03          -> 2017-01-01T00:03          LocalDateTime
2017-01-01T00:03:00.5     -> 2017-01-01T00:03:00.500   LocalDateTime
2017-01-01T03:03:00+00:00 -> 2017-01-01T03:03Z         ZonedDateTime
2017-01-01T03:03:00-05:00 -> 2017-01-01T03:03-05:00    ZonedDateTime
2017-01-01T03:03:00+0500  -> 2017-01-01T03:03+05:00    ZonedDateTime
2017-01-01T03:03:00Z      -> 2017-01-01T03:03Z         ZonedDateTime
20170101T030300Z          -> 2017-01-01T03:03Z         ZonedDateTime
2017-W01-1                -> 2017-01-02                LocalDate
2017W011                  -> 2017-01-02                LocalDate
2017-001                  -> 2017-01-01                LocalDate
2017001                   -> 2017-01-01                LocalDate

您当然可以选择始终返回ZonedDateTime,在未指定区域的情况下使用JVM默认时区替换语句,如下所示:

private static Temporal parse(String text) {
private static ZonedDateTime parse(String text) {

return Instant.ofEpochMilli(Long.parseLong(m.group(1)));
return Instant.ofEpochMilli(Long.parseLong(m.group(1))).atZone(ZoneOffset.UTC);

return localDate;
return ZonedDateTime.of(localDate, LocalTime.MIDNIGHT, ZoneId.systemDefault());

return LocalDateTime.of(localDate, localTime);
return ZonedDateTime.of(localDate, localTime, ZoneId.systemDefault());

测试

private static void test(String text) {
    System.out.printf("%-25s -> %s%n", text, parse(text));
}

输出

1534251817666             -> 2018-08-14T13:03:37.666Z
2017-01-01                -> 2017-01-01T00:00-05:00[America/New_York]
2017-01-01T00             -> 2017-01-01T00:00-05:00[America/New_York]
2017-01-01T00:03          -> 2017-01-01T00:03-05:00[America/New_York]
2017-01-01T00:03:00.5     -> 2017-01-01T00:03:00.500-05:00[America/New_York]
2017-01-01T03:03:00+00:00 -> 2017-01-01T03:03Z
2017-01-01T03:03:00-05:00 -> 2017-01-01T03:03-05:00
2017-01-01T03:03:00+0500  -> 2017-01-01T03:03+05:00
2017-01-01T03:03:00Z      -> 2017-01-01T03:03Z
20170101T030300Z          -> 2017-01-01T03:03Z
2017-W01-1                -> 2017-01-02T00:00-05:00[America/New_York]
2017W011                  -> 2017-01-02T00:00-05:00[America/New_York]
2017-001                  -> 2017-01-01T00:00-05:00[America/New_York]
2017001                   -> 2017-01-01T00:00-05:00[America/New_York]

答案 1 :(得分:-2)

我有类似的任务。我编写了一个处理此问题的实用程序。不幸的是,我没有该实用程序本身,但是写了一篇文章,描述了该解决方案的想法。这是文章的链接:Java 8 java.time package: parsing any string to date。尽管有标题,但该想法也可以在早于8的版本中实现。基本上,这个想法是将所有可能的格式放在配置文件中,并尝试一个一个地解析String直到成功。格式的顺序很重要,因为有时可以用不同的格式成功解析字符串并导致不同的Date值。因此,首先放置更重要的格式。阅读文章了解详情