如何使用 DateTimeFormatter 解析非标准月份名称
How to parse non-standard month names with DateTimeFormatter
我需要解析以下形式的(德语)日期:
10. Jan. 18:14
8. Feb. 19:02
1. Mär. 19:40
4. Apr. 18:55
2. Mai 21:55
5. Juni 08:25
5. Juli 20:09
1. Aug. 13:42
[...]
如您所见,如果月份超过 4 个字符,月份名称将被截断。更奇怪的是,不要问我为什么,三月被缩短为 Mär.
虽然全名是 März
。我如何用 java.time
解析它?
(日期格式基于创建日期列表的 android 设备的本地化。但是,我没有在 Android 上解析它)
我的方法是创建一个像这样的 DateTimeFormatter
:
DateTimeFormatter.ofPattern("d. MMMM HH:mm").withLocale(Locale.GERMAN);
// or
DateTimeFormatter.ofPattern("d. MMMMM HH:mm").withLocale(Locale.GERMAN);
但是 MMMM
和 MMMMM
模式都不适合缩短的日期。当然,我可以使用以下模式 d. MMM. HH:mm
来匹配缩短的月份,但是我无法匹配 3 和 4 个字符的月份。我知道我可以有两个格式化程序 (MMM. and MMMMM
),但我宁愿有一个解决方案,我只有一个格式化程序,可能还有一个自定义语言环境或类似的东西。
您可以用正则表达式替换月份部分,这样在使用 "d. MMM HH:mm"
解析之前它的长度始终为 3 个字符
text = text.replaceFirst("(\S+\s\S{3})\S", "")
正则表达式部分的解释:找到 1 个或多个非空白 (\S+) 后跟 1 个空白 (\s) 后跟三个非空白 (\S{3}) 后跟一个非空白,并将其替换为第一个括号内的部分 ($1)
10. Jan. 18:14
将变为 10. Jan 18:14
并且
5. Juni 08:25
将变为 5. Jun 08:25
正如所指出的那样,使用标准且一致的格式会更容易 - 这里您混合了长月份和短月份名称。
一个选项(不使用 DateTimeFormatterBuilder
)是分别处理这两种情况:
private static final DateTimeFormatter SHORT_MONTH = DateTimeFormatter.ofPattern("d. MMM. HH:ss", Locale.GERMAN);
private static final DateTimeFormatter LONG_MONTH = DateTimeFormatter.ofPattern("d. MMMM HH:ss", Locale.GERMAN);
private static TemporalAccessor parse(String s) {
try {
return SHORT_MONTH.parse(s);
} catch (DateTimeParseException e) {
return LONG_MONTH.parse(s);
}
}
您可以使用 DateTimeFormatterBuilder
:
private static final DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendOptional(DateTimeFormatter.ofPattern("d. MMM. HH:ss"))
.appendOptional(DateTimeFormatter.ofPattern("d. MMMM HH:ss"))
.toFormatter(Locale.GERMAN);
运行 就这个:
Stream.of(("10. Jan. 18:14\n" +
"8. Feb. 19:02\n" +
"1. Mär. 19:40\n" +
"4. Apr. 18:55\n" +
"2. Mai 21:55\n" +
"5. Juni 08:25\n" +
"5. Juli 20:09\n" +
"1. Aug. 13:42").split("\n"))
.map(formatter::parse)
.forEach(System.out::println);
你得到:
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=10, MonthOfYear=1, MilliOfSecond=0, SecondOfMinute=14, HourOfDay=18},ISO
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=8, MonthOfYear=2, MilliOfSecond=0, SecondOfMinute=2, HourOfDay=19},ISO
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=1, MonthOfYear=3, MilliOfSecond=0, SecondOfMinute=40, HourOfDay=19},ISO
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=4, MonthOfYear=4, MilliOfSecond=0, SecondOfMinute=55, HourOfDay=18},ISO
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=2, MonthOfYear=5, MilliOfSecond=0, SecondOfMinute=55, HourOfDay=21},ISO
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=5, MonthOfYear=6, MilliOfSecond=0, SecondOfMinute=25, HourOfDay=8},ISO
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=5, MonthOfYear=7, MilliOfSecond=0, SecondOfMinute=9, HourOfDay=20},ISO
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=1, MonthOfYear=8, MilliOfSecond=0, SecondOfMinute=42, HourOfDay=13},ISO
问题的答案是DateTimeFormatterBuilder
class and the appendText(TemporalField, Map)
方法。它允许任何文本在格式化或解析时关联一个值,有效且优雅地解决了这个问题:
Map<Long, String> monthNameMap = new HashMap<>();
monthNameMap.put(1L, "Jan.");
monthNameMap.put(2L, "Feb.");
monthNameMap.put(3L, "Mar.");
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
.appendPattern("d. ")
.appendText(ChronoField.MONTH_OF_YEAR, monthNameMap)
.appendPattern(" HH:mm")
.parseDefaulting(ChronoField.YEAR, 2016)
.toFormatter();
System.out.println(LocalDateTime.parse("10. Jan. 18:14", fmt));
System.out.println(LocalDateTime.parse("8. Feb. 19:02", fmt));
一些注意事项:
monthNameMap
必须填写全部 12 个月
- 格式化程序通常应分配给静态最终常量,而不是一直创建
- 添加了
parseDefaulting(YEAR, 2016)
,可以直接使用LocalDateTime.parse(String, DateTimeFormatter)
。没有它,就不会有年份,因此只能解析 TemporalAccessor
(年份必须是闰年,以防解析 2 月 29 日)
我需要解析以下形式的(德语)日期:
10. Jan. 18:14
8. Feb. 19:02
1. Mär. 19:40
4. Apr. 18:55
2. Mai 21:55
5. Juni 08:25
5. Juli 20:09
1. Aug. 13:42
[...]
如您所见,如果月份超过 4 个字符,月份名称将被截断。更奇怪的是,不要问我为什么,三月被缩短为 Mär.
虽然全名是 März
。我如何用 java.time
解析它?
(日期格式基于创建日期列表的 android 设备的本地化。但是,我没有在 Android 上解析它)
我的方法是创建一个像这样的 DateTimeFormatter
:
DateTimeFormatter.ofPattern("d. MMMM HH:mm").withLocale(Locale.GERMAN);
// or
DateTimeFormatter.ofPattern("d. MMMMM HH:mm").withLocale(Locale.GERMAN);
但是 MMMM
和 MMMMM
模式都不适合缩短的日期。当然,我可以使用以下模式 d. MMM. HH:mm
来匹配缩短的月份,但是我无法匹配 3 和 4 个字符的月份。我知道我可以有两个格式化程序 (MMM. and MMMMM
),但我宁愿有一个解决方案,我只有一个格式化程序,可能还有一个自定义语言环境或类似的东西。
您可以用正则表达式替换月份部分,这样在使用 "d. MMM HH:mm"
解析之前它的长度始终为 3 个字符text = text.replaceFirst("(\S+\s\S{3})\S", "")
正则表达式部分的解释:找到 1 个或多个非空白 (\S+) 后跟 1 个空白 (\s) 后跟三个非空白 (\S{3}) 后跟一个非空白,并将其替换为第一个括号内的部分 ($1)
10. Jan. 18:14
将变为 10. Jan 18:14
并且
5. Juni 08:25
将变为 5. Jun 08:25
正如所指出的那样,使用标准且一致的格式会更容易 - 这里您混合了长月份和短月份名称。
一个选项(不使用 DateTimeFormatterBuilder
)是分别处理这两种情况:
private static final DateTimeFormatter SHORT_MONTH = DateTimeFormatter.ofPattern("d. MMM. HH:ss", Locale.GERMAN);
private static final DateTimeFormatter LONG_MONTH = DateTimeFormatter.ofPattern("d. MMMM HH:ss", Locale.GERMAN);
private static TemporalAccessor parse(String s) {
try {
return SHORT_MONTH.parse(s);
} catch (DateTimeParseException e) {
return LONG_MONTH.parse(s);
}
}
您可以使用 DateTimeFormatterBuilder
:
private static final DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendOptional(DateTimeFormatter.ofPattern("d. MMM. HH:ss"))
.appendOptional(DateTimeFormatter.ofPattern("d. MMMM HH:ss"))
.toFormatter(Locale.GERMAN);
运行 就这个:
Stream.of(("10. Jan. 18:14\n" +
"8. Feb. 19:02\n" +
"1. Mär. 19:40\n" +
"4. Apr. 18:55\n" +
"2. Mai 21:55\n" +
"5. Juni 08:25\n" +
"5. Juli 20:09\n" +
"1. Aug. 13:42").split("\n"))
.map(formatter::parse)
.forEach(System.out::println);
你得到:
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=10, MonthOfYear=1, MilliOfSecond=0, SecondOfMinute=14, HourOfDay=18},ISO
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=8, MonthOfYear=2, MilliOfSecond=0, SecondOfMinute=2, HourOfDay=19},ISO
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=1, MonthOfYear=3, MilliOfSecond=0, SecondOfMinute=40, HourOfDay=19},ISO
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=4, MonthOfYear=4, MilliOfSecond=0, SecondOfMinute=55, HourOfDay=18},ISO
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=2, MonthOfYear=5, MilliOfSecond=0, SecondOfMinute=55, HourOfDay=21},ISO
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=5, MonthOfYear=6, MilliOfSecond=0, SecondOfMinute=25, HourOfDay=8},ISO
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=5, MonthOfYear=7, MilliOfSecond=0, SecondOfMinute=9, HourOfDay=20},ISO
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=1, MonthOfYear=8, MilliOfSecond=0, SecondOfMinute=42, HourOfDay=13},ISO
问题的答案是DateTimeFormatterBuilder
class and the appendText(TemporalField, Map)
方法。它允许任何文本在格式化或解析时关联一个值,有效且优雅地解决了这个问题:
Map<Long, String> monthNameMap = new HashMap<>();
monthNameMap.put(1L, "Jan.");
monthNameMap.put(2L, "Feb.");
monthNameMap.put(3L, "Mar.");
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
.appendPattern("d. ")
.appendText(ChronoField.MONTH_OF_YEAR, monthNameMap)
.appendPattern(" HH:mm")
.parseDefaulting(ChronoField.YEAR, 2016)
.toFormatter();
System.out.println(LocalDateTime.parse("10. Jan. 18:14", fmt));
System.out.println(LocalDateTime.parse("8. Feb. 19:02", fmt));
一些注意事项:
monthNameMap
必须填写全部 12 个月- 格式化程序通常应分配给静态最终常量,而不是一直创建
- 添加了
parseDefaulting(YEAR, 2016)
,可以直接使用LocalDateTime.parse(String, DateTimeFormatter)
。没有它,就不会有年份,因此只能解析TemporalAccessor
(年份必须是闰年,以防解析 2 月 29 日)