Java 将符合ISO 8601的字符串转换为ZoneDateTime

Java 将符合ISO 8601的字符串转换为ZoneDateTime,java,java-time,Java,Java Time,我正在解析来自不同来源的日志,我正在从日志中提取datetime字符串。现在我想把它转换成java ZoneDateTime 这里的问题是,我不知道确切的日期时间格式,我只知道,字符串将符合ISO-8601。所以我想编写如下函数: /** * @param _dateTime : any ISO 8601 format , e.g 1. %Y-%m-%dT%H:%M:%s%z => 2014-05-25T08:20:03.123456Z , *

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

这里的问题是,我不知道确切的日期时间格式,我只知道,字符串将符合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
    要么是一个空格(要么两者都有,要么两者都没有,但我只知道字符串中有一个空格)。方括号表示格式的可选部分。单引号表示文字部分,并导致
    T
    不被解释为模式字母
  • 一天中的某个时间<代码>ISO_本地时间接受带小数点和不带小数点的秒数
  • 可选的UTC偏移量,如
    Z
    (零)或
    -0600
对于字符串中没有偏移量的情况,我们还需要一个时区。例如:

private static final ZoneId defaultZone = ZoneId.of("America/Curacao");
您的方法仍然需要做的是区分有UTC偏移和没有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"));
输出:

另外,对于UTC偏移量的字符串,您更喜欢
OffsetDateTime
而不是
zoneDateTime
。代码将是相同的,除非您需要在
atZone(defaultZone)
之后进一步转换
.toOffsetDateTime()
,如果您没有直接从解析中获得
OffsetDateTime
ZonedDateTime
表示时区为欧洲/布拉格的日期和时间。
OffsetDateTime
表示具有UTC偏移的日期和时间。正如我提到的,
Z
是UTC的0偏移量

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

  • [XX]
    替换为
    [XX][XXX]
    也将满足+02:00等偏移量
  • 日期-时间解析/格式类型对区域设置敏感,因此,建议将其与适用的区域设置一起使用,例如
    .toFormatter(Locale.ENGLISH)
    而不是
    .toFormatter()
  • 如果希望自动拾取JVM的
    ZoneId
    ,可以使用
    ZoneId.systemDefault()

  • 这不是小事。大部分神奇之处在于以下格式化程序:

    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
      要么是一个空格(要么两者都有,要么两者都没有,但我只知道字符串中有一个空格)。方括号表示格式的可选部分。单引号表示文字部分,并导致
      T
      不被解释为模式字母
    • 一天中的某个时间<代码>ISO_本地时间接受带小数点和不带小数点的秒数
    • 可选的UTC偏移量,如
      Z
      (零)或
      -0600
    对于字符串中没有偏移量的情况,我们还需要一个时区。例如:

    private static final ZoneId defaultZone = ZoneId.of("America/Curacao");
    
    您的方法仍然需要做的是区分有UTC偏移和没有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"));
    
    输出:

    另外,对于UTC偏移量的字符串,您更喜欢
    OffsetDateTime
    而不是
    zoneDateTime
    。代码将是相同的,除非您需要在
    atZone(defaultZone)
    之后进一步转换
    .toOffsetDateTime()
    ,如果您没有直接从解析中获得
    OffsetDateTime
    ZonedDateTime
    表示时区为欧洲/布拉格的日期和时间。
    OffsetDateTime
    表示具有UTC偏移的日期和时间。正如我提到的,
    Z
    是UTC的0偏移量

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

  • [XX]
    替换为
    [XX][XXX]
    也将满足+02:00等偏移量
  • 日期-时间解析/格式类型对区域设置敏感,因此,建议将其与适用的区域设置一起使用,例如
    .toFormatter(Locale.ENGLISH)
    而不是
    .toFormatter()
  • 如果希望自动拾取JVM的
    ZoneId
    ,可以使用
    ZoneId.systemDefault()

  • 你不知道格式是什么意思?ISO 8601 datetime是一种格式。您的javadoc似乎建议在日期和时间之间使用空格可以替代字母T。这不是ISO 8601。ISO 8601始终使用T作为分隔符。正如我在方法描述中提到的,它可能类似于2014-05-25T08:20:03.123456或2014-11-08 15:55:55.123456Z或2014-11-08 15:55:55等等……您的第四个示例,
    2014-11-08 15:55:55
    ,不符合ISO 8601。ISO 8601要求使用
    T
    来表示时间部分的开始,如第一个和第二个示例所示。第三个示例也存在同样的问题。当字符串以UTC的
    Z
    结尾时,显然应该在UTC中获得
    ZonedDateTime
    。如果没有,你想要哪个时区?它需要一个时区来构造
    ZonedDateTim