java.time and ThreeTenABP
My solution is to build three formatters for the three possible input formats and then for each input try the formatters in turn. For a simple demomstration of the idea:
DateTimeFormatter[] inputFormatters = {
DateTimeFormatter.ISO_INSTANT,
DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss xx yyyy", Locale.ROOT),
new DateTimeFormatterBuilder()
.appendValue(ChronoField.INSTANT_SECONDS)
.appendValue(ChronoField.MILLI_OF_SECOND, 3)
.toFormatter()
};
DateTimeFormatter displayFormatter
= DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.US);
for (String inputString : new String[] {
"2020-01-25T21:59:27Z",
"Sat Jan 25 20:06:07 +0000 2020",
"1566777888999"
}) {
// try the formatters in turn and see which one works
for (DateTimeFormatter formatter : inputFormatters) {
try {
ZonedDateTime dateTime = formatter.parse(inputString, Instant.FROM)
.atZone(ZoneId.systemDefault());
System.out.format("%-30s was parsed to %s%n",
inputString, dateTime.format(displayFormatter));
break;
} catch (DateTimeParseException ignore) {
// Ignore, try next format
}
}
}
In my time zone (Europe/Copenhagen) output from this snippet is:
2020-01-25T21:59:27Z was parsed to Jan 25, 2020
Sat Jan 25 20:06:07 +0000 2020 was parsed to Jan 25, 2020
1566777888999 was parsed to Aug 26, 2019
Since it is never the same date in all time zones, output will vary with time zone.
I am recommending java.time, the modern Java date and time API. I saw that you tagged the question simpledateformat, but the SimpleDateFormat
class is notoriously troublesome and long outdated, so I recommend against using it. And I am exploiting the fact that your first format is standard ISO 8601 and that java.time has a built-in formatter for it, DateTimeFormatter.ISO_INSTANT
.
My third input formatter, the one for the long value, regards the last three characters as milliseconds of the second and everything before it as seconds since the epoch. The net result is that it parses milliseconds since the epoch. A DateTimeFormatterBuilder
was required to build this formatter.
A no-lib solution
I admit that I hate to write this. I would really have hoped that you could avoid the notoriously troublesome SimpleDateFormat
class and its long outdated cronies like Date
. Since I understand that yours is a no-lib app, both of Joda-Time and ThreeTenABP seem out of the question. Sorry. In this case since there is no way that SimpleDateFormat
can parse a long, my approach is to take a taste of the string to determine the format and choose my way of parsing based on that.
DateFormat inputIso = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
// This format resembles the output from Date.toString
DateFormat inputDatelike
= new SimpleDateFormat("EEE MMM dd HH:mm:ss ZZZ yyyy", Locale.ROOT);
DateFormat displayFormat
= DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.US);
displayFormat.setTimeZone(TimeZone.getDefault());
for (String inputString : new String[] {
"2020-01-25T21:59:27Z",
"Sat Jan 25 20:06:07 +0000 2020",
"1566777888999"
}) {
Date parsedDate;
if (Character.isDigit(inputString.charAt(0))) {
if (inputString.contains("-")) {
parsedDate = inputIso.parse(inputString);
} else {
// long number of millis
parsedDate = new Date(Long.parseLong(inputString));
}
} else {
parsedDate = inputDatelike.parse(inputString);
}
System.out.format("%-30s was parsed to %s%n",
inputString, displayFormat.format(parsedDate));
}
Output is exactly the same as before:
2020-01-25T21:59:27Z was parsed to Jan 25, 2020
Sat Jan 25 20:06:07 +0000 2020 was parsed to Jan 25, 2020
1566777888999 was parsed to Aug 26, 2019
Please be aware that here invalid input may cause either a NumberFormatException
or a ParseException
, so catch both. And only resort to this solution if there is no way that you can avoid it.
The line displayFormat.setTimeZone(TimeZone.getDefault());
is technically superfluous, but it makes explicit that the output depends on the time zone, and maybe more importantly, it tells you where you need to modify the code if you want output in a different time zone.
Question: Doesn’t java.time require Android API level 26?
java.time works nicely on both older and newer Android devices. It just requires at least Java 6.
- In Java 8 and later and on newer Android devices (from API level 26) the modern API comes built-in. Only in this case use the method reference
Instant::from
instead of the constant Instant.FROM
.
- In non-Android Java 6 and 7 get the ThreeTen Backport, the backport of the modern classes (ThreeTen for JSR 310; see the links at the bottom).
- On (older) Android use the Android edition of ThreeTen Backport. It’s called ThreeTenABP. And make sure you import the date and time classes from
org.threeten.bp
with subpackages.
Links