如何从字符串“06.03.2018 06:00 CET”获取 {Zoned,Offset}DateTime?

How do I get a {Zoned,Offset}DateTime from String "06.03.2018 06:00 CET"?

我正在尝试解析来自欧洲 RSS 提要的日期时间。日期如下所示:"06.03.2018 06:00 CET"。我想要 ZonedDateTimeOffsetDateTime,但我无法说服 Java 解析字符串。我做错了什么?

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd.MM.YYYY HH:mm z");
ZonedDateTime zdt = ZonedDateTime.parse("06.03.2018 06:00 CET", dtf);

Caused by: java.time.DateTimeException: Unable to obtain ZonedDateTime from TemporalAccessor: {WeekBasedYear[WeekFields[SUNDAY,1]]=2018, MonthOfYear=3, DayOfMonth=6},ISO,Europe/Paris resolved to 06:00 of type java.time.format.Parsed

// seems to be crashing here:  LocalDate.java:363
public static LocalDate from(TemporalAccessor temporal) {
    Objects.requireNonNull(temporal, "temporal");
    LocalDate date = temporal.query(TemporalQueries.localDate());
    if (date == null) {  // <== SOMEHOW THIS IS NULL
        throw new DateTimeException("Unable to obtain LocalDate from TemporalAccessor: " +
                temporal + " of type " + temporal.getClass().getName());
    }
    return date;
}

temporal的值为:

{WeekBasedYear[WeekFields[SUNDAY,1]]=2018, MonthOfYear=3, DayOfMonth=6},ISO,Europe/Paris resolved to 06:00

大写Y表示以周为单位的年份(详见here),而年份字段用小写y表示。有关详细信息,请参阅 javadoc: https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#patterns

所以将模式更改为 "dd.MM.yyyy HH:mm z"。

另一个细节是时区缩写不明确。 CET 被多个国家使用,因此它可以映射到多个时区:https://www.timeanddate.com/time/zones/cet#tz-where

一些缩写可能有效(又名 "doesn't throw exception"),但它们将被映射到一些您无法控制的任意默认值。在我的 JVM 中,运行 这段代码:

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm z");
ZonedDateTime zdt = ZonedDateTime.parse("06.03.2018 06:00 CET", dtf);

产生一个 ZonedDateTime 等于:

2018-03-06T06:00+01:00[Europe/Paris]

CET 被映射到 "Europe/Paris"(这是 JVM 的一些任意选择,但不能保证您总能得到它)。 CET 也被许多其他时区使用,例如 "Europe/Berlin"、"Europe/Madrid" 和许多其他时区。

如果您想在出现缩写时准确控制您想要的时区,您可以创建一个带有您的选择的集合并使用 DateTimeFormatterBuilder 创建您的 DateTimeFormatter:

// set of preferred zones
Set<ZoneId> preferredZones = new HashSet<ZoneId>();
// my arbitrary choice for CET
preferredZones.add(ZoneId.of("Europe/Berlin"));
DateTimeFormatter dtf = new DateTimeFormatterBuilder()
    // date/time
    .appendPattern("dd.MM.yyyy HH:mm ")
    // zone names
    .appendZoneText(TextStyle.SHORT, preferredZones)
    // create formatter
    .toFormatter(Locale.US);

ZonedDateTime zdt = ZonedDateTime.parse("06.03.2018 06:00 CET", dtf);

现在 ZonedDateTime 将设置为我在首选时区集中选择的时区:

2018-03-06T06:00+01:00[Europe/Berlin]