【问题标题】:java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME fails to parse time zone namesjava.time.format.DateTimeFormatter.RFC_1123_DATE_TIME 无法解析时区名称
【发布时间】:2018-01-31 10:59:43
【问题描述】:

我正在尝试从定义为使用 RFC1123 兼容的日期时间规范的数据源解析时间戳。我的代码是:

value = Instant.from (DateTimeFormatter.RFC_1123_DATE_TIME.parse (textValue));

这适用于某些数据,但对于包含区域名称的字符串,即使是在 RFC2822 中定义的字符串(因为它已经过时 RFC822 而间接引用自 RFC1123),我也会遇到异常。例子:

java.time.format.DateTimeParseException: Text 'Sun, 20 Aug 2017 00:30:00 UT' could not be parsed at index 26
java.time.format.DateTimeParseException: Text 'Mon, 21 Aug 2017 15:00:00 EST' could not be parsed at index 26

如何说服DateTimeFormatter接受这种约会方式?

【问题讨论】:

  • docs 明确声明“不处理北美地区名称和军事地区名称。”但后来source// should handle UT/Z/EST/EDT/CST/CDT/MST/MDT/PST/MDT,我猜是TODO?
  • 好的,所以他们称之为 RFC_1123_DATE_TIME 但它不处理 RFC1123 日期时间?为什么叫它……?!你如何解析这个?我正在尝试为其提供一个格式字符串,但看不到如何处理该区域可选地是命名区域 偏移量的事实......
  • 击败我。也许是因为它可以格式化为 RFC1123。
  • 您可以从源中复制格式化程序的结构,并将区域更改为.appendZoneText(TextStyle.SHORT)。这将解析 EST、UT 和 GMT,但不解析偏移量。
  • 其实它看起来会解析偏移量。只是对 DateTimeFormatter 的格式字符串进行试验表明,“z”(我相信是同一件事)能够接受我的所有文本,包括带有数字偏移的文本。没有文档提到这个......

标签: java date parsing java-time datetime-parsing


【解决方案1】:

@shmosel's comment 注意到,javadoc says RFC_1123_DATE_TIME “不处理北美或军事区域名称,仅处理 'GMT' 和偏移量.

要让它识别像UTEST 这样的短时区名称,唯一的方法是构建一个自定义格式化程序,其结构类似于RFC_1123_DATE_TIME 的结构,但最后添加短时区ID。

这种格式使用英文名称来表示月份和星期几,因此一种替代方法是使用英文区域设置,但 source code 使用具有固定值的自定义映射,如果更改不依赖于区域设置(注释应用程序代码可以更改区域设置数据)。所以我们首先重新创建这些地图:

// custom map for days of week
Map<Long, String> dow = new HashMap<>();
dow.put(1L, "Mon");
dow.put(2L, "Tue");
dow.put(3L, "Wed");
dow.put(4L, "Thu");
dow.put(5L, "Fri");
dow.put(6L, "Sat");
dow.put(7L, "Sun");
// custom map for months
Map<Long, String> moy = new HashMap<>();
moy.put(1L, "Jan");
moy.put(2L, "Feb");
moy.put(3L, "Mar");
moy.put(4L, "Apr");
moy.put(5L, "May");
moy.put(6L, "Jun");
moy.put(7L, "Jul");
moy.put(8L, "Aug");
moy.put(9L, "Sep");
moy.put(10L, "Oct");
moy.put(11L, "Nov");
moy.put(12L, "Dec");

然后我重新创建RFC_1123_DATE_TIME的相同结构,但在最后添加区域ID:

// create with same format as RFC_1123_DATE_TIME 
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    .parseCaseInsensitive()
    .parseLenient()
    .optionalStart()
    .appendText(DAY_OF_WEEK, dow)
    .appendLiteral(", ")
    .optionalEnd()
    .appendValue(DAY_OF_MONTH, 1, 2, SignStyle.NOT_NEGATIVE)
    .appendLiteral(' ')
    .appendText(MONTH_OF_YEAR, moy)
    .appendLiteral(' ')
    .appendValue(YEAR, 4)  // 2 digit year not handled
    .appendLiteral(' ')
    .appendValue(HOUR_OF_DAY, 2)
    .appendLiteral(':')
    .appendValue(MINUTE_OF_HOUR, 2)
    .optionalStart()
    .appendLiteral(':')
    .appendValue(SECOND_OF_MINUTE, 2)
    .optionalEnd()
    .appendLiteral(' ')
    // difference from RFC_1123_DATE_TIME: optional offset OR zone ID
    .optionalStart()
    .appendZoneText(TextStyle.SHORT)
    .optionalEnd()
    .optionalStart()
    .appendOffset("+HHMM", "GMT")
    // use the same resolver style and chronology
    .toFormatter().withResolverStyle(ResolverStyle.SMART).withChronology(IsoChronology.INSTANCE);

这里的区别在于.appendZoneText(TextStyle.SHORT)(与optionalStart(),因为它可以有偏移/GMT短区域ID)。

您还会注意到在source code 中它使用:

.toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE);

但是toFormatter 的这个重载版本是不公开的。所以我不得不使用with 方法来调整它,以相应地调整值。

使用这个格式化程序,我可以解析输入:

System.out.println(Instant.from(fmt.parse("Mon, 21 Aug 2017 15:00:00 EST")));
System.out.println(Instant.from(fmt.parse("Sun, 20 Aug 2017 00:30:00 UT")));

输出是:

2017-08-21T19:00:00Z
2017-08-20T00:30:00Z


PS:EST 这样的短名称是 ambiguous and not standard。理想的情况是始终使用IANA timezones names(始终使用Region/City 的格式,例如America/New_YorkEurope/London)。

EST 不明确,因为有 more than one timezone that uses it。一些短名称无法识别,但由于追溯兼容性原因,其中一些被设置为任意默认值。例如,EST 映射到 America/New_York,如果我将其解析为 ZonedDateTime

System.out.println(ZonedDateTime.from(fmt.parse("Mon, 21 Aug 2017 15:00:00 EST")));

输出是:

2017-08-21T15:00-04:00[美国/纽约]

也许这不适用于您的情况,因为您将所有内容解析为 Instant,但如果您想要 ZonedDateTime,可以通过定义一组首选区域来更改这些默认值:

// set of preferred zones
Set<ZoneId> preferredZones = new HashSet<>();
// add my arbitrary choices
preferredZones.add(ZoneId.of("America/Indianapolis"));

America/Indianapolis 是另一个使用EST 作为短名称的时区,因此我可以将其设置为首选而不是默认的America/New_York。我只需要在格式化程序中设置它。而不是这个:

.appendZoneText(TextStyle.SHORT)

我称之为:

.appendZoneText(TextStyle.SHORT, preferredZones)

现在将使用我首选的任意区域。同样的代码:

System.out.println(ZonedDateTime.from(fmt.parse("Mon, 21 Aug 2017 15:00:00 EST")));

现在打印:

2017-08-21T15:00-04:00[美国/印第安纳波利斯]

还要注意上面的ZonedDateTime 的偏移量是-04:00。那是因为在 8 月这些区域处于夏令时 (DST),所以实际上各自的简称是 EDT。如果您使用上述相同的格式化程序格式化日期:

System.out.println(ZonedDateTime.now(ZoneId.of("America/New_York")).format(fmt));

输出将是:

2017 年 8 月 23 日星期三 08:43:52 EDT-0400

请注意,格式化程序使用所有可选部分来打印日期(因此它会同时打印区域 ID EDT 和偏移量 -0400)。如果您只想打印其中一个,则必须创建另一个格式化程序(或只使用RFC_1123_DATE_TIME)。


除了appendZoneTextappendOffset,您还可以使用:

.appendPattern("[z][x]")

注意可选部分(由[] 分隔)。这将解析区域 ID (z) 偏移量 (x)。看看docs for more details关于模式的信息。

唯一的区别是使用这种模式你不能使用首选区域集。

为了格式化,这也将打印两个字段(因此输出将类似于EDT-0400)。

【讨论】:

    猜你喜欢
    • 2011-05-28
    • 2022-07-07
    • 2017-05-30
    • 2020-05-01
    • 2016-06-23
    • 1970-01-01
    • 2022-12-11
    • 2022-12-10
    • 1970-01-01
    相关资源
    最近更新 更多