Search code examples
javaandroidkotlinsimpledateformatiso8601

Convert any ISO-8601 format to a readable format in Android


So, I know this is a problem well discussed upon and a lot of questions and answers (mostly concerning Joda) and also with the new class DateTimeFormatter and all that supports api levels above 26. But my concern is this:

  1. My android application supports 21 and above
  2. I get multiple variations of date/time formats of ISO-8601 from different APIs: for eg: a) “2020-09-03T17:03:11.719566Z” b) “2021-03-05T18:30:00Z”

So, I require to find #days between today and that date. When I write the code to parse one of them the other hits my catch block and vice versa, so I write a nested try/catch block to handle both the cases...something like this:

fun getFormattedDate(stringDate: String?): Long {
if (dueDateString.isNullOrEmpty())
    return 0
val today = Calendar.getInstance().time
try {
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
    val date = inputFormat.parse(dueDateString)
    return if (date != null) {
        val dueCalendar = Calendar.getInstance()
        dueCalendar.time = date
        getNoOfDays(today.time, dueCalendar.timeInMillis)
    } else
        0
} catch (ex: Exception) {
    ex.showLog()
    try {
val inputFormat1 = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH)
        val date = inputFormat1.parse(dueDateString)
        return if (date != null) {
            val dueCalendar = Calendar.getInstance()
            dueCalendar.time = date
            getNoOfDays(today.time, dueCalendar.timeInMillis)
        } else
            0
    } catch (exc: Exception) {
        exc.showLog()
        return 0
    }
}
}

I am using this function to find #days between two dates:

fun getDueDateAfterParsing(dueDateString: String?): Long {
val today = ZonedDateTime.ofInstant(now(), ZoneId.systemDefault())
val dueDate = ZonedDateTime.parse(
    dueDateString,
    DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneId.systemDefault())
)
return ChronoUnit.DAYS.between(today, dueDate)
}

I am pretty sure that the solution to this cannot be this complex. There are so many formats for ISO-8601 so i cant be writing try/catch blocks that fits for all cases right? So can anybody help me with the most simplest way I can get my way through this?

I have thought about regex too and most of them will end up saying Joda I am guessing, but at-least I want to be sure about what is the most optimal way or the best way of doing this.

Thanks in advance for any help.


Solution

  • java.time and desugaring or ThreeTenABP

    You can use DateTimeFormatter and the other classes from java.time, the modern Java date and time API, on Android API level 21 and up in two ways:

    1. Through desugaring.
    2. Through the backport; there is even an Android adaptation of it, ThreeTenABP, already mentioned by chrylis. It’s ThreeTen for JSR-310, where java.time was first described, and ABP for Android backport.

    For both ways see the links at the bottom.

    Code

    You don’t even need to specify a formatter.

        String stringWeGot = "2020-09-03T17:03:11.719566Z";
        Instant parsed = Instant.parse(stringWeGot);
        System.out.println(parsed);
    

    Output:

    2020-09-03T17:03:11.719566Z

    Or with your other example string:

        String stringWeGot = "2021-03-05T18:30:00Z";
    

    2021-03-05T18:30:00Z

    The classes of java.time parse the most common ISO 8601 variants as their default, that is, without an explicit formatter. Presence or absence of from 0 through 9 decimals on the seconds is built in, even the presence or absence of the seconds themselves. Both Instant and OffsetDateTime can be used in the manner shown in the code.

    Warning! If you do opt for one or more formatters for one reason or another, never hardcode Z as a literal in the format pattern string. Z is an offset (of zero) from UTC and must be parsed as such, or you get incorrect results on the vast majority of Android devices.

    Also when it comes to counting days, java.time is far superior to the old and poorly designed classes like Calendar. EDIT: Your method for counting whole days until the due date in the device time zone is correct. Just for reference, my way of doing it would be:

        ZoneId zone = ZoneId.systemDefault();
        ZonedDateTime today = ZonedDateTime.now(zone);
        ZonedDateTime dueDate = parsed.atZone(zone);
        long daysUntilDue = ChronoUnit.DAYS.between(today, dueDate);
        System.out.println(daysUntilDue);
    

    Using your example string of 2021-03-05T18:30:00Z and running in Europe/Copenhagen time zone just now the result was:

    181

    Links