【问题标题】:DateTimeFormatter doesn't parse custom date formatDateTimeFormatter 不解析自定义日期格式
【发布时间】:2017-07-05 11:22:06
【问题描述】:

我对 java DataTimeFormmater 有疑问。 我觉得我错过了一些东西,但无法弄清楚到底是什么。

String format = "yyyy-MM-dd'T'HH:mm:ss[.S]'T'zxxx";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);

String date = "2017-07-05T12:28:36.4TGMT+03:00";

System.out.println(formatter.format(ZonedDateTime.now()));
System.out.println(formatter.parse(date));

上面的代码生成当前ZonedDateTime 的字符串,并尝试使用相同的日期格式化程序解析日期时间字符串。 结果成功生成2017-07-05T06:07:51.0TCDT-05:00,但解析失败2017-07-05T12:28:36.4TGMT+03:00

我的目标是解析2017-07-05T12:28:36.4TGMT+03:00 并提出适当的DateTimeFormatter

【问题讨论】:

  • 这是一种奇怪的格式。尽可能坚持使用标准的ISO 8601 格式。

标签: java datetime java-time datetime-parsing


【解决方案1】:

您必须将格式更改为:

String format = "yyyy-MM-dd'T'HH:mm:ss[.S]'T'[zzz][xxx]";

[zzz][xxx] 都在可选部分中,因为zzz 可以解析整个GMT+03:00 部分或仅解析区域短名称(例如CDT),而xxx 仅解析偏移量部分(例如-05:00 - 因此如果找到GMT+03:00 则不需要)。

只是提醒formatter.parse(date) 返回一个TemporalAccessor 对象。如果要创建特定类型,最好使用类各自的parse方法:

System.out.println(ZonedDateTime.parse(date, formatter)); // 2017-07-05T12:28:36.400+03:00[GMT+03:00]

PS:此格式化程序的唯一问题是,在格式化时,它会打印所有可选部分。所以,如果你这样做:

String date = "2017-07-05T12:28:36.4TGMT+03:00";
ZonedDateTime z  = ZonedDateTime.parse(date, formatter);
System.out.println(formatter.format(z));

输出将是:

2017-07-05T12:28:36.4TGMT+03:00+03:00

这是因为GMT+03:00zzz 的结果,而第二个+03:00xxx 的结果。如果您不希望这样,我建议使用 2 个不同的 DateTimeFormatter(一个用于解析,另一个用于格式化)。

或者(一种“更丑”的方法),使用 2 种不同的格式化程序:

DateTimeFormatter noGMT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.S]'T'zzzxxx");
DateTimeFormatter gmt = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.S]'TGMT'xxx");

然后,您尝试使用第一个进行解析 - 如果遇到异常,请尝试使用第二个(或者检查您的输入是否包含 GMT 以了解要使用哪个)。

我个人不喜欢这样,因为GMT 是区域名称的一部分,不应被视为文字。但最后,你会得到一个带有正确偏移量的ZonedDateTime,所以我不确定这种方法有多错误。


时区缩写

请注意,您应该(尽可能)避免使用 3 个字母的缩写(例如 CDTPST),因为它们是 ambiguous and not standardCDT 可以同时是 Central Daylight Time (UTC-05:00)、Cuba Daylight Time (UTC-04:00) 甚至是 China Daylight Time (UTC+09:00)。

如果可能,最好使用IANA timezones names(始终采用Continent/City 的格式,例如America/Sao_PauloEurope/Berlin)。根据该列表,有 40 多个时区使用(或在过去某处使用)CDT 缩写。

CDT 适用于这种情况,因为某些缩写配置了默认值,可能是出于复古兼容性的原因,但您不应该在所有情况下都依赖它们。

为确保您的时区缩写始终有效(以防万一您无法避免使用它们),您可以创建一个使用一组首选时区的格式化程序。在这种情况下,我使用的是 America/Chicago(因此,CSTCDT 将被解析为芝加哥的时区):

Set<ZoneId> preferedZones = new HashSet<>();
preferedZones.add(ZoneId.of("America/Chicago"));
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
    // append first part of pattern (before timezone)
    .appendPattern("yyyy-MM-dd'T'HH:mm:ss[.S]'T'")
    // append zone name, use prefered zones (optional)
    .optionalStart().appendZoneText(TextStyle.SHORT, preferedZones).optionalEnd()
    // offset (optional)
    .appendPattern("[xxx]")
    // create formatter
    .toFormatter();

此格式化程序的工作方式与上述相同,对于您的输入(有和没有GMT),当CDT 在输入中时,使用America/Chicago 作为默认时区。您可以根据自己的用例在集合中添加任意数量的区域。

只是提醒这个格式化程序在输出方面存在相同的问题(它打印所有可选部分),如上所述。

【讨论】:

  • 也可以使用 OOOO 表示时区,但在 Java 8 中解析存在错误,在 Java 9 中已更正:JDK-8154050
  • @CarlosHeuberger 确实!我试过OOOO,但它只适用于格式化。
  • 它本来是正确的使用但代码是错误的拳头+第二行int pos = position; int end = pos + text.length(); end大于字符串长度最终导致StringIndexOutOfBoundsException(位置是开始寻找时区的位置)
  • 我没有看到做 zzz 比 z 有任何好处的情况。 z可以解析像GMT+03:00这样的区域
【解决方案2】:

tl;博士

OffsetDateTime.parse(
    "2017-07-05T12:28:36.4TGMT+03:00".replace( "TGMT" , "" ) 
)

详情

您的格式很奇怪,就像对标准 ISO 8601 格式的奇怪误解或损坏一样。

如果您所有输入的最后一部分都有“TGMT”,请将其去掉以符合 ISO 8601。

java.time 类在解析/生成字符串时默认使用标准格式。所以不需要定义格式化模式。

OffsetDateTime odt = OffsetDateTime.parse( "2017-07-05T12:28:36.4TGMT+03:00".replace( "TGMT" , "" ) ) ;

并且从不使用 3-4 个字母的伪时区,例如 CMTESTIST。这些是不是实际的时区,不是标准化的,甚至不是唯一的(!)。实时时区名称采用continent/region 格式,如America/MontrealPacific/Auckland

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多