将符合 ISO 8601 的字符串转换为 ZonedDateTime

Converting ISO 8601-compliant String to ZonedDateTime

我正在解析来自不同来源的日志,我正在从日志中提取日期时间字符串。现在我想把它转换成 java ZonedDateTime.

这里的问题是,我不知道确切的日期时间格式,我只知道字符串将符合 ISO-8601 标准。所以我想写这样的函数:

    /**
     * @param _dateTime : any ISO 8601 format , e.g 1. %Y-%m-%dT%H:%M:%s%z => 2014-05-25T08:20:03.123456Z , 
     *                    e.g 2. %Y-%m-%dT%H:%M:%s => 2014-05-25T08:20:03.123456, e.g 3. %Y-%m-%d %H:%M:%s%z => 2014-11-08 15:55:55.123456Z, 
     *                    e.g 4. %Y-%m-%d %H:%M:%s => 2014-11-08 15:55:55
     * @return Instance of ZonedDateTime if conversion successful
     * @throws DateTimeParseException
     */
    private ZonedDateTime parse (String _dateTime) throws DateTimeParseException  {
        // Magic here.
    }

在 java 中最好的方法是什么?

这很重要。大多数魔法都在以下格式化程序中:

private static final DateTimeFormatter formatter
        = new DateTimeFormatterBuilder().append(DateTimeFormatter.ISO_LOCAL_DATE)
                .appendPattern("['T'][' ']")
                .append(DateTimeFormatter.ISO_LOCAL_TIME)
                .appendPattern("[XX]")
                .toFormatter();

此格式化程序接受

  • 2014-05-25 这样的日期。
  • 要么是 T 要么是 space (或者两者都有,或者两者都不是,但我指望其中一个在你的字符串中)。方括号表示格式的可选部分。单引号表示文字部分并导致 T 不被解释为模式字母。
  • 一天中的某个时间。 ISO_LOCAL_TIME 接受带小数和不带小数的秒数。
  • 可选的 UTC 偏移量,如 Z(表示零)或 -0600

对于字符串中没有偏移量的情况,我们还需要一个时区。例如:

private static final ZoneId defaultZone = ZoneId.of("America/Curacao");

你的方法还需要做的是区分有和没有UTC偏移的情况。带偏移量的形式可以直接解析成一个ZonedDateTime。没有的不能,所以我们需要解析成 LocalDateTime 并转换为

/**
 * @param dateTime : any ISO 8601 format , e.g 1. %Y-%m-%dT%H:%M:%s%z => 2014-05-25T08:20:03.123456Z , 
 *                    e.g 2. %Y-%m-%dT%H:%M:%s => 2014-05-25T08:20:03.123456, 
 *                    e.g 3. %Y-%m-%d %H:%M:%s%z => 2014-11-08 15:55:55.123456Z, 
 *                    e.g 4. %Y-%m-%d %H:%M:%s => 2014-11-08 15:55:55
 * @return Instance of ZonedDateTime if conversion successful
 * @throws DateTimeParseException
 */
private static ZonedDateTime parse(String dateTime) {
    // Little magic here.
    TemporalAccessor parsed = formatter.parseBest(dateTime,
            ZonedDateTime::from, LocalDateTime::from);
    if (parsed instanceof ZonedDateTime) {
        return (ZonedDateTime) parsed;
    } else {
        return ((LocalDateTime) parsed).atZone(defaultZone);
    }
}

我们来试试吧。我已经使用了你的四个例子加上我在第一个和第二个之间添加的一个。

    System.out.println(parse("2014-05-25T08:20:03.123456Z"));
    System.out.println(parse("2014-05-25T10:20:03.123456+0200"));
    System.out.println(parse("2014-05-25T08:20:03.123456"));
    System.out.println(parse("2014-11-08 15:55:55.123456Z"));
    System.out.println(parse("2014-11-08 15:55:55"));

输出:

2014-05-25T08:20:03.123456Z
2014-05-25T10:20:03.123456+02:00
2014-05-25T08:20:03.123456-04:00[America/Curacao]
2014-11-08T15:55:55.123456Z
2014-11-08T15:55:55-04:00[America/Curacao]

顺便说一句,对于具有 UTC 偏移量的字符串,更喜欢 OffsetDateTime 而不是 ZonedDateTime。代码将是相同的,除了在 atZone(defaultZone) 之后您需要进一步转换 .toOffsetDateTime() 在您没有直接从解析中获得 OffsetDateTime 的情况下。 ZonedDateTime 用于带有时区的日期和时间,如 Europe/Prague。 OffsetDateTime 用于具有 UTC 偏移量的日期和时间。正如我提到的,Z 是与 UTC 的偏移量 0。

编辑:Arvind Kumar Avinash 在评论中的注释非常有用且重要,值得放在正确的答案中:给未来访客的一些注释:

  1. [XX] 替换为 [XX][XXX] 也将迎合像 +02:00 这样的偏移量。
  2. 日期时间 parsing/formatting 类型对区域设置敏感,因此,始终建议将其与适用的区域设置一起使用,例如.toFormatter(Locale.ENGLISH) 而不是 .toFormatter().
  3. 如果您希望 JVM 的 ZoneId 被自动拾取,您可以使用 ZoneId.systemDefault()