xsd:dateTime 转换为 Java OffsetDateTime

5
为了正确处理使用JAXB的xs:dateTime,我需要编写自己的转换器将String->java.time.OffsetDateTime
正如XML模式定义中所提到的,dateTime受ISO 8601的启发。我使用OffsetDateTime.parse(s, DateTimeFormatter.ISO_OFFSET_DATE_TIME)来解析xs:dateTime,这对于例如...是有效的。
"2007-12-03T10:15:30+01:00" //or
"2007-12-03T10:15:30Z"

不幸的是,在xs:dateTime中,偏移部分被声明为可选项,因此解析有效的日期时间字符串时需要小心。

"2016-03-02T17:09:55"

抛出 DateTimeParseException

是否有一个适用于 OffsetDateTime 的 DateTimeFormatter,它还处理未指定时区的 xs:dateTime(可能带有默认时区)?

4个回答

5

我认为没有内置的方法,但是你可以使用 DateTimeFormatterBuilder 类来创建自己的方法。

你可以指定一个可选的偏移量,用方括号括起来,即 [XXXXX](匹配 "+HH:MM:ss"),然后,你可以提供一个默认偏移量(parseDefaulting),以防它不存在。如果你想将默认值设置为 UTC,则可以将 0 设置为指定无偏移量;如果你想将默认值设置为 VM 的当前偏移量,则可以使用 OffsetDateTime.now().getLong(ChronoField.OFFSET_SECONDS) 获取它。

public static void main(String[] args) {
    String[] dates = {
        "2007-12-03T10:15:30+01:00",
        "2007-12-03T10:15:30Z",
        "2016-03-02T17:09:55",
        "2016-03-02T17:09:55Z"
    };
    DateTimeFormatter formatter =
        new DateTimeFormatterBuilder().appendPattern("yyyy-MM-dd'T'HH:mm:ss[XXXXX]")
                                      .parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
                                      // or OffsetDateTime.now().getLong(ChronoField.OFFSET_SECONDS)
                                      .toFormatter();
    for (String date : dates) {
        System.out.println(OffsetDateTime.parse(date, formatter));
    }
}

好的回答。但我们都必须这样做,这不是很奇怪吗?为什么 DateTimeFormatter 没有标准模式来处理 xs:datexs:dateTimexs:time - peterh
实际上,我认为 xs:dateTime 的正确模式是 [XXX](而不是 5 个 X,而是 3 个 X),因为 xs:dateTime 不允许基于秒的区域偏移。 - b4hand
@b4hand:或者是6,因为+01:00有6个字符;-) - Kukeltje
不,根据文档,您应该只使用3个X。XXX 等同于 appendOffset("+HH:MM","Z")。请参阅 https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/format/DateTimeFormatterBuilder.html#appendPattern(java.lang.String)。 - b4hand
请注意,使用本地时区偏移量可能会导致夏令时问题。 如果字符串中的时间处于夏令时而当前时间不是,或者反之,则该偏移量会偏差一个小时(通常如此)。例如,如果您在欧洲中部且目前处于夏令时期间,则OffsetDateTime.now()的偏移量为2个小时,而在夏令时期间外的日期中,应该是1个小时。 Stefan K的答案没有这个问题。 - Kadser

3

我想展示一下我的当前解决方案,它将未指定时区格式转换为系统默认偏移量,以当前解析的日期时间为准

public static OffsetDateTime parseDateTime(String s) {
    if (s == null) {
        return null;
    }
    try {
        return OffsetDateTime.parse(s, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
    } catch (DateTimeParseException e) {
        try { // try to handle zoneless xml dateTime
            LocalDateTime localDateTime = LocalDateTime.parse(s, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
            ZoneOffset offset = ZoneId.systemDefault().getRules().getOffset(localDateTime);
            return OffsetDateTime.of(localDateTime.toLocalDate(), localDateTime.toLocalTime(), offset);
        } catch (Exception fallbackTryException) {
            throw e;
        }
    }
}

1
啊,所以当偏移量缺失时,你会将日期时间解释为JVM的时区。我认为我会使用localDateTime.atZone(ZoneId.systemDefault()).toOffsetDateTime()。这不是更容易阅读吗? - Ole V.V.

0

在@Tunaki的回答中,我提到了一个问题。因为xs:dateTime不允许基于秒的区域偏移量。所以正确的模式应该只包括[XXX],这基于appendPattern的文档说明。

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
    .appendPattern("yyyy-MM-dd'T'HH:mm:ss[XXX]")
    .parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
    .toFormatter();

以下是时区格式的列表:

  Pattern  Count  Equivalent builder methods
  -------  -----  --------------------------
    O       1      appendLocalizedOffset(TextStyle.SHORT)
    OOOO    4      appendLocalizedOffset(TextStyle.FULL)
    X       1      appendOffset("+HHmm","Z")
    XX      2      appendOffset("+HHMM","Z")
    XXX     3      appendOffset("+HH:MM","Z")
    XXXX    4      appendOffset("+HHMMss","Z")
    XXXXX   5      appendOffset("+HH:MM:ss","Z")
    x       1      appendOffset("+HHmm","+00")
    xx      2      appendOffset("+HHMM","+0000")
    xxx     3      appendOffset("+HH:MM","+00:00")
    xxxx    4      appendOffset("+HHMMss","+0000")
    xxxxx   5      appendOffset("+HH:MM:ss","+00:00")
    Z       1      appendOffset("+HHMM","+0000")
    ZZ      2      appendOffset("+HHMM","+0000")
    ZZZ     3      appendOffset("+HHMM","+0000")
    ZZZZ    4      appendLocalizedOffset(TextStyle.FULL)
    ZZZZZ   5      appendOffset("+HH:MM:ss","Z")

0

jTextTime库可以解决这个问题。这是一个最小的无依赖库,可从Maven Central获取。它处理缺少时区偏移量的情况。

该库包括预构建的XmlAdapters,因此您只需像下面的示例那样对您的类进行注释:

public class Customer {

    @XmlElement
    @XmlJavaTypeAdapter(OffsetDateTimeXmlAdapter.class)
    @XmlSchemaType(name="dateTime")
    public OffsetDateTime getLastOrderTime() {
        ....
    }

    @XmlElement
    @XmlJavaTypeAdapter(OffsetDateXmlAdapter.class)
    @XmlSchemaType(name="date")
    public OffsetDateTime getDateOfBirth() {   // returns a date-only value
        ....
    }
}

或者,您可以在包级别上进行注释,这样您就不必为每个类和每个属性都进行注释。

如果您对库默认处理缺少时区偏移量的情况不满意,则可以自定义

完全披露:我是jTextTime的作者。


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