1

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.

theAndDev
  • 662
  • 2
  • 11
  • 33

1 Answers1

1

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

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • Hi Ole. Thanks for the well explained response. Looking into it and I have attached my code snippet in the question. I am using to find #days with this library you have mentioned, but I am getting an error '2020-09-09T16:00:00Z' could not be parsed, unparsed text found at index 10. So could you help me with that – theAndDev Sep 04 '20 at 06:51
  • Probably use a `ZonedDateTime` as Z is the zero zone (offset), zone being an addition to `LocalDateTime`. – Joop Eggen Sep 04 '20 at 07:13
  • I wrote the code getting help from here & there. I have updated the snippet in my question. Still would be open for the verification/optimization of the code. – theAndDev Sep 04 '20 at 11:47
  • @OleV.V. if you could kindly show your method if doing it in a more optimal way by posting it in your answer, it would be of great help. – theAndDev Sep 05 '20 at 06:40