Lenient Java 8 日期解析

Lenient Java 8 Date parsing

我想用 LocalDateTime 解析“2015-10-01”。我要做的是

LocalDate localDate = LocalDate.parse('2015-10-01');
LocalDateTime localDateTime = localDateTime.of(localDate, LocalTime.MIN);

但我想像

一样一次性解析它
// throws DateTimeParseException
LocalDateTime date = LocalDateTime.parse('2015-10-01', DateTimeFormatter.ISO_LOCAL_DATE);

字符串的微小差异也会引发异常。

// throws DateTimeParseException
LocalDate localDate = LocalDate.parse("2015-9-5", DateTimeFormatter.ISO_LOCAL_DATE);

我可以使用 Java 8 个日期 API 轻松地解析日期字符串吗?

解析日期和时间

要从字符串创建 LocalDateTime 对象,您可以使用静态 LocalDateTime.parse() 方法。它需要一个字符串和一个 DateTimeFormatter 作为参数。 DateTimeFormatter 用于指定 date/time 模式。

String str = "1986-04-08 12:00";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.parse(str, formatter);

格式化日期和时间

要从 LocalDateTime 对象中创建格式化字符串,您可以使用 format() 方法。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.of(1986, Month.APRIL, 8, 12, 30);
String formattedDateTime = dateTime.format(formatter); // "1986-04-08 12:30"

请注意,DateTimeFormatter 中有一些常用的 date/time 格式预定义为常量。例如:使用 DateTimeFormatter.ISO_DATE_TIME 格式化上面的 LocalDateTime 实例将导致字符串“1986-04-08T12:30:00”。

parse() 和 format() 方法可用于所有 date/time 相关对象(例如 LocalDate 或 ZonedDateTime)

如果您想将日期字符串解析为 "2015-10-01""2015-9-5"LocalDateTime 对象,您可以使用 DateTimeFormatterBuilder 构建自己的 DateTimeFormatter

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
                                        .appendPattern("yyyy")
                                        .appendLiteral('-')
                                        .appendValue(MONTH_OF_YEAR)
                                        .appendLiteral('-')
                                        .appendValue(DAY_OF_MONTH)
                                        .parseDefaulting(HOUR_OF_DAY, HOUR_OF_DAY.range().getMinimum())
                                        .parseDefaulting(MINUTE_OF_HOUR, MINUTE_OF_HOUR.range().getMinimum())
                                        .parseDefaulting(SECOND_OF_MINUTE, SECOND_OF_MINUTE.range().getMinimum())
                                        .parseDefaulting(NANO_OF_SECOND, NANO_OF_SECOND.range().getMinimum())
                                        .toFormatter();
System.out.println(LocalDateTime.parse("2015-9-5", formatter));
System.out.println(LocalDateTime.parse("2015-10-01", formatter));

每个字段的可变长度由对 appendValue(field) 的调用处理。引用 Javadoc:

The parser for a variable width value such as this normally behaves greedily, requiring one digit, but accepting as many digits as possible.

这意味着它将能够解析格式为 1 或 2 位数字的月份和日期。

要构建一个LocalDateTime,我们还需要为这个构建器提供一个LocalTime。这是通过使用该字段的 parseDefaulting(field, value) for each field of a LocalTime. This method takes a field and a default value for that field if it is not present in the String to parse. Since, in our case, the time information will not be present in the String, the default values will be chosen, i.e. the minimum value for the range of valid values for that field (it is obtained by calling getMinimum to the ValueRange 来完成的;也许我们也可以在这里硬编码 0)。


如果要解析的字符串可能包含时间信息,我们可以使用 DateTimeFormatter 的可选部分,如下所示:

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
                                        .appendPattern("yyyy")
                                        .appendLiteral('-')
                                        .appendValue(MONTH_OF_YEAR)
                                        .appendLiteral('-')
                                        .appendValue(DAY_OF_MONTH)
                                        .appendPattern("[ HH:mm]") // optional sections are surrounded by []
                                        .parseDefaulting(HOUR_OF_DAY, HOUR_OF_DAY.range().getMinimum())
                                        .parseDefaulting(MINUTE_OF_HOUR, MINUTE_OF_HOUR.range().getMinimum())
                                        .parseDefaulting(SECOND_OF_MINUTE, SECOND_OF_MINUTE.range().getMinimum())
                                        .parseDefaulting(NANO_OF_SECOND, NANO_OF_SECOND.range().getMinimum())
                                        .toFormatter();
System.out.println(LocalDateTime.parse("2015-9-5", formatter));
System.out.println(LocalDateTime.parse("2015-10-01", formatter));
System.out.println(LocalDateTime.parse("2015-1-1 10:10", formatter));

你可以试试

    private static final DateTimeFormatter formatter =
                DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss").withResolverStyle(ResolverStyle.LENIENT);

针对您的特定用例的一个简单解决方案是定义您自己的格式。在此示例中,即使我在我的模式中为月份指定了一个 M,并为一天指定了一个 d,它仍然以相同的方式解析这两个示例。

@Test
public void parsing_singleDigitDates_andDoubleDigitDates_areEqual()
{
    DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-M-d");

    LocalDate dt1 = LocalDate.parse("2015-09-05", fmt);
    LocalDate dt2 = LocalDate.parse("2015-9-5", fmt);

    Assert.assertEquals(dt1, dt2);
}

在看到 DateTimeFormatterBuilder 要求您编写代码汤以解决同样的问题后,我决定写这个答案。