Reputation: 1666
My application should be able to parse date ignoring timezone (I always know for sure that it is UTC). The problem is that the date might come in both following forms -
2017-09-11T12:44:07.793Z
0001-01-01T00:00:00
I can parse the first one using LocalDateTime
, and the second one using Instant
class. Is there a way to do that using a single mechanism?
P.S. I'm trying to avoid hardcoding Z
at the end of the input string
Upvotes: 8
Views: 3677
Reputation: 3993
You can "parseBest", like this:
DateTimeFormatter parser = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[Z]");
Temporal parsed = parser.parseBest(inputString, Instant::from, LocalDateTime::from);
Then you should check what did get parsed, and act accordingly.
The parseBest
method will work with any type of TemporalQuery
, including most of from
methods available on java.time
classes. So you can lengthen that list with LocalDate.from
, for example.
You can also use that method and lambdas to coerse parse results to the type you want without having instanceof
checks that are external for result resolution (although not without one cast):
Instant parsed = (Instant) parser.parseBest(inputString,
Instant::from,
interResult -> LocalDateTime.from(interResult).atZone(ZoneOffset.UTC).toInstant())
Notice that second option uses lambda that converts LocalDateTime
to ZonedDateTime
and then to Instant
, so the parse results are always coersed to Instant
.
Upvotes: 5
Reputation:
If the Z
offset is optional, you can use a java.time.format.DateTimeFormatterBuilder
with an optional section:
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// date/time
.append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
// optional offset
.optionalStart().appendOffsetId()
// create formatter
.toFormatter();
Then you can use the parseBest
method, with a list of TemporalQuery
's that tries to create the correspondent object. Then you check the return type and act accordingly:
Instant instant = null;
// tries to create Instant, and if it fails, try a LocalDateTime
TemporalAccessor parsed = fmt.parseBest("2017-09-11T12:44:07.793Z", Instant::from, LocalDateTime::from);
if (parsed instanceof Instant) {
instant = (Instant) parsed;
} else if (parsed instanceof LocalDateTime) {
// convert LocalDateTime to UTC instant
instant = ((LocalDateTime) parsed).atOffset(ZoneOffset.UTC).toInstant();
}
System.out.println(instant); // 2017-09-11T12:44:07.793Z
Running with the second input (0001-01-01T00:00:00
) produces the Instant
equivalent to 0001-01-01T00:00:00Z
.
In the example above, I used just Instant::from
and LocalDateTime::from
, so the formatter tries to first create an Instant
. If it's not possible, then it tries to create a LocalDateTime
. You can add as many types you want to that list (for example, I could add ZonedDateTime::from
, and if a ZonedDateTime
is created, I could just convert to Instant
using toInstant()
method).
As you know for sure that the input is always in UTC, you can also set it directly in the formatter:
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// date/time
.append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
// optional offset
.optionalStart().appendOffsetId()
// create formatter with UTC
.toFormatter().withZone(ZoneOffset.UTC);
So you can parse it directly to Instant
:
System.out.println(Instant.from(fmt.parse("2017-09-11T12:44:07.793Z"))); // 2017-09-11T12:44:07.793Z
System.out.println(Instant.from(fmt.parse("0001-01-01T00:00:00"))); // 0001-01-01T00:00:00Z
Upvotes: 15