解析作为查询参数传递给 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
之类的情况下抛出异常,因为没有时间过去,我正在使用 LocalDate
在 catch 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
这样做。
在您的代码中,您正在转换为 Date
。 java.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;
}
}
我正在开发一个支持日期作为查询参数的 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
之类的情况下抛出异常,因为没有时间过去,我正在使用 LocalDate
在 catch 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
这样做。
在您的代码中,您正在转换为 Date
。 java.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;
}
}