解析作为查询参数传递给 REST API 的不同日期的标准方法是什么?

What is the Standard way to Parse different Dates passed as Query-Params to the REST API?

我正在开发一个支持日期作为查询参数的 REST API。因为它是查询参数,所以它将是字符串。现在可以在 QueryParams 中以下列格式发送日期:

yyyy-mm-dd[(T| )HH:MM:SS[.fff]][(+|-)NNNN] 

表示以下为有效日期:

2017-05-05 00:00:00.000+0000
2017-05-05 00:00:00.000
2017-05-05T00:00:00
2017-05-05+0000
2017-05-05

现在我正在使用 Java8 datetime api 解析所有这些不同的日期时间。代码如下所示:

DateTimeFormatter formatter = new DateTimeFormatterBuilder().parseCaseInsensitive()
    .append(DateTimeFormatter.ofPattern("yyyy-MM-dd[[ ][['T'][ ]HH:mm:ss[.SSS]][Z]"))
    .toFormatter(); 
LocalDateTime localDateTime = null;
LocalDate localDate = null;
ZoneId zoneId = ZoneId.of(ZoneOffset.UTC.getId());
Date date = null;

try {
    localDateTime = LocalDateTime.parse(datetime, formatter);
    date = Date.from(localDateTime.atZone(zoneId).toInstant());
} catch (Exception exception) {
    System.out.println("Inside Excpetion");
    localDate = LocalDate.parse(datetime, formatter);
    date = Date.from(localDate.atStartOfDay(zoneId).toInstant());
}

从代码中可以看出,我正在使用 DateTimeFormatter 并附加一个模式。现在我首先尝试在 try-block 中将日期解析为 LocalDateTime,如果它在 2017-05-05 之类的情况下抛出异常,因为没有时间过去,我正在使用 LocalDatecatch block

上述方法为我提供了我正在寻找的解决方案,但我的问题是,这是处理作为字符串发送的日期的标准方法吗?我的方法是否符合这些标准?

此外,如果可能的话,除了一些其他简单的解决方案,例如使用数组列表并放置所有可能的格式,然后使用 for -循环尝试解析日期?

我一般用DateUtils.parseDate which belongs to commons-lang.

这个方法看起来像这样:

public static Date parseDate(String str,
                         String... parsePatterns)
                  throws ParseException

描述如下:

Parses a string representing a date by trying a variety of different parsers.

The parse will try each parse pattern in turn. A parse is only deemed successful if it parses the whole of the input string. If no parse patterns match, a ParseException is thrown.

The parser will be lenient toward the parsed date.

    DateTimeFormatter formatter = new DateTimeFormatterBuilder()
            .append(DateTimeFormatter.ISO_LOCAL_DATE)
            // time is optional
            .optionalStart()
            .parseCaseInsensitive()
            .appendPattern("[ ]['T']")
            .append(DateTimeFormatter.ISO_LOCAL_TIME)
            .optionalEnd()
            // offset is optional
            .appendPattern("[xx]")
            .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
            .parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
            .toFormatter();
    for (String queryParam : new String[] {
            "2017-05-05 00:00:00.000+0000",
            "2017-05-05 00:00:00.000",
            "2017-05-05T00:00:00",
            "2017-05-05+0000",
            "2017-05-05",
            "2017-05-05T11:20:30.643+0000",
            "2017-05-05 16:25:09.897+0000",
            "2017-05-05 22:13:55.996",
            "2017-05-05t02:24:01"
    }) {
        Instant inst = OffsetDateTime.parse(queryParam, formatter).toInstant();
        System.out.println(inst);
    }

此片段的输出是:

2017-05-05T00:00:00Z
2017-05-05T00:00:00Z
2017-05-05T00:00:00Z
2017-05-05T00:00:00Z
2017-05-05T00:00:00Z
2017-05-05T11:20:30.643Z
2017-05-05T16:25:09.897Z
2017-05-05T22:13:55.996Z
2017-05-05T02:24:01Z

我使用的技巧包括:

  • 可选部分可以包含在模式中的 optionalStart/optionalEnd[] 中。我两种都用,我觉得每一种都更容易阅读,你可能会有不同的偏好。
  • 日期和时间已经有预定义的格式化程序,所以我重用了它们。特别是我利用了 DateTimeFormatter.ISO_LOCAL_TIME 已经处理可选秒数和秒数的事实。
  • 要解析为 OffsetDateTime 才能正常工作,我们需要为查询参数中可能缺失的部分提供默认值。 parseDefaulting 这样做。

在您的代码中,您正在转换为 Datejava.util.Date class 早已过时并且存在许多设计问题,因此请尽可能避免使用它。 Instant 会很好。如果您确实需要 Date 用于无法更改或不想立即更改的遗留 API,请按照问题中的相同方式进行转换。

编辑:现在默认 HOUR_OF_DAY,而不是 MILLI_OF_DAY。后者在仅缺少毫秒时引起了冲突,但格式化程序似乎对缺少时间的默认小时感到满意。

@Configuration
public class DateTimeConfig extends WebMvcConfigurationSupport {
    /**
     * https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#format-configuring-formatting-globaldatetimeformat
     * @return
     */

    @Bean
    @Override
    public FormattingConversionService mvcConversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
        conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
// Register JSR-310 date conversion with a specific global format
        DateTimeFormatterRegistrar dateTimeRegistrar = new DateTimeFormatterRegistrar();
        dateTimeRegistrar.setDateTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        dateTimeRegistrar.setDateTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
       dateTimeRegistrar.setDateTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"));
       dateTimeRegistrar.registerFormatters(conversionService);

  // Register date conversion with a specific global format
        DateFormatterRegistrar dateRegistrar = new DateFormatterRegistrar();
        dateRegistrar.setFormatter(new DateFormatter("yyyy-MM-dd"));
        dateRegistrar.setFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));
        dateRegistrar.setFormatter(new DateFormatter("yyyy-MM-dd'T'HH:mm:ss'Z'"));
        dateRegistrar.registerFormatters(conversionService);
        return conversionService;
    }
}