如果删除了 space,DateTimeFormatter 解析带有可选时间部分的字符串将失败

DateTimeFormatter parsing string with optional time part fails if space removed

之后,尝试使用模式 yyyyMMdd[HHmmss] 解析字符串 20120301122133 时出现错误。奇怪的是,使用模式 yyyyMMdd[ HHmmss] 解析 20120301 122133 效果很好。

所以这段代码工作正常

LocalDateTime.parse(
     "19940513 230000", 
     new DateTimeFormatterBuilder()
        .appendPattern("yyyyMMdd[ HHmmss]")
        .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
        .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)   
        .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
        .toFormatter()
)

而这个失败了

LocalDateTime.parse(
    "19940513230000", 
    new DateTimeFormatterBuilder()
        .appendPattern("yyyyMMdd[HHmmss]")
        .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) 
        .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)         
        .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
        .toFormatter()
)

我应该如何解析格式为 yyyyMMdd[HHmmss] 的字符串,即使用 java 8 时间 API 的格式为 yyyyMMddHHmmss 的可选时间部分?

解析模式是一个可配置选项,因此仅在运行时已知。所以我不能用硬编码的 DateTimeFormatterBuilder 调用替换字符串模式。

问题在于模式表达式"yyyy"并不表示固定的four-digit-year而是至少4位(或更多,所以解析器是贪婪的)。但您可以执行以下操作:

LocalDateTime ldt =
    LocalDateTime.parse(
        "19940513230000",
        new DateTimeFormatterBuilder()
            .appendValue(ChronoField.YEAR, 4)
            .appendPattern("MMdd[HHmmss]")
            .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
            .toFormatter());
System.out.println(ldt); // 1994-05-13T23:00
    System.out.println(LocalDateTime.parse(
            "19940513230000",
            new DateTimeFormatterBuilder()
                .appendPattern("[uuuuMMddHHmmss][uuuuMMdd]")
                .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) 
                .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)         
                .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
                .toFormatter()
        ));

这会打印:

1994-05-13T23:00

如果我尝试解析仅包含日期 "19940513" 的字符串,我会得到

1994-05-13T00:00

它也适用于 yyyy 而不是 uuuu。假设你所有的年份都在这个时代(第 1 年或更晚),你使用哪个时代没有任何区别。通常 uuuu 也会接受负数年份,0 表示公元前 1 年,-1 表示公元前 2 年,依此类推。

出现这种情况是因为年份没有固定值,通常限制为 19 位数字。

如果您创建以下格式化程序(不需要分钟和秒)并使用 toString() 方法:

new DateTimeFormatterBuilder()
        .appendPattern("yyyyMMdd[ HHmmss]")
        .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
        .toFormatter()
        .toString();

您可以看到以下内容:

"Value(YearOfEra,4,19,EXCEEDS_PAD)Value(MonthOfYear,2)Value(DayOfMonth,2)[' 'Value(HourOfDay,2)Value(MinuteOfHour,2)Value(SecondOfMinute,2)]java.time.format.DateTimeFormatterBuilder$DefaultValueParser@32eff876"

在这里你可以看到 YearOfEra 的最小宽度为 4,最大宽度为 19。

您可以使用 Meno 或 Ole 的答案之一。

然而,如果您需要接收格式和日期作为参数,并且希望能够以更简单的方式指定日期格式(例如 yyyyMMdd[HHmmSS] 而不是 [...][...]),您可以预处理其中一个值(日期格式)。

您可以创建 'dinamically' 格式化程序,因此每个 yyyy 仅被解释为 4 位数年份。

自定义格式生成器可能类似于(可以改进):

public static DateTimeFormatter createFixed4DigitYearFormatter(String format) {
    DateTimeFormatterBuilder formatBuilder = new DateTimeFormatterBuilder();
    Arrays.stream(format.split("yyyy", -1))
            .flatMap(cur -> Stream.of("yyyy", cur)).skip(1)
            .filter(str -> !str.isEmpty())
            .forEach(pattern -> {
                if ("yyyy".equals(pattern)) formatBuilder
                        .appendValue(ChronoField.YEAR_OF_ERA, 4);
                else formatBuilder.appendPattern(pattern);
            });
    return formatBuilder.parseDefaulting(ChronoField.HOUR_OF_DAY, 0).toFormatter();
}

此格式化程序按字符串 "yyyy" 拆分格式,然后将每个非 "yyyy" 添加为模式(使用 appendPattern(..))并将 "yyyy" 添加为YEAR_OF_ERA 类型的值,具有固定的 4 位数字(appendValue(..))。

终于可以使用多种格式的格式化程序了:

System.out.println(LocalDateTime.parse("19940513230000",
        createFixed4DigitYearFormatter("yyyyMMdd[HHmmss]"))); // 1994-05-13T23:00
System.out.println(LocalDateTime.parse("19940513",
        createFixed4DigitYearFormatter("yyyyMMdd[HHmmss]"))); // 1994-05-13T00:00
System.out.println(LocalDateTime.parse("1994-05-13 23:00:00",
        createFixed4DigitYearFormatter("yyyy-MM-dd[ HH:mm:ss]"))); // 1994-05-13T23:00
System.out.println(LocalDateTime.parse("1994-05-13",
        createFixed4DigitYearFormatter("yyyy-MM-dd[ HH:mm:ss]"))); // 1994-05-13T00:00