DateTimeFormatter 不解析自定义日期格式
DateTimeFormatter doesn't parse custom date format
我对 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
。
您必须将格式更改为:
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)
returns 一个 TemporalAccessor
对象。如果要创建特定类型,最好使用 class 各自的 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:00
是zzz
的结果,第二个+03:00
是xxx
的结果。如果你不想要这个,我建议使用 2 个不同的 DateTimeFormatter
(一个用于解析,另一个用于格式化)。
或者("uglier" 方法),使用 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 个字母的缩写(如 CDT
或 PST
),因为它们是 ambiguous and not standard。 CDT
可以是 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_Paulo
或 Europe/Berlin
)。根据该列表,有 40 多个时区使用(或过去曾在某处使用过)CDT
缩写。
CDT
适用于这种情况,因为一些缩写配置了默认值,可能是由于追溯兼容性原因,但您不应该在所有情况下都依赖它们。
为确保您的时区缩写始终有效(以防万一您无法避免使用它们),您可以创建一个使用一组首选时区的格式化程序。在这种情况下,我使用 America/Chicago
(因此,CST
或 CDT
将被解析为芝加哥的时区):
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
作为默认时区。根据您的用例,您可以在集合中添加任意数量的区域。
提醒一下,如上所述,此格式化程序在输出方面存在相同的问题(它打印所有可选部分)。
tl;博士
OffsetDateTime.parse(
"2017-07-05T12:28:36.4TGMT+03:00".replace( "TGMT" , "" )
)
详情
您的格式很奇怪,像是对标准 ISO 8601 格式的奇怪误解或损坏。
如果您所有输入的最后一部分都有“TGMT”,请将其删除以符合 ISO 8601。
java.time 类 在 parsing/generating 字符串时默认使用标准格式。因此无需定义格式化模式。
OffsetDateTime odt = OffsetDateTime.parse( "2017-07-05T12:28:36.4TGMT+03:00".replace( "TGMT" , "" ) ) ;
并且从不使用3-4个字母的伪时区,如CMT
、EST
和IST
。这些 不是 实际时区,不是标准化的,甚至不是唯一的(!)。实时时区名称的格式为 continent/region
,例如 America/Montreal
或 Pacific/Auckland
.
我对 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
。
您必须将格式更改为:
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)
returns 一个 TemporalAccessor
对象。如果要创建特定类型,最好使用 class 各自的 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:00
是zzz
的结果,第二个+03:00
是xxx
的结果。如果你不想要这个,我建议使用 2 个不同的 DateTimeFormatter
(一个用于解析,另一个用于格式化)。
或者("uglier" 方法),使用 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 个字母的缩写(如 CDT
或 PST
),因为它们是 ambiguous and not standard。 CDT
可以是 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_Paulo
或 Europe/Berlin
)。根据该列表,有 40 多个时区使用(或过去曾在某处使用过)CDT
缩写。
CDT
适用于这种情况,因为一些缩写配置了默认值,可能是由于追溯兼容性原因,但您不应该在所有情况下都依赖它们。
为确保您的时区缩写始终有效(以防万一您无法避免使用它们),您可以创建一个使用一组首选时区的格式化程序。在这种情况下,我使用 America/Chicago
(因此,CST
或 CDT
将被解析为芝加哥的时区):
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
作为默认时区。根据您的用例,您可以在集合中添加任意数量的区域。
提醒一下,如上所述,此格式化程序在输出方面存在相同的问题(它打印所有可选部分)。
tl;博士
OffsetDateTime.parse(
"2017-07-05T12:28:36.4TGMT+03:00".replace( "TGMT" , "" )
)
详情
您的格式很奇怪,像是对标准 ISO 8601 格式的奇怪误解或损坏。
如果您所有输入的最后一部分都有“TGMT”,请将其删除以符合 ISO 8601。
java.time 类 在 parsing/generating 字符串时默认使用标准格式。因此无需定义格式化模式。
OffsetDateTime odt = OffsetDateTime.parse( "2017-07-05T12:28:36.4TGMT+03:00".replace( "TGMT" , "" ) ) ;
并且从不使用3-4个字母的伪时区,如CMT
、EST
和IST
。这些 不是 实际时区,不是标准化的,甚至不是唯一的(!)。实时时区名称的格式为 continent/region
,例如 America/Montreal
或 Pacific/Auckland
.