Simple version:
I need to be able to parse two types of Timestamp strings using only one org.threeten.bp.format.DateTimeFormatter
object.
Pattern 1 ("YYYY-MM-DD HH:mm:ss.SSSSSS" -- this code works):
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("YYYY-MM-DD HH:mm:ss.SSSSSS");
System.out.println(dtf.parse("2020-06-30 20:20:42.871216"));
Pattern 2 ("YYYY-MM-DD'T'HH:mm:ss.SSS'Z'" -- this code also works):
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("YYYY-MM-DD'T'HH:mm:ss.SSS'Z'");
System.out.println(dtf.parse("2020-06-30T20:20:42.871Z"));
But I need the single object to parse both (this doesn't work, obviously lol):
DateTimeFormatter dtf = DateTimeFormatter
.ofPattern("YYYY-MM-DD HH:mm:ss.SSSSSS")
.andThisPattern("YYYY-MM-DD'T'HH:mm:ss.SSS'Z'");
System.out.println(dtf.parse("2020-06-30 20:20:42.871216"));
System.out.println(dtf.parse("2020-06-30T20:20:42.871Z"));
I've attempted several things, this is the latest attempt:
DateTimeFormatterBuilder dtfb = new DateTimeFormatterBuilder();
DateTimeFormatter dtf = dtfb
.appendPattern("YYYY-MM-DD HH:mm:ss.SSSSSS")
.appendPattern("YYYY-MM-DD'T'HH:mm:ss.SSS'Z'")
.toFormatter();
System.out.println(dtf.parse("2020-06-30 20:20:42.871216"));
System.out.println(dtf.parse("2020-06-30T20:20:42.871Z"));
But this didn't work. Nothing I do seems to allow the single object to parse two types.
Is there a way to accomplish this?
Larger context (Swagger codegen):
I have an application that interacts with a web service using Java Swagger codegen. The JSON response from the web service contains two timestamp formats (see above). At a certain point in my application, I call JSON#deserialize which attempts to use the (configurable) DateTimeFormatter object. However, it's impossible to know before you make the call what timestamp format you'll have.
2020-07-06 18:53:45 ERROR PartyContactMethodsControllerEmail:352 - org.threeten.bp.format.DateTimeParseException: Text '2020-07-06 18:53:45.449445' could not be parsed at index 10
at org.threeten.bp.format.DateTimeFormatter.parseToBuilder(DateTimeFormatter.java:1587)
at org.threeten.bp.format.DateTimeFormatter.parse(DateTimeFormatter.java:1491)
at org.threeten.bp.OffsetDateTime.parse(OffsetDateTime.java:359)
at webservice.com.webapp.invoker.JSON$OffsetDateTimeTypeAdapter.read(JSON.java:183)
at webservice.com.webapp.invoker.JSON$OffsetDateTimeTypeAdapter.read(JSON.java:1)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)
at com.google.gson.Gson.fromJson(Gson.java:887)
at com.google.gson.Gson.fromJson(Gson.java:852)
at com.google.gson.Gson.fromJson(Gson.java:801)
at webservice.com.webapp.invoker.JSON.deserialize(JSON.java:133)
at webservice.com.webapp.invoker.ApiClient.deserialize(ApiClient.java:711)
at webservice.com.webapp.invoker.ApiClient.handleResponse(ApiClient.java:914)
at webservice.com.webapp.invoker.ApiClient.execute(ApiClient.java:841)
...
So the desearialize
call is unable to successfully parse the JSON response when it finds a timestamp format it doesn't expect.
Original question again: how do I configure the DateTimeFormatter to (before calling deserialize) not choke on the timestamp format? Is there a way I can configure / interface with Swagger codegen to accomodate the server's response?
The basic challenge in what you are asking is that the two formats don’t convey compatible information.
2020-06-30 20:20:42.871216
is a date and time of day without time zone or UTC offset. It probably denotes some point in time, but we are unable to interpret it correctly as such unless we know which time zone is correct for doing so. On the other hand the trailingZ
in2020-06-30T20:20:42.871Z
is an offset of zero form UTC, so here we have the information given to us.In the following code snippet I am assuming that also the format without UTC offset is to be understood in UTC.
Output is:
The difference between having a space and a
T
between date and time I handle through optional elements in the format pattern string. Anything enclosed in square brackets is considered optional, so[ ]['T']
parses an optional space followed by an optional literalT
. The variation in the number of decimals is easy enough since the built-inDateTimeFormatter.ISO_LOCAL_TIME
handles this. This was my main reason why I wanted to use a builder: it allows me to build other formatters into the formatter I am building. Finally the optional offset is handled through a new set of square brackets. UppercaseX
parses an offset that may be given asZ
for zero. SinceZ
is an offset, you must never use a hardcoded literalZ
for parsing it since this throws the offset information away. Parsing the offset allows me to parse the entire string into anOffsetDateTime
. Only how is that possible when the other format hasn’t got an offset? The call toparseDefaulting()
specifies the offset to use when none is parsed.