Our Rest API is used by several external parties. They all use "ISO-ish" formats, but the formatting of the time zone offset is slightly different. These are some of the most common formats we see:
2018-01-01T15:56:31.410Z
2018-01-01T15:56:31.41Z
2018-01-01T15:56:31Z
2018-01-01T15:56:31+00:00
2018-01-01T15:56:31+0000
2018-01-01T15:56:31+00
In my controller I use the following annotations:
@RequestMapping(value = ["/some/api/call"], method = [GET])
fun someApiCall(
@RequestParam("from")
@DateTimeFormat(iso = ISO.DATE_TIME)
from: OffsetDateTime
) {
...
}
It parses variant 1-4 just fine but produces a 400 Bad Request error for variants 5 and 6 with the following exception:
Caused by: java.time.format.DateTimeParseException: Text '2018-01-01T13:37:00.001+00' could not be parsed, unparsed text found at index 23
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1952)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
How can I make it accept all the above ISO formatting variants (even if they are not 100% compliant to the ISO standard)?
I solved it by adding a custom formatter annotation:
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class IsoDateTime
Plus a FormatterFactory:
class DefensiveDateTimeFormatterFactory :
EmbeddedValueResolutionSupport(), AnnotationFormatterFactory<IsoDateTime>
{
override fun getParser(annotation: IsoDateTime?, fieldType: Class<*>?): Parser<*> {
return Parser<OffsetDateTime> { text, _ -> OffsetDateTime.parse(text, JacksonConfig.defensiveFormatter) }
}
override fun getPrinter(annotation: IsoDateTime, fieldType: Class<*>): Printer<*> {
return Printer<OffsetDateTime> { obj, _ -> obj.format(DateTimeFormatter.ISO_DATE_TIME) }
}
override fun getFieldTypes(): MutableSet<Class<*>> {
return mutableSetOf(OffsetDateTime::class.java)
}
}
The actual DateTimeFormat class comes from my other question, How to parse different ISO date/time formats with Jackson and java.time?
And added it to Spring using WebMvcConfigurer:
@Configuration
open class WebMvcConfiguration : WebMvcConfigurer {
override fun addFormatters(registry: FormatterRegistry) {
registry.addFormatterForFieldAnnotation(DefensiveDateTimeFormatterFactory())
}
}
You get 400-Bad Request
because format 5 and 6 are not part of the ISO
specifications.
If you look at the Time Zone Designator, it can be one of Z
or +hh:mm
or -hh:mm
.
The official list of ISO-8601
formats can be found here.
Year:
YYYY (eg 1997)
Year and month:
YYYY-MM (eg 1997-07)
Complete date:
YYYY-MM-DD (eg 1997-07-16)
Complete date plus hours and minutes:
YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
Complete date plus hours, minutes and seconds:
YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
Complete date plus hours, minutes, seconds and a decimal fraction of a second
YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
where:
YYYY = four-digit year
MM = two-digit month (01=January, etc.)
DD = two-digit day of month (01 through 31)
hh = two digits of hour (00 through 23) (am/pm NOT allowed)
mm = two digits of minute (00 through 59)
ss = two digits of second (00 through 59)
s = one or more digits representing a decimal fraction of a second
TZD = time zone designator (Z or +hh:mm or -hh:mm)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With