【问题标题】:How to parse non-standard month names with DateTimeFormatter如何使用 DateTimeFormatter 解析非标准月份名称
【发布时间】:2015-08-29 05:28:09
【问题描述】:

我需要解析以下形式的(德语)日期:

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ärz,但三月份缩短为Mär.。我如何用java.time 解析这个? (日期的格式是基于创建日期列表的 android 设备的本地化。但是,我不会在 Android 上解析它)

我的方法是像这样创建DateTimeFormatter

DateTimeFormatter.ofPattern("d. MMMM HH:mm").withLocale(Locale.GERMAN);
// or
DateTimeFormatter.ofPattern("d. MMMMM HH:mm").withLocale(Locale.GERMAN);

但是MMMMMMMMM 模式都不适合缩短的日期。当然,我可以使用以下模式d. MMM. HH:mm 来匹配缩短的月份,但是我无法匹配 3 和 4 个字符的月份。我知道我可以有两个格式化程序 (MMM. and MMMMM),但我宁愿有一个解决方案,我只有一个格式化程序,可能还有一个自定义语言环境或类似的东西。

【问题讨论】:

  • 如果您可以控制该 Android 应用,最好让它以标准格式发送日期。本地化格式实际上应该只用于用户交互,而不是用于数据交换。如果没有,我认为您可能应该在解析之前删除空格之前的字符。
  • 不幸的是,我无法控制 Android 应用程序,否则我会以结构化形式传输数据并使用 unix 时间戳记日期 :-)。只是删除空格之前的字符,即点,并没有帮助,因为它仍然让我在缩短的月份名称和全名之间混合。
  • 不,如果您删除空格前的字符,它还会将 Juli 和 Juni 缩短为 Jul 和 Jun。
  • 然后Mai变成了Ma。 März 的正确短月是 Mrz。因为除了德语还有其他本地化版本,我更喜欢一种不需要针对特殊情况更改代码的方法。
  • 啊,我错过了。在这种情况下,我认为您最好的选择是创建一种方法,在解析之前将非标准月份名称简单地替换为标准月份名称。

标签: java datetime localization java-8 java-time


【解决方案1】:

问题的答案是DateTimeFormatterBuilder 类和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 日)

【讨论】:

  • 这正是我想要的,谢谢。这样,我可以将地图的内容存储在其他地方,并且可以轻松添加偏离标准语言环境的新案例。
【解决方案2】:

你可以使用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

【讨论】:

  • 优雅。我相信DateTimeFormatter.ofPattern("d. [MMM.][MMMM] HH:ss", Locale.GERMAN) 会做。方括号表示可选部分。
【解决方案3】:

正如所指出的,使用标准和一致的格式会更容易 - 在这里您混合了长月份和短月份名称。

一种选择(不使用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。您将如何使用它来实现这一目标?
【解决方案4】:

您可以使用正则表达式替换月份部分,使其始终为 3 个字符长度,然后再使用“d.MMM HH:mm”进行解析

text = text.replaceFirst("(\\S+\\s\\S{3})\\S", "$1")

正则表达式部分的解释:查找 1 个或多个非空白 (\S+) 后跟 1 个空白 (\s) 后跟三个非空白 (\S{3}) 后跟一个非空白,然后替换它与第一个括号内的部分 ($1)

10. Jan. 18:14 将变为 10. Jan 18:145. Juni 08:25 将变为 5. Jun 08:25

【讨论】:

    猜你喜欢
    • 2018-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-07
    • 1970-01-01
    相关资源
    最近更新 更多