0

Having input date string could be possible as

"2020-01-25T21:59:27Z"

or

"Sat Jan 25 20:06:07 +0000 2020"

or a Long

and expect one to display is like

Jan 25, 2020

how to get the desired formatted date string?

update:

@Ole V.V provided a very good suggestion, it's just cannt apply it with android lib case.

but I guess there is no single format for all these three cases, so have to try out one by one. such as for the ISO8601 one to do something like:

    return try { 
            val dateStr = "Sat Jan 25 20:06:07 +0000 2020". //ISO8601
            val format = SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.getDefault())
            val dsipFormat = SimpleDateFormat("MMM dd yyyy", Locale.getDefault()) // for display result
            val date = format.parse(dateStr) // parse it to date
            dsipFormat.format(date) // returning the display result
            } catch (e: Exception) {
                Log.e("+++", "+++ error: $e")
                ""
            }

If there is better approach?

lannyf
  • 9,865
  • 12
  • 70
  • 152
  • I expect `Sat` and `Jan` in your input to be in English always, so I’d use `Locale.ROOT` or another English-speaking locale instead of `Locale.getDefault()` on the first formatter (for displaying the result the default locale is probably the best, on the other hand). – Ole V.V. Mar 16 '20 at 13:11

1 Answers1

1

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

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • thanks @Ole V.V. it's good option, but we have Android api level 21, and it is a lib not application so cannt do something like ``` public class App extends Application { @Override public void onCreate() { super.onCreate(); AndroidThreeTen.init(this); } } ``` – lannyf Mar 15 '20 at 21:37
  • Sorry to hear that. You are right,then it’s a nogo. You can probably do something similar with `SimpleDateFormat`, only I had hoped you would not have needed to, and you will certainly need to do the case of a long separately.. What you say, doe that mean that [Joda-Time](https://www.joda.org/joda-time/) would be out of the question too? – Ole V.V. Mar 16 '20 at 05:26
  • I have hesitatingly added a no-lib solution to the answer. – Ole V.V. Mar 16 '20 at 05:54
  • V.V, thanks for your help! I update in the question. – lannyf Mar 16 '20 at 12:56