有没有办法在构造后确定 DateTimeFormatter 是仅日期还是仅时间?

Is there a way to determine if a DateTimeFormatter is date only or time only after construction?

使用Java 8 的新日期时间库,将字符串解析为日期的方法是使用DateTimeFormatterLocalDateLocalTimeLocalDateTime 都有一个接受字符串和格式化程序的静态解析方法。一个潜在的陷阱是,如果您的 DateTimeFormat 不包含时间部分(或 DateTime,日期部分),即使您的模式匹配,您最终也会收到解析错误。

例子

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYY-MM-DD");
LocalDateTime dt = LocalDateTime.parse("2016-01-11", formatter);

这将抛出 DateTimeParseException(如讨论的 here)和消息

java.time.format.DateTimeParseException:
Text '2016-01-11' could not be parsed:
Unable to obtain LocalDateTime from TemporalAccessor

这是一个有点无用的错误消息,因为文本可以被解析,因为它与模式匹配。相反,该错误与它无法创建 LocalDateTime 的时间部分这一事实有关。将代码重构为以下工作:

LocalDateTime ldt = LocalDate.parse("2016-01-11", formatter).atStartOfDay());

我的问题是,假设您有这样的通用方法

 public static LocalDateTime getDate(String s, DateTimeFormatter format) {
  ...
  }

有没有办法通过应用一些默认逻辑来确定您需要调用哪个静态解析方法来将字符串强制转换为 LocalDateTime?例如如果日期只用午夜,如果时间只用今天等

我觉得你的问题反过来了。您不想确定格式化程序是仅使用日期还是 date/time。您可以根据您应该解析的内容以及您打算将结果存储到的内容来创建格式化程序。显然,如果你创建了一个不处理时间部分的格式化程序,用它来解析成 LocalDateTime 是一种误解。

如果您需要解析可以以 "yyyy-MM-dd""yyyy-MM-dd HH:mm:ss"(带有时间部分)等两种格式到达的日期,则解析不能确定它应该做什么,但是在没有时间的情况下,由格式化程序提供默认值。

在Java时,这是通过可选部分和默认值完成的。例如,模式 "yyyy-MM-dd[ HH:mm:ss]" 将能够解析日期字符串(如 "2016-01-11")和 date/time 字符串(如 "2016-01-11 20:10:10")。如果将其存储到 LocalDateTime 中,则需要提供默认值以防没有时间组件。这是通过 parseDefaulting(field, value) 完成的:如果尚未设置,这将告诉格式化程序 return 该计时字段的给定默认值。

以下代码创建了这样一个格式化程序并将时间部分默认为午夜。

public static void main(String[] args) {
    DateTimeFormatter formatter = 
            new DateTimeFormatterBuilder().appendPattern("yyyy-MM-dd[ HH:mm:ss]")
                                          .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
                                          .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
                                          .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
                                          .toFormatter();

    LocalDateTime dt1 = LocalDateTime.parse("2016-01-11", formatter);
    LocalDateTime dt2 = LocalDateTime.parse("2016-01-11 20:10:10", formatter);
}

当然可以将此逻辑扩展为仅解析时间字符串并将日期组件默认为当前日期。

与标题中的主要问题相关DateTimeFormatter只是returns一般TemporalAccessor而不是具体的期望结果类型.相反,用户应该间接使用解析器的结果来为具体类型提供静态 from(parsed) 方法。因此,您会在这些方法的文档中找到成功解析所需的字段类型:

LocalDate requires EPOCH_DAY

LocalTime requires NANO_OF_DAY

因此,查询这些字段的原始解析数据就足以决定格式化程序是 date-like、time-like 还是组合。

DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd", Locale.ENGLISH);
TemporalAccessor tacc = dateFormatter.parse("2015-07-24");
System.out.println("date-like: " + tacc.isSupported(ChronoField.EPOCH_DAY)); // true
System.out.println("time-like: " + tacc.isSupported(ChronoField.NANO_OF_DAY)); // false


DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm", Locale.ENGLISH);
tacc = timeFormatter.parse("17:45");
System.out.println("date-like: " + tacc.isSupported(ChronoField.EPOCH_DAY)); // false
System.out.println("time-like: " + tacc.isSupported(ChronoField.NANO_OF_DAY)); // true

DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm", Locale.ENGLISH);
tacc = dateTimeFormatter.parse("2015-07-24 17:45");
System.out.println("date-like: " + tacc.isSupported(ChronoField.EPOCH_DAY)); // true
System.out.println("time-like: " + tacc.isSupported(ChronoField.NANO_OF_DAY)); // true

但除此之外,我不得不同意@Tunaki 的观点,即从设计的角度来看,对字段使用默认值是更好的主意。要完成上面给出的代码,如果输入与模式不匹配等,您还需要特殊的异常处理。

使用其他人创建的格式化程序进行解析比解析您可以控制的格式化程序更棘手(Tunaki 的 parseDefaulting() 答案是正确的。)但是可以做到:

public static LocalDateTime getDate(String s, DateTimeFormatter format) {
    TemporalAccessor dt = parser.parseBest(
            str, LocalDateTime::from, LocalDate::from, LocalTime::from, YearMonth::from);

    if (dt instanceof LocalDate) {
        return ((LocalDate) dt).atStartOfDay();
    } else if (dt instanceof LocalTime) {
        return ((LocalTime) dt).atDate(LocalDate.now());
    } else if (dt instanceof YearMonth) {
        return ((YearMonth) dt).atDay(1).atStartOfDay();
    } else {
        return LocalDateTime.from(dt);
    }
}

我还没有测试上面的代码,但这是 parseBest() 方法的设计目的。