1

I have the following code for taking a date in the form of a string yyyy-MM-dd HH:mm:ss (UTC timezone) and turning it into EEEE d(st, nd, rd, th) MMMM yyyy HH:mm (device's default timezone).

However, my problem with the way I have done it is the code looks messy and inefficient. Is there a way to achieve what I want without formating and parsing the same date so many times to make it more efficient? Or any other improvements?

Preferably supporting Android API level 14.


String inputExample = "2017-06-28 22:44:55";

//Converts UTC to Device Default (Local)
private String convertUTC(String dateStr) {
    try {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        df.setTimeZone(TimeZone.getTimeZone("UTC"));
        Date temp = df.parse(dateStr);
        df.setTimeZone(TimeZone.getDefault());
        String local = df.format(temp);
        Date localDate = df.parse(dateStr);
        SimpleDateFormat outputDF1 = new SimpleDateFormat("EEEE ");
        SimpleDateFormat outputDF2 = new SimpleDateFormat(" MMMM yyyy HH:mm");
        return outputDF1.format(temp) + prefix(local) + outputDF2.format(temp);
    } catch(java.text.ParseException pE) {
        Log.e("", "Parse Exception", pE);
        return null;
    }
}

private String prefix(String dateStr) {
    try {
        SimpleDateFormat outputDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date temp = outputDF.parse(dateStr);
        SimpleDateFormat df = new SimpleDateFormat("d");
        int d = Integer.parseInt(df.format(temp));
        if(1 <= d && d <= 31) {
            if(11 <= d && d <= 13)
                return d + "th";
            switch (d % 10) {
                case 1: return d + "st";
                case 2: return d + "nd";
                case 3: return d + "rd";
                default: return d + "th";
            }
        }
        Log.e("", "Null Date");
        return null;
    } catch(java.text.ParseException pE) {
        Log.e("", "Parse Exception", pE);
        return null;
    }
}
Dan
  • 5,704
  • 4
  • 31
  • 75
  • 7
    I'm voting to close this question as off-topic because it belongs on [codereview.se]. – shmosel Jun 28 '17 at 22:11
  • @shmosel Thanks for pointing that out. I'll post it there – Dan Jun 28 '17 at 22:12
  • You have to profile your code to find out what causes poor performance. As for the "messy" part - well, java.util.date APIs are widely considered "messy", there's not a lot you can do. Refactor the code to find ways to make it cleaner. Make sure you have a bulletproof suite of unit tests before you start refactoring. – Egor Jun 28 '17 at 22:15
  • If it is decided that this question is closed here follow it on [Code Review](https://codereview.stackexchange.com/questions/166935/parse-and-format-date) – Dan Jun 28 '17 at 22:15

1 Answers1

2

With SimpleDateFormat there's probably not much to improve. As your output format has EEEE (day of the week) and MMMM (month name), you'll have to parse the date in order to know the values for those. Without using a date formatter, you'll have to do lots of if's to get the respective names for each value.


In Android, as an alternative to SimpleDateFormat, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes, together with the ThreeTenABP (more on how to use it here).

All classes are below the org.threeten.bp package. In the code below I'm also using Locale.ENGLISH, otherwise it'll use the system's default (as mine is not English and I'm assuming yours is):

String inputExample = "2017-06-28 22:44:55";
// parser for input
DateTimeFormatter parser = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH);
// parse the date and set to UTC
ZonedDateTime z = LocalDateTime.parse(inputExample, parser).atZone(ZoneOffset.UTC);

// map of custom values - map each numeric value to its string with suffix (st, nd...)
Map<Long, String> textLookup = new HashMap<Long, String>();
for (int i = 1; i <= 31; i++) {
    String suffix = "";
    switch (i) {
    case 1:
    case 21:
    case 31:
        suffix = "st";
        break;
    case 2:
    case 22:
        suffix = "nd";
        break;
    case 3:
    case 23:
        suffix = "rd";
        break;
    default:
        suffix = "th";
    }
    textLookup.put((long) i, i + suffix);
}
// output formatter
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // day of week
    .appendPattern("EEEE ")
    // append day with suffix (use map of custom values)
    .appendText(ChronoField.DAY_OF_MONTH, textLookup)
    // rest of pattern
    .appendPattern(" MMMM yyyy HH:mm")
    // create formatter with English locale
    .toFormatter(Locale.ENGLISH);

// print date, convert it to device default timezone
System.out.println(fmt.format(z.withZoneSameInstant(ZoneId.systemDefault())));

The output will be:

Wednesday 28th June 2017 19:44

The time was set to 19:44 because my default timezone is America/Sao_Paulo (in UTC-03:00).

Not sure if it's less messy enough for you, but at least IMO it's much more clear than SimpleDateFormat. Just 2 formatters were created (one for output, another for output). Of course there's the textLookup map, but it has only 31 entries and the formatters can be reused as well.

And SimpleDateFormat is not thread safe, while the new API is.