DateTimeFormatter不解析自定义日期格式

时间:2017-07-05 11:22:07

标签: java datetime java-time datetime-parsing

我遇到了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

2 个答案:

答案 0 :(得分:3)

您必须将格式更改为:

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),此格式化程序的工作方式与上述相同,并且当America/Chicago位于输入中时,使用CDT作为默认时区。根据您的使用情况,您可以在集合中添加所需的区域。

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

答案 1 :(得分: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